Skip to content

Commit 8baa858

Browse files
RickardZrinskirobhogan
authored andcommitted
Fix maxFontSizeMultiplier prop on Text and TextInput components in new architecture (facebook#47614)
Summary: The `maxFontSizeMultiplier` prop for `Text` and `TextInput` was not handled in Fabric / New Architecture as documented in facebook#47499. bypass-github-export-checks [GENERAL] [FIXED] - Fix `maxFontSizeMultiplier` prop on `Text` and `TextInput` components in Fabric / New Architecture Pull Request resolved: facebook#47614 Test Plan: I have not added any automated tests for this change but try to do so if requested. I have however added examples to RN Tester for both the Text and TextInput components, as well as compared the behaviour with Paper / Old Architecture. Both on version 0.76. Noticed now I didn't do exactly the same steps in both videos, oops! Be aware that reapplying changes made in the Settings are currently half-broken on the new architecture, thus I'm restarting the app on Android and iOS. But this issue is unrelated to my changes. I've tested on main branch and it has the same issue. Here are comparison videos between Paper and Fabric on iOS *after* I've made my fix. | Paper | Fabric | | ------------- | ------------- | | <video src="https://github.com/user-attachments/assets/f4fd009f-aa6d-41ab-92fa-8dcf1e351ba1" /> | <video src="https://github.com/user-attachments/assets/fda42cc6-34c2-42a7-a6e2-028e7c866075" /> | | Paper | Fabric | | ------------- | ------------- | | <video src="https://github.com/user-attachments/assets/59b59f7b-25d2-4b5b-a8e2-d2054cc6390b" /> | <video src="https://github.com/user-attachments/assets/72068566-8f2a-4463-874c-45a6f5b63b0d" /> | Reviewed By: Abbondanzo Differential Revision: D65953019 Pulled By: cipolleschi fbshipit-source-id: 90c3c7e236229e9ad9bd346941fafe4af8a9d9fc
1 parent 877e82c commit 8baa858

File tree

11 files changed

+274
-5
lines changed

11 files changed

+274
-5
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7443,6 +7443,7 @@ public class com/facebook/react/views/text/TextAttributeProps {
74437443
public static final field TA_KEY_LETTER_SPACING S
74447444
public static final field TA_KEY_LINE_BREAK_STRATEGY S
74457445
public static final field TA_KEY_LINE_HEIGHT S
7446+
public static final field TA_KEY_MAX_FONT_SIZE_MULTIPLIER S
74467447
public static final field TA_KEY_OPACITY S
74477448
public static final field TA_KEY_ROLE S
74487449
public static final field TA_KEY_TEXT_DECORATION_COLOR S
@@ -7475,6 +7476,7 @@ public class com/facebook/react/views/text/TextAttributeProps {
74757476
protected field mLetterSpacingInput F
74767477
protected field mLineHeight F
74777478
protected field mLineHeightInput F
7479+
protected field mMaxFontSizeMultiplier F
74787480
protected field mNumberOfLines I
74797481
protected field mOpacity F
74807482
protected field mRole Lcom/facebook/react/uimanager/ReactAccessibilityDelegate$Role;

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public class TextAttributeProps {
6161
public static final short TA_KEY_LINE_BREAK_STRATEGY = 25;
6262
public static final short TA_KEY_ROLE = 26;
6363
public static final short TA_KEY_TEXT_TRANSFORM = 27;
64+
public static final short TA_KEY_MAX_FONT_SIZE_MULTIPLIER = 29;
6465

6566
public static final int UNSET = -1;
6667

@@ -81,6 +82,7 @@ public class TextAttributeProps {
8182
protected float mLineHeight = Float.NaN;
8283
protected boolean mIsColorSet = false;
8384
protected boolean mAllowFontScaling = true;
85+
protected float mMaxFontSizeMultiplier = Float.NaN;
8486
protected int mColor;
8587
protected boolean mIsBackgroundColorSet = false;
8688
protected int mBackgroundColor;
@@ -227,6 +229,9 @@ public static TextAttributeProps fromMapBuffer(MapBuffer props) {
227229
case TA_KEY_TEXT_TRANSFORM:
228230
result.setTextTransform(entry.getStringValue());
229231
break;
232+
case TA_KEY_MAX_FONT_SIZE_MULTIPLIER:
233+
result.setMaxFontSizeMultiplier((float) entry.getDoubleValue());
234+
break;
230235
}
231236
}
232237

@@ -243,6 +248,8 @@ public static TextAttributeProps fromReadableMap(ReactStylesDiffMap props) {
243248
result.setLineHeight(getFloatProp(props, ViewProps.LINE_HEIGHT, ReactConstants.UNSET));
244249
result.setLetterSpacing(getFloatProp(props, ViewProps.LETTER_SPACING, Float.NaN));
245250
result.setAllowFontScaling(getBooleanProp(props, ViewProps.ALLOW_FONT_SCALING, true));
251+
result.setMaxFontSizeMultiplier(
252+
getFloatProp(props, ViewProps.MAX_FONT_SIZE_MULTIPLIER, Float.NaN));
246253
result.setFontSize(getFloatProp(props, ViewProps.FONT_SIZE, ReactConstants.UNSET));
247254
result.setColor(props.hasKey(ViewProps.COLOR) ? props.getInt(ViewProps.COLOR, 0) : null);
248255
result.setColor(
@@ -411,7 +418,14 @@ private void setAllowFontScaling(boolean allowFontScaling) {
411418
mAllowFontScaling = allowFontScaling;
412419
setFontSize(mFontSizeInput);
413420
setLineHeight(mLineHeightInput);
414-
setLetterSpacing(mLetterSpacingInput);
421+
}
422+
}
423+
424+
private void setMaxFontSizeMultiplier(float maxFontSizeMultiplier) {
425+
if (maxFontSizeMultiplier != mMaxFontSizeMultiplier) {
426+
mMaxFontSizeMultiplier = maxFontSizeMultiplier;
427+
setFontSize(mFontSizeInput);
428+
setLineHeight(mLineHeightInput);
415429
}
416430
}
417431

@@ -420,7 +434,7 @@ private void setFontSize(float fontSize) {
420434
if (fontSize != ReactConstants.UNSET) {
421435
fontSize =
422436
mAllowFontScaling
423-
? (float) Math.ceil(PixelUtil.toPixelFromSP(fontSize))
437+
? (float) Math.ceil(PixelUtil.toPixelFromSP(fontSize, mMaxFontSizeMultiplier))
424438
: (float) Math.ceil(PixelUtil.toPixelFromDIP(fontSize));
425439
}
426440
mFontSize = (int) fontSize;

packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ void TextAttributes::apply(TextAttributes textAttributes) {
4646
allowFontScaling = textAttributes.allowFontScaling.has_value()
4747
? textAttributes.allowFontScaling
4848
: allowFontScaling;
49+
maxFontSizeMultiplier = !std::isnan(textAttributes.maxFontSizeMultiplier)
50+
? textAttributes.maxFontSizeMultiplier
51+
: maxFontSizeMultiplier;
4952
dynamicTypeRamp = textAttributes.dynamicTypeRamp.has_value()
5053
? textAttributes.dynamicTypeRamp
5154
: dynamicTypeRamp;
@@ -168,6 +171,7 @@ bool TextAttributes::operator==(const TextAttributes& rhs) const {
168171
rhs.accessibilityRole,
169172
rhs.role,
170173
rhs.textTransform) &&
174+
floatEquality(maxFontSizeMultiplier, rhs.maxFontSizeMultiplier) &&
171175
floatEquality(opacity, rhs.opacity) &&
172176
floatEquality(fontSize, rhs.fontSize) &&
173177
floatEquality(fontSizeMultiplier, rhs.fontSizeMultiplier) &&
@@ -211,6 +215,8 @@ SharedDebugStringConvertibleList TextAttributes::getDebugProps() const {
211215
debugStringConvertibleItem("fontStyle", fontStyle),
212216
debugStringConvertibleItem("fontVariant", fontVariant),
213217
debugStringConvertibleItem("allowFontScaling", allowFontScaling),
218+
debugStringConvertibleItem(
219+
"maxFontSizeMultiplier", maxFontSizeMultiplier),
214220
debugStringConvertibleItem("dynamicTypeRamp", dynamicTypeRamp),
215221
debugStringConvertibleItem("letterSpacing", letterSpacing),
216222

packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class TextAttributes : public DebugStringConvertible {
5151
std::optional<FontStyle> fontStyle{};
5252
std::optional<FontVariant> fontVariant{};
5353
std::optional<bool> allowFontScaling{};
54+
Float maxFontSizeMultiplier{std::numeric_limits<Float>::quiet_NaN()};
5455
std::optional<DynamicTypeRamp> dynamicTypeRamp{};
5556
Float letterSpacing{std::numeric_limits<Float>::quiet_NaN()};
5657
std::optional<TextTransform> textTransform{};
@@ -117,6 +118,7 @@ struct hash<facebook::react::TextAttributes> {
117118
textAttributes.opacity,
118119
textAttributes.fontFamily,
119120
textAttributes.fontSize,
121+
textAttributes.maxFontSizeMultiplier,
120122
textAttributes.fontSizeMultiplier,
121123
textAttributes.fontWeight,
122124
textAttributes.fontStyle,

packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,7 @@ constexpr static MapBuffer::Key TA_KEY_LINE_BREAK_STRATEGY = 25;
910910
constexpr static MapBuffer::Key TA_KEY_ROLE = 26;
911911
constexpr static MapBuffer::Key TA_KEY_TEXT_TRANSFORM = 27;
912912
constexpr static MapBuffer::Key TA_KEY_ALIGNMENT_VERTICAL = 28;
913+
constexpr static MapBuffer::Key TA_KEY_MAX_FONT_SIZE_MULTIPLIER = 29;
913914

914915
// constants for ParagraphAttributes serialization
915916
constexpr static MapBuffer::Key PA_KEY_MAX_NUMBER_OF_LINES = 0;
@@ -1004,6 +1005,10 @@ inline MapBuffer toMapBuffer(const TextAttributes& textAttributes) {
10041005
builder.putBool(
10051006
TA_KEY_ALLOW_FONT_SCALING, *textAttributes.allowFontScaling);
10061007
}
1008+
if (!std::isnan(textAttributes.maxFontSizeMultiplier)) {
1009+
builder.putDouble(
1010+
TA_KEY_MAX_FONT_SIZE_MULTIPLIER, textAttributes.maxFontSizeMultiplier);
1011+
}
10071012
if (!std::isnan(textAttributes.letterSpacing)) {
10081013
builder.putDouble(TA_KEY_LETTER_SPACING, textAttributes.letterSpacing);
10091014
}

packages/react-native/ReactCommon/react/renderer/components/text/BaseTextProps.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ static TextAttributes convertRawProp(
7373
"allowFontScaling",
7474
sourceTextAttributes.allowFontScaling,
7575
defaultTextAttributes.allowFontScaling);
76+
textAttributes.maxFontSizeMultiplier = convertRawProp(
77+
context,
78+
rawProps,
79+
"maxFontSizeMultiplier",
80+
sourceTextAttributes.maxFontSizeMultiplier,
81+
defaultTextAttributes.maxFontSizeMultiplier);
7682
textAttributes.dynamicTypeRamp = convertRawProp(
7783
context,
7884
rawProps,
@@ -266,6 +272,12 @@ void BaseTextProps::setProp(
266272
defaults, value, textAttributes, fontVariant, "fontVariant");
267273
REBUILD_FIELD_SWITCH_CASE(
268274
defaults, value, textAttributes, allowFontScaling, "allowFontScaling");
275+
REBUILD_FIELD_SWITCH_CASE(
276+
defaults,
277+
value,
278+
textAttributes,
279+
maxFontSizeMultiplier,
280+
"maxFontSizeMultiplier");
269281
REBUILD_FIELD_SWITCH_CASE(
270282
defaults, value, textAttributes, letterSpacing, "letterSpacing");
271283
REBUILD_FIELD_SWITCH_CASE(

packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,17 +135,19 @@ inline static CGFloat RCTBaseSizeForDynamicTypeRamp(const DynamicTypeRamp &dynam
135135
inline static CGFloat RCTEffectiveFontSizeMultiplierFromTextAttributes(const TextAttributes &textAttributes)
136136
{
137137
if (textAttributes.allowFontScaling.value_or(true)) {
138+
CGFloat fontSizeMultiplier = !isnan(textAttributes.fontSizeMultiplier) ? textAttributes.fontSizeMultiplier : 1.0;
138139
if (textAttributes.dynamicTypeRamp.has_value()) {
139140
DynamicTypeRamp dynamicTypeRamp = textAttributes.dynamicTypeRamp.value();
140141
UIFontMetrics *fontMetrics =
141142
[UIFontMetrics metricsForTextStyle:RCTUIFontTextStyleForDynamicTypeRamp(dynamicTypeRamp)];
142143
// Using a specific font size reduces rounding errors from -scaledValueForValue:
143144
CGFloat requestedSize =
144145
isnan(textAttributes.fontSize) ? RCTBaseSizeForDynamicTypeRamp(dynamicTypeRamp) : textAttributes.fontSize;
145-
return [fontMetrics scaledValueForValue:requestedSize] / requestedSize;
146-
} else {
147-
return textAttributes.fontSizeMultiplier;
146+
fontSizeMultiplier = [fontMetrics scaledValueForValue:requestedSize] / requestedSize;
148147
}
148+
CGFloat maxFontSizeMultiplier =
149+
!isnan(textAttributes.maxFontSizeMultiplier) ? textAttributes.maxFontSizeMultiplier : 0.0;
150+
return maxFontSizeMultiplier >= 1.0 ? fminf(maxFontSizeMultiplier, fontSizeMultiplier) : fontSizeMultiplier;
149151
} else {
150152
return 1.0;
151153
}

packages/rn-tester/js/examples/Text/TextExample.android.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,37 @@ function AllowFontScalingExample(props: {}): React.Node {
443443
);
444444
}
445445

446+
function MaxFontSizeMultiplierExample(props: {}): React.Node {
447+
return (
448+
<View testID={'max-font-size-multiplier'}>
449+
<Text>
450+
When allowFontScaling is enabled, you can use the maxFontSizeMultiplier
451+
prop to set an upper limit on how much the font size will be scaled.
452+
</Text>
453+
<Text
454+
allowFontScaling={true}
455+
maxFontSizeMultiplier={1}
456+
style={{marginTop: 10}}>
457+
This text will not scale up (max 1x)
458+
</Text>
459+
<Text allowFontScaling={true} maxFontSizeMultiplier={1.5}>
460+
This text will scale up (max 1.5x)
461+
</Text>
462+
<Text allowFontScaling={true} maxFontSizeMultiplier={1}>
463+
<Text>Inherit max (max 1x)</Text>
464+
</Text>
465+
<Text allowFontScaling={true} maxFontSizeMultiplier={1}>
466+
<Text maxFontSizeMultiplier={1.5}>
467+
Override inherited max (max 1.5x)
468+
</Text>
469+
</Text>
470+
<Text allowFontScaling={true} maxFontSizeMultiplier={1}>
471+
<Text maxFontSizeMultiplier={0}>Ignore inherited max (no max)</Text>
472+
</Text>
473+
</View>
474+
);
475+
}
476+
446477
function NumberOfLinesExample(props: {}): React.Node {
447478
return (
448479
<>
@@ -1370,6 +1401,13 @@ const examples = [
13701401
return <AllowFontScalingExample />;
13711402
},
13721403
},
1404+
{
1405+
title: 'maxFontSizeMultiplier attribute',
1406+
name: 'maxFontSizeMultiplier',
1407+
render(): React.Node {
1408+
return <MaxFontSizeMultiplierExample />;
1409+
},
1410+
},
13731411
{
13741412
title: 'selectable attribute',
13751413
name: 'selectable',

packages/rn-tester/js/examples/Text/TextExample.ios.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,6 +1127,62 @@ const examples = [
11271127
);
11281128
},
11291129
},
1130+
{
1131+
title: 'maxFontSizeMultiplier attribute',
1132+
name: 'maxFontSizeMultiplier',
1133+
render(): React.Node {
1134+
return (
1135+
<View testID={'max-font-size-multiplier'}>
1136+
<Text>
1137+
When allowFontScaling is enabled, you can use the
1138+
maxFontSizeMultiplier prop to set an upper limit on how much the
1139+
font size will be scaled.
1140+
</Text>
1141+
<Text
1142+
allowFontScaling={true}
1143+
maxFontSizeMultiplier={1}
1144+
style={{marginTop: 10}}>
1145+
This text will not scale up (max 1x)
1146+
</Text>
1147+
<Text allowFontScaling={true} maxFontSizeMultiplier={1.5}>
1148+
This text will scale up (max 1.5x)
1149+
</Text>
1150+
<Text allowFontScaling={true} maxFontSizeMultiplier={1}>
1151+
<Text>Inherit max (max 1x)</Text>
1152+
</Text>
1153+
<Text allowFontScaling={true} maxFontSizeMultiplier={1}>
1154+
<Text maxFontSizeMultiplier={1.5}>
1155+
Override inherited max (max 1.5x)
1156+
</Text>
1157+
</Text>
1158+
<Text allowFontScaling={true} maxFontSizeMultiplier={1}>
1159+
<Text maxFontSizeMultiplier={0}>Ignore inherited max (no max)</Text>
1160+
</Text>
1161+
<Text
1162+
allowFontScaling={true}
1163+
style={{fontSize: 22}}
1164+
dynamicTypeRamp="title2">
1165+
This text will scale with 'title2' dynamic type ramp (no max)
1166+
</Text>
1167+
<Text
1168+
allowFontScaling={true}
1169+
style={{fontSize: 22}}
1170+
dynamicTypeRamp="title2"
1171+
maxFontSizeMultiplier={1.2}>
1172+
This text will scale with 'title2' dynamic type ramp (max 1.2x)
1173+
</Text>
1174+
<Text
1175+
allowFontScaling={true}
1176+
style={{fontSize: 22}}
1177+
dynamicTypeRamp="title2"
1178+
maxFontSizeMultiplier={1}>
1179+
This text uses 'title2' dynamic type ramp but will not scale up (max
1180+
1x)
1181+
</Text>
1182+
</View>
1183+
);
1184+
},
1185+
},
11301186
{
11311187
title: 'Inline views',
11321188
render: (): React.Node => <TextInlineView.Basic />,
@@ -1392,17 +1448,41 @@ const examples = [
13921448
<Text style={{fontSize: 20}} dynamicTypeRamp="title3">
13931449
Title 3
13941450
</Text>
1451+
<Text style={{fontSize: 17}} dynamicTypeRamp="headline">
1452+
Headline
1453+
</Text>
13951454
<Text style={{fontSize: 17}} dynamicTypeRamp="body">
13961455
Body
13971456
</Text>
1457+
<Text style={{fontSize: 16}} dynamicTypeRamp="callout">
1458+
Callout
1459+
</Text>
1460+
<Text style={{fontSize: 15}} dynamicTypeRamp="subheadline">
1461+
Subheadline
1462+
</Text>
1463+
<Text style={{fontSize: 13}} dynamicTypeRamp="footnote">
1464+
Footnote
1465+
</Text>
1466+
<Text style={{fontSize: 12}} dynamicTypeRamp="caption1">
1467+
Caption
1468+
</Text>
1469+
<Text style={{fontSize: 11}} dynamicTypeRamp="caption2">
1470+
Caption 2
1471+
</Text>
13981472
</View>
13991473
<View style={boxStyle}>
14001474
<Text style={boldStyle}>Without `dynamicTypeRamp`:</Text>
14011475
<Text style={{fontSize: 34}}>Large Title</Text>
14021476
<Text style={{fontSize: 28}}>Title</Text>
14031477
<Text style={{fontSize: 22}}>Title 2</Text>
14041478
<Text style={{fontSize: 20}}>Title 3</Text>
1479+
<Text style={{fontSize: 17}}>Headline</Text>
14051480
<Text style={{fontSize: 17}}>Body</Text>
1481+
<Text style={{fontSize: 16}}>Callout</Text>
1482+
<Text style={{fontSize: 15}}>Subheadline</Text>
1483+
<Text style={{fontSize: 13}}>Footnote</Text>
1484+
<Text style={{fontSize: 12}}>Caption</Text>
1485+
<Text style={{fontSize: 11}}>Caption 2</Text>
14061486
</View>
14071487
</View>
14081488
);

0 commit comments

Comments
 (0)