Skip to content

Commit 6306468

Browse files
imsankalpmeta-codesync[bot]
authored andcommitted
Fix adjustsFontSizeToFit clipping when container height shrinks on Android (#57597)
Summary: When a `<Text adjustsFontSizeToFit>` component's container height decreases dynamically (e.g. height=60 → height=20), the text is clipped instead of having its font size recalculated. **Root cause:** `mShouldAdjustSpannableFontSize` — the gate that triggers font recalculation in `onDraw` — is only set by text-content setters (`setSpanned`, `setNumberOfLines`, etc.). A pure container resize never sets it. Meanwhile the background Fabric measurement thread mutates the shared cached spannable (`tagToSpannableCache`) to the larger font size. When the container then shrinks, `onDraw` skips recalculation and draws the stale large font in the smaller container, causing clipping. **Fix:** Override `onSizeChanged` in `ReactTextView` to set `mShouldAdjustSpannableFontSize = true` whenever the view dimensions change and `adjustsFontSizeToFit` is active. Android automatically schedules a redraw on size change, so the next `onDraw` recalculates the font with the correct current height. **Note on the shared spannable:** the recalculation in `onDraw` mutates the (possibly shared) cached spannable on the UI thread via `adjustSpannableFontToFit` followed by `setText`. This change makes that recalculation also run on a container resize, so it rides the existing UI-thread mutation of the shared spannable a bit more often. It is the same mutation already performed on content changes, so it does not introduce a new race, but it is worth flagging if the spannable caching is revisited. ## Changelog: [ANDROID] [FIXED] - Fix Text with adjustsFontSizeToFit being clipped when container height decreases dynamically Pull Request resolved: #57597 Test Plan: Tested using the existing RNTester example which already reproduces this bug: 1. Build and run RNTester on Android 2. Navigate to **Text** → **Font Size Adjustment with Dynamic Layout** 3. Press **Set Height to 60** — text grows to fill the yellow container 4. Press **Set Height to 20** — before this fix, text is clipped; after this fix, text shrinks to fit correctly 5. Repeat steps 3–4 rapidly — no clipping at any point The reproducer is `packages/rn-tester/js/examples/Text/TextAdjustsDynamicLayoutExample.js`. Automated: added `ReactTextViewTest.adjustsFontSizeToFitRecalculatesWhenContainerHeightShrinks`, which lays out an `adjustsFontSizeToFit` text at a tall height then a short height and asserts the `ReactAbsoluteSizeSpan` font size shrinks. It fails without this change (the resize does not retrigger recalculation) and passes with it. NOTE: Addition video provided by the author in the PR. Reviewed By: cortinico Differential Revision: D112811263 Pulled By: fabriziocucci fbshipit-source-id: 411f61c9f678a3fc36a073300ce3cb57b56e2be6
1 parent 3861118 commit 6306468

3 files changed

Lines changed: 62 additions & 0 deletions

File tree

packages/react-native/ReactAndroid/api/ReactAndroid.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6029,6 +6029,7 @@ public class com/facebook/react/views/text/ReactTextView : androidx/appcompat/wi
60296029
public final fun onFocusChanged (ZILandroid/graphics/Rect;)V
60306030
protected fun onLayout (ZIIII)V
60316031
protected fun onMeasure (II)V
6032+
protected fun onSizeChanged (IIII)V
60326033
public fun reactTagForTouch (FF)I
60336034
public fun setAdjustFontSizeToFit (Z)V
60346035
public fun setBackgroundColor (I)V

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,14 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
313313
}
314314
}
315315

316+
@Override
317+
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
318+
super.onSizeChanged(w, h, oldw, oldh);
319+
if (mAdjustsFontSizeToFit && (w != oldw || h != oldh)) {
320+
mShouldAdjustSpannableFontSize = true;
321+
}
322+
}
323+
316324
public void setText(ReactTextUpdate update) {
317325
try (SystraceSection s = new SystraceSection("ReactTextView.setText(ReactTextUpdate)")) {
318326
// Android's TextView crashes when it tries to relayout if LayoutParams are

packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/text/ReactTextViewTest.kt

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ import android.text.style.ReplacementSpan
1818
import android.util.TypedValue
1919
import android.view.Gravity
2020
import android.view.View
21+
import android.view.ViewGroup
2122
import androidx.core.graphics.createBitmap
2223
import androidx.core.graphics.get
24+
import com.facebook.react.views.text.internal.span.ReactAbsoluteSizeSpan
2325
import org.assertj.core.api.Assertions.assertThat
2426
import org.junit.Test
2527
import org.junit.runner.RunWith
@@ -45,6 +47,57 @@ class ReactTextViewTest {
4547
assertThat(firstVisiblePixelY(bitmap)).isGreaterThanOrEqualTo(lineHeight)
4648
}
4749

50+
@Test
51+
fun adjustsFontSizeToFitRecalculatesWhenContainerHeightShrinks() {
52+
val width = 200
53+
val tallHeight = 200
54+
val shortHeight = 20
55+
val largeFontSize = 40
56+
57+
val text = SpannableString("Hello")
58+
text.setSpan(
59+
ReactAbsoluteSizeSpan(largeFontSize),
60+
0,
61+
text.length,
62+
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
63+
)
64+
65+
val view = TestReactTextView(RuntimeEnvironment.getApplication())
66+
view.layoutParams =
67+
ViewGroup.LayoutParams(
68+
ViewGroup.LayoutParams.WRAP_CONTENT,
69+
ViewGroup.LayoutParams.WRAP_CONTENT,
70+
)
71+
view.setTextColor(Color.BLACK)
72+
view.setMinimumFontSize(4f)
73+
view.setNumberOfLines(0)
74+
view.setAdjustFontSizeToFit(true)
75+
view.setSpanned(text)
76+
view.text = text
77+
78+
layoutAndDraw(view, width, tallHeight)
79+
val fontSizeWhenTall = largestAbsoluteSizeSpan(text)
80+
81+
// Shrinking the container must retrigger the font recalculation, otherwise the
82+
// stale large font is drawn into the smaller container and gets clipped.
83+
layoutAndDraw(view, width, shortHeight)
84+
val fontSizeWhenShort = largestAbsoluteSizeSpan(text)
85+
86+
assertThat(fontSizeWhenShort).isLessThan(fontSizeWhenTall)
87+
}
88+
89+
private fun layoutAndDraw(view: TestReactTextView, width: Int, height: Int) {
90+
view.measure(
91+
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
92+
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY),
93+
)
94+
view.layout(0, 0, width, height)
95+
view.drawTextForTest(Canvas(createBitmap(width, height)))
96+
}
97+
98+
private fun largestAbsoluteSizeSpan(text: Spanned): Int =
99+
text.getSpans(0, text.length, ReactAbsoluteSizeSpan::class.java).maxOfOrNull { it.size } ?: 0
100+
48101
private fun drawReactTextViewWithOverflow(overflow: String?): Bitmap {
49102
return drawReactTextViewWithOverflow(overflow, lineHeight = 24, viewHeight = 24, gravity = null)
50103
}

0 commit comments

Comments
 (0)