[go: nahoru, domu]

Fix a pixel difference in width when measured in RTL

This is reproduced when letter spacing is provided.
For more details, please check the bug.

Fixes: 233856978
Test: new tests

Change-Id: I52143ddb41da78ae5e21e259339206a8f93b979c
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextDelegateIntegrationTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextDelegateIntegrationTest.kt
index 1e699be..3120601 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextDelegateIntegrationTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextDelegateIntegrationTest.kt
@@ -183,6 +183,23 @@
         assertThat(layoutResult.lineCount).isEqualTo(2)
         assertThat(layoutResult.isLineEllipsized(1)).isTrue()
     }
+
+    @Test
+    fun TextLayoutResult_sameWidth_inRtlAndLtr_withLetterSpacing() {
+        val fontSize = 20f
+        val text = AnnotatedString(text = "Hello World")
+        val textDelegate = TextDelegate(
+            text = text,
+            style = TextStyle(fontSize = fontSize.sp, letterSpacing = 0.5.sp),
+            overflow = TextOverflow.Ellipsis,
+            density = density,
+            fontFamilyResolver = fontFamilyResolver
+        )
+        val layoutResultLtr = textDelegate.layout(Constraints(), LayoutDirection.Ltr)
+        val layoutResultRtl = textDelegate.layout(Constraints(), LayoutDirection.Rtl)
+
+        assertThat(layoutResultLtr.size.width).isEqualTo(layoutResultRtl.size.width)
+    }
 }
 
 private fun TextLayoutResult.toBitmap() = Bitmap.createBitmap(
diff --git a/text/text/src/androidTest/java/androidx/compose/ui/text/android/TextLayoutIntrinsicWidthTest.kt b/text/text/src/androidTest/java/androidx/compose/ui/text/android/TextLayoutIntrinsicWidthTest.kt
index 1d24cbfb..83169be 100644
--- a/text/text/src/androidTest/java/androidx/compose/ui/text/android/TextLayoutIntrinsicWidthTest.kt
+++ b/text/text/src/androidTest/java/androidx/compose/ui/text/android/TextLayoutIntrinsicWidthTest.kt
@@ -125,6 +125,28 @@
         assertLineCount(defaultText)
     }
 
+    @Test
+    fun intrinsicWidth_sameInLtrAndRtl() {
+        val text = SpannableString("asdf")
+
+        val intrinsicsLtr = LayoutIntrinsics(text, defaultPaint, LayoutCompat.TEXT_DIRECTION_LTR)
+        val intrinsicsRtl = LayoutIntrinsics(text, defaultPaint, LayoutCompat.TEXT_DIRECTION_RTL)
+
+        assertThat(intrinsicsLtr.maxIntrinsicWidth).isEqualTo(intrinsicsRtl.maxIntrinsicWidth)
+    }
+
+    @Test
+    fun intrinsicWidth_sameInLtrAndRtl_withLetterSpacing() {
+        val text = SpannableString("asdf").apply {
+            setSpan(LetterSpacingSpanPx(letterSpacingPx))
+        }
+
+        val intrinsicsLtr = LayoutIntrinsics(text, defaultPaint, LayoutCompat.TEXT_DIRECTION_LTR)
+        val intrinsicsRtl = LayoutIntrinsics(text, defaultPaint, LayoutCompat.TEXT_DIRECTION_RTL)
+
+        assertThat(intrinsicsLtr.maxIntrinsicWidth).isEqualTo(intrinsicsRtl.maxIntrinsicWidth)
+    }
+
     private fun assertLineCount(text: CharSequence, paint: TextPaint = defaultPaint) {
         val intrinsics = LayoutIntrinsics(text, paint, LayoutCompat.TEXT_DIRECTION_LTR)
         assertThat(
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/LayoutIntrinsics.kt b/text/text/src/main/java/androidx/compose/ui/text/android/LayoutIntrinsics.kt
index d9b29cd..af9cb35 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/LayoutIntrinsics.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/LayoutIntrinsics.kt
@@ -24,6 +24,7 @@
 import androidx.compose.ui.text.android.style.LetterSpacingSpanPx
 import java.text.BreakIterator
 import java.util.PriorityQueue
+import kotlin.math.ceil
 
 /**
  * Computes and caches the text layout intrinsic values such as min/max width.
@@ -59,8 +60,17 @@
      * of text where no soft line breaks are applied.
      */
     val maxIntrinsicWidth: Float by lazy(LazyThreadSafetyMode.NONE) {
-        var desiredWidth: Float = boringMetrics?.width?.toFloat()
-            ?: Layout.getDesiredWidth(charSequence, 0, charSequence.length, textPaint)
+        var desiredWidth = boringMetrics?.width?.toFloat()
+
+        // boring metrics doesn't cover RTL text so we fallback to different calculation when boring
+        // metrics can't be calculated
+        if (desiredWidth == null) {
+            // b/233856978, apply `ceil` function here to be consistent with the boring metrics
+            // width calculation that does it under the hood, too
+            desiredWidth = ceil(
+                Layout.getDesiredWidth(charSequence, 0, charSequence.length, textPaint)
+            )
+        }
         if (shouldIncreaseMaxIntrinsic(desiredWidth, charSequence, textPaint)) {
             // b/173574230, increase maxIntrinsicWidth, so that StaticLayout won't form 2
             // lines for the given maxIntrinsicWidth