Skip to content

Commit d833014

Browse files
TorinAsakurameta-codesync[bot]
authored andcommitted
Fix Android Text clipping with Bold text setting (#57118)
Summary: Fixes #54931. Android's Bold text accessibility setting updates `Configuration.fontWeightAdjustment`, which makes platform `TextView` draw text with an adjusted typeface. React Native was measuring Text without that adjustment, so Yoga could allocate a row item width that was too small and the rendered text was clipped. This passes the current font weight adjustment through Fabric text measurement, prepared text layout, mounted Text spans, and TextInput spans, and applies it to the `TextPaint`/`CustomStyleSpan` typeface used for measurement and drawing. ## Changelog: [ANDROID] [FIXED] - Account for Android Bold text font weight adjustment when measuring Text. Pull Request resolved: #57118 Test Plan: - `./gradlew :packages:react-native:ReactAndroid:testDebugUnitTest --tests com.facebook.react.views.text.TextLayoutManagerFontWeightAdjustmentTest -Preact.internal.useHermesStable=true` - `./gradlew :packages:react-native:ReactAndroid:ktfmtCheck -Preact.internal.useHermesStable=true` Reproduced locally before the fix with https://github.com/Lego4m/reproducer-react-native-android-bold-text on Android 16 AVD by toggling `font_weight_adjustment` between `0` and `300`. Screenshots from #54931: | Bold text off | Bold text on | | --- | --- | | <img width="1280" height="2856" alt="Bold text off" src="https://github.com/user-attachments/assets/0e3b9be7-7935-438c-a564-084904efddc4" /> | <img width="1280" height="2856" alt="Bold text on" src="https://github.com/user-attachments/assets/9745fecc-57a1-436a-8665-0282a241dd26" /> | Reviewed By: mdvacca Differential Revision: D108136049 Pulled By: javache fbshipit-source-id: 8299b94708cafebd91fdff9f6726b599a0b72445
1 parent b364490 commit d833014

29 files changed

Lines changed: 511 additions & 103 deletions

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6079,7 +6079,9 @@ public abstract interface class com/facebook/react/views/text/ReactTextViewManag
60796079

60806080
public final class com/facebook/react/views/text/ReactTypefaceUtils {
60816081
public static final field INSTANCE Lcom/facebook/react/views/text/ReactTypefaceUtils;
6082+
public static final fun applyFontWeightAdjustment (Landroid/graphics/Typeface;I)Landroid/graphics/Typeface;
60826083
public static final fun applyStyles (Landroid/graphics/Typeface;IILjava/lang/String;Landroid/content/res/AssetManager;)Landroid/graphics/Typeface;
6084+
public static final fun getFontWeightAdjustment (Landroid/content/Context;)I
60836085
public static final fun parseFontStyle (Ljava/lang/String;)I
60846086
public static final fun parseFontVariant (Lcom/facebook/react/bridge/ReadableArray;)Ljava/lang/String;
60856087
public static final fun parseFontWeight (Ljava/lang/String;)I

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
import com.facebook.react.views.text.PreparedLayout;
9494
import com.facebook.react.views.text.ReactTextViewManager;
9595
import com.facebook.react.views.text.ReactTextViewManagerCallback;
96+
import com.facebook.react.views.text.ReactTypefaceUtils;
9697
import com.facebook.react.views.text.TextEffectRegistry;
9798
import com.facebook.react.views.text.TextLayoutManager;
9899
import java.util.ArrayList;
@@ -553,6 +554,7 @@ private NativeArray measureLines(
553554
return (NativeArray)
554555
TextLayoutManager.measureLines(
555556
mReactApplicationContext.getAssets(),
557+
ReactTypefaceUtils.getFontWeightAdjustment(mReactApplicationContext),
556558
attributedString,
557559
paragraphAttributes,
558560
PixelUtil.toPixelFromDIP(width),
@@ -639,6 +641,7 @@ public long measureText(
639641

640642
return TextLayoutManager.measureText(
641643
mReactApplicationContext.getAssets(),
644+
ReactTypefaceUtils.getFontWeightAdjustment(mReactApplicationContext),
642645
attributedString,
643646
paragraphAttributes,
644647
getYogaSize(minWidth, maxWidth),
@@ -666,6 +669,7 @@ public PreparedLayout prepareTextLayout(
666669

667670
return TextLayoutManager.createPreparedLayout(
668671
mReactApplicationContext.getAssets(),
672+
ReactTypefaceUtils.getFontWeightAdjustment(mReactApplicationContext),
669673
attributedString,
670674
paragraphAttributes,
671675
getYogaSize(minWidth, maxWidth),

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<00bb39539f4fc2dc0ad85a346bdee22b>>
7+
* @generated SignedSource<<8b571241be0c86847ed265ab665059b2>>
88
*/
99

1010
/**
@@ -102,6 +102,12 @@ public object ReactNativeFeatureFlags {
102102
@JvmStatic
103103
public fun enableAccumulatedUpdatesInRawPropsAndroid(): Boolean = accessor.enableAccumulatedUpdatesInRawPropsAndroid()
104104

105+
/**
106+
* When enabled, Android Text measurement and rendering respects the system Bold text accessibility setting via `Configuration.fontWeightAdjustment`.
107+
*/
108+
@JvmStatic
109+
public fun enableAndroidFontWeightAdjustment(): Boolean = accessor.enableAndroidFontWeightAdjustment()
110+
105111
/**
106112
* Enables various optimizations throughout the path of measuring text on Android.
107113
*/

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<731e92ecac8dbdf0a4b204b763a2c8c7>>
7+
* @generated SignedSource<<856ed167e90b330583c3a6e981927c43>>
88
*/
99

1010
/**
@@ -32,6 +32,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
3232
private var disableViewPreallocationAndroidCache: Boolean? = null
3333
private var enableAccessibilityOrderCache: Boolean? = null
3434
private var enableAccumulatedUpdatesInRawPropsAndroidCache: Boolean? = null
35+
private var enableAndroidFontWeightAdjustmentCache: Boolean? = null
3536
private var enableAndroidTextMeasurementOptimizationsCache: Boolean? = null
3637
private var enableBridgelessArchitectureCache: Boolean? = null
3738
private var enableCppPropsIteratorSetterCache: Boolean? = null
@@ -213,6 +214,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
213214
return cached
214215
}
215216

217+
override fun enableAndroidFontWeightAdjustment(): Boolean {
218+
var cached = enableAndroidFontWeightAdjustmentCache
219+
if (cached == null) {
220+
cached = ReactNativeFeatureFlagsCxxInterop.enableAndroidFontWeightAdjustment()
221+
enableAndroidFontWeightAdjustmentCache = cached
222+
}
223+
return cached
224+
}
225+
216226
override fun enableAndroidTextMeasurementOptimizations(): Boolean {
217227
var cached = enableAndroidTextMeasurementOptimizationsCache
218228
if (cached == null) {

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<4d0952ded695aec4a52b7b581ad19979>>
7+
* @generated SignedSource<<2f68282c0b8c1971acdab2f229189d82>>
88
*/
99

1010
/**
@@ -52,6 +52,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
5252

5353
@DoNotStrip @JvmStatic public external fun enableAccumulatedUpdatesInRawPropsAndroid(): Boolean
5454

55+
@DoNotStrip @JvmStatic public external fun enableAndroidFontWeightAdjustment(): Boolean
56+
5557
@DoNotStrip @JvmStatic public external fun enableAndroidTextMeasurementOptimizations(): Boolean
5658

5759
@DoNotStrip @JvmStatic public external fun enableBridgelessArchitecture(): Boolean

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<4227353cb809dac40fe70d83154ba2bc>>
7+
* @generated SignedSource<<5f946970ba2e0676b55cc150de4bee01>>
88
*/
99

1010
/**
@@ -47,6 +47,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
4747

4848
override fun enableAccumulatedUpdatesInRawPropsAndroid(): Boolean = false
4949

50+
override fun enableAndroidFontWeightAdjustment(): Boolean = true
51+
5052
override fun enableAndroidTextMeasurementOptimizations(): Boolean = false
5153

5254
override fun enableBridgelessArchitecture(): Boolean = false

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<156af11c13257170355284118e400435>>
7+
* @generated SignedSource<<c7adeebaabd6c647865bb5398b823221>>
88
*/
99

1010
/**
@@ -36,6 +36,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
3636
private var disableViewPreallocationAndroidCache: Boolean? = null
3737
private var enableAccessibilityOrderCache: Boolean? = null
3838
private var enableAccumulatedUpdatesInRawPropsAndroidCache: Boolean? = null
39+
private var enableAndroidFontWeightAdjustmentCache: Boolean? = null
3940
private var enableAndroidTextMeasurementOptimizationsCache: Boolean? = null
4041
private var enableBridgelessArchitectureCache: Boolean? = null
4142
private var enableCppPropsIteratorSetterCache: Boolean? = null
@@ -229,6 +230,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
229230
return cached
230231
}
231232

233+
override fun enableAndroidFontWeightAdjustment(): Boolean {
234+
var cached = enableAndroidFontWeightAdjustmentCache
235+
if (cached == null) {
236+
cached = currentProvider.enableAndroidFontWeightAdjustment()
237+
accessedFeatureFlags.add("enableAndroidFontWeightAdjustment")
238+
enableAndroidFontWeightAdjustmentCache = cached
239+
}
240+
return cached
241+
}
242+
232243
override fun enableAndroidTextMeasurementOptimizations(): Boolean {
233244
var cached = enableAndroidTextMeasurementOptimizationsCache
234245
if (cached == null) {

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<b1dd783c9075c9a35cbd06aa812e56c9>>
7+
* @generated SignedSource<<c4f37ab0fc935a27bae3b92e86abc243>>
88
*/
99

1010
/**
@@ -47,6 +47,8 @@ public interface ReactNativeFeatureFlagsProvider {
4747

4848
@DoNotStrip public fun enableAccumulatedUpdatesInRawPropsAndroid(): Boolean
4949

50+
@DoNotStrip public fun enableAndroidFontWeightAdjustment(): Boolean
51+
5052
@DoNotStrip public fun enableAndroidTextMeasurementOptimizations(): Boolean
5153

5254
@DoNotStrip public fun enableBridgelessArchitecture(): Boolean

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import com.facebook.react.uimanager.style.BorderRadiusProp
4343
import com.facebook.react.uimanager.style.BorderStyle.Companion.fromString
4444
import com.facebook.react.uimanager.style.LogicalEdge
4545
import com.facebook.react.views.text.DefaultStyleValuesUtil.getDefaultTextColorHighlight
46+
import com.facebook.react.views.text.ReactTypefaceUtils.getFontWeightAdjustment
4647
import java.util.HashMap
4748

4849
/** View manager for `<Text>` nodes. */
@@ -158,6 +159,7 @@ public constructor(
158159
val spanned: Spannable =
159160
TextLayoutManager.getOrCreateSpannableForText(
160161
view.context.assets,
162+
getFontWeightAdjustment(view.context),
161163
attributedString,
162164
reactTextViewManagerCallback,
163165
TextEffectRegistry.current,

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,23 @@
77

88
package com.facebook.react.views.text
99

10+
import android.content.Context
1011
import android.content.res.AssetManager
12+
import android.content.res.Configuration
1113
import android.graphics.Typeface
14+
import android.os.Build
1215
import com.facebook.react.bridge.ReadableArray
1316
import com.facebook.react.common.ReactConstants
1417
import com.facebook.react.common.assets.ReactFontManager
18+
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
19+
import kotlin.math.max
20+
import kotlin.math.min
1521

1622
public object ReactTypefaceUtils {
1723

24+
private const val FONT_WEIGHT_MIN = 1
25+
private const val FONT_WEIGHT_MAX = 1000
26+
1827
@JvmStatic
1928
public fun parseFontWeight(fontWeightString: String?): Int =
2029
when (fontWeightString) {
@@ -110,4 +119,36 @@ public object ReactTypefaceUtils {
110119
ReactFontManager.getInstance().getTypeface(fontFamilyName, typefaceStyle, assetManager)
111120
}
112121
}
122+
123+
@JvmStatic
124+
public fun getFontWeightAdjustment(context: Context): Int =
125+
if (
126+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
127+
ReactNativeFeatureFlags.enableAndroidFontWeightAdjustment()
128+
) {
129+
context.resources.configuration.fontWeightAdjustment
130+
} else {
131+
0
132+
}
133+
134+
@JvmStatic
135+
public fun applyFontWeightAdjustment(
136+
typeface: Typeface?,
137+
fontWeightAdjustment: Int,
138+
): Typeface? {
139+
if (
140+
Build.VERSION.SDK_INT < Build.VERSION_CODES.S ||
141+
fontWeightAdjustment == 0 ||
142+
fontWeightAdjustment == Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED
143+
) {
144+
return typeface
145+
}
146+
147+
val baseTypeface = typeface ?: Typeface.DEFAULT
148+
val adjustedWeight =
149+
min(max(baseTypeface.weight + fontWeightAdjustment, FONT_WEIGHT_MIN), FONT_WEIGHT_MAX)
150+
val italic = baseTypeface.style and Typeface.ITALIC != 0
151+
152+
return Typeface.create(baseTypeface, adjustedWeight, italic)
153+
}
113154
}

0 commit comments

Comments
 (0)