Skip to content

Commit 4936d28

Browse files
Adam Comellafacebook-github-bot
Adam Comella
authored andcommittedJan 23, 2019
Android: Add a maxFontSizeMultiplier prop to <Text> and <TextInput> (#23069)
Summary: Equivalent of this iOS PR: #20915 Motivation: ---------- Whenever a user changes the system font size to its maximum allowable setting, React Native apps that allow font scaling can become unusable because the text gets too big. Experimenting with a native app like iMessage on iOS, the font size used for non-body text (e.g. header, navigational elements) is capped while the body text (e.g. text in the message bubbles) is allowed to grow. This PR introduces a new prop on `<Text>` and `<TextInput>` called `maxFontSizeMultiplier`. This enables devs to set the maximum allowed text scale factor on a Text/TextInput. The default is 0 which means no limit. Pull Request resolved: #23069 Differential Revision: D13748513 Pulled By: mdvacca fbshipit-source-id: 8dd5d6d97bf79387d9a2236fa2e586ccb01afde9
1 parent 5bc709d commit 4936d28

File tree

6 files changed

+67
-8
lines changed

6 files changed

+67
-8
lines changed
 

‎ReactAndroid/src/main/java/com/facebook/react/uimanager/PixelUtil.java

+16-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
package com.facebook.react.uimanager;
99

10+
import android.util.DisplayMetrics;
1011
import android.util.TypedValue;
1112

1213
/**
@@ -42,10 +43,21 @@ public static float toSPFromPixel(float value) {
4243
* Convert from SP to PX
4344
*/
4445
public static float toPixelFromSP(float value) {
45-
return TypedValue.applyDimension(
46-
TypedValue.COMPLEX_UNIT_SP,
47-
value,
48-
DisplayMetricsHolder.getWindowDisplayMetrics());
46+
return toPixelFromSP(value, Float.NaN);
47+
}
48+
49+
/**
50+
* Convert from SP to PX
51+
*/
52+
public static float toPixelFromSP(float value, float maxFontScale) {
53+
DisplayMetrics displayMetrics = DisplayMetricsHolder.getWindowDisplayMetrics();
54+
float scaledDensity = displayMetrics.scaledDensity;
55+
float currentFontScale = scaledDensity / displayMetrics.density;
56+
if (maxFontScale >= 1 && maxFontScale < currentFontScale) {
57+
scaledDensity = displayMetrics.density * maxFontScale;
58+
}
59+
60+
return value * scaledDensity;
4961
}
5062

5163
/**

‎ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java

+1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ public class ViewProps {
106106
public static final String VISIBLE = "visible";
107107

108108
public static final String ALLOW_FONT_SCALING = "allowFontScaling";
109+
public static final String MAX_FONT_SIZE_MULTIPLIER = "maxFontSizeMultiplier";
109110
public static final String INCLUDE_FONT_PADDING = "includeFontPadding";
110111

111112
public static final String BORDER_WIDTH = "borderWidth";

‎ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java

+8
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,14 @@ public void setAllowFontScaling(boolean allowFontScaling) {
349349
}
350350
}
351351

352+
@ReactProp(name = ViewProps.MAX_FONT_SIZE_MULTIPLIER, defaultFloat = Float.NaN)
353+
public void setMaxFontSizeMultiplier(float maxFontSizeMultiplier) {
354+
if (maxFontSizeMultiplier != mTextAttributes.getMaxFontSizeMultiplier()) {
355+
mTextAttributes.setMaxFontSizeMultiplier(maxFontSizeMultiplier);
356+
markUpdated();
357+
}
358+
}
359+
352360
@ReactProp(name = ViewProps.TEXT_ALIGN)
353361
public void setTextAlign(@Nullable String textAlign) {
354362
if (textAlign == null || "auto".equals(textAlign)) {

‎ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributes.java

+30-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

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

10+
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
1011
import com.facebook.react.uimanager.PixelUtil;
1112
import com.facebook.react.uimanager.ViewDefaults;
1213

@@ -15,13 +16,17 @@
1516
* to child so inheritance can be implemented correctly. An example complexity that causes a prop
1617
* to end up in TextAttributes is when multiple props need to be considered together to determine
1718
* the rendered aka effective value. For example, to figure out the rendered/effective font size,
18-
* you need to take into account the fontSize and allowFontScaling props.
19+
* you need to take into account the fontSize, maxFontSizeMultiplier, and allowFontScaling props.
1920
*/
2021
public class TextAttributes {
22+
// Setting the default to 0 indicates that there is no max.
23+
public static final float DEFAULT_MAX_FONT_SIZE_MULTIPLIER = 0.0f;
24+
2125
private boolean mAllowFontScaling = true;
2226
private float mFontSize = Float.NaN;
2327
private float mLineHeight = Float.NaN;
2428
private float mLetterSpacing = Float.NaN;
29+
private float mMaxFontSizeMultiplier = Float.NaN;
2530
private float mHeightOfTallestInlineImage = Float.NaN;
2631

2732
public TextAttributes() {
@@ -37,6 +42,7 @@ public TextAttributes applyChild(TextAttributes child) {
3742
result.mFontSize = !Float.isNaN(child.mFontSize) ? child.mFontSize : mFontSize;
3843
result.mLineHeight = !Float.isNaN(child.mLineHeight) ? child.mLineHeight : mLineHeight;
3944
result.mLetterSpacing = !Float.isNaN(child.mLetterSpacing) ? child.mLetterSpacing : mLetterSpacing;
45+
result.mMaxFontSizeMultiplier = !Float.isNaN(child.mMaxFontSizeMultiplier) ? child.mMaxFontSizeMultiplier : mMaxFontSizeMultiplier;
4046
result.mHeightOfTallestInlineImage = !Float.isNaN(child.mHeightOfTallestInlineImage) ? child.mHeightOfTallestInlineImage : mHeightOfTallestInlineImage;
4147

4248
return result;
@@ -77,6 +83,17 @@ public void setLetterSpacing(float value) {
7783
mLetterSpacing = value;
7884
}
7985

86+
public float getMaxFontSizeMultiplier() {
87+
return mMaxFontSizeMultiplier;
88+
}
89+
90+
public void setMaxFontSizeMultiplier(float maxFontSizeMultiplier) {
91+
if (maxFontSizeMultiplier != 0 && maxFontSizeMultiplier < 1) {
92+
throw new JSApplicationIllegalArgumentException("maxFontSizeMultiplier must be NaN, 0, or >= 1");
93+
}
94+
mMaxFontSizeMultiplier = maxFontSizeMultiplier;
95+
}
96+
8097
public float getHeightOfTallestInlineImage() {
8198
return mHeightOfTallestInlineImage;
8299
}
@@ -94,7 +111,7 @@ public void setHeightOfTallestInlineImage(float value) {
94111
public int getEffectiveFontSize() {
95112
float fontSize = !Float.isNaN(mFontSize) ? mFontSize : ViewDefaults.FONT_SIZE_SP;
96113
return mAllowFontScaling
97-
? (int) Math.ceil(PixelUtil.toPixelFromSP(fontSize))
114+
? (int) Math.ceil(PixelUtil.toPixelFromSP(fontSize, getEffectiveMaxFontSizeMultiplier()))
98115
: (int) Math.ceil(PixelUtil.toPixelFromDIP(fontSize));
99116
}
100117

@@ -104,7 +121,7 @@ public float getEffectiveLineHeight() {
104121
}
105122

106123
float lineHeight = mAllowFontScaling
107-
? PixelUtil.toPixelFromSP(mLineHeight)
124+
? PixelUtil.toPixelFromSP(mLineHeight, getEffectiveMaxFontSizeMultiplier())
108125
: PixelUtil.toPixelFromDIP(mLineHeight);
109126

110127
// Take into account the requested line height
@@ -121,14 +138,21 @@ public float getEffectiveLetterSpacing() {
121138
}
122139

123140
float letterSpacingPixels = mAllowFontScaling
124-
? PixelUtil.toPixelFromSP(mLetterSpacing)
141+
? PixelUtil.toPixelFromSP(mLetterSpacing, getEffectiveMaxFontSizeMultiplier())
125142
: PixelUtil.toPixelFromDIP(mLetterSpacing);
126143

127144
// `letterSpacingPixels` and `getEffectiveFontSize` are both in pixels,
128145
// yielding an accurate em value.
129146
return letterSpacingPixels / getEffectiveFontSize();
130147
}
131148

149+
// Never returns NaN
150+
public float getEffectiveMaxFontSizeMultiplier() {
151+
return !Float.isNaN(mMaxFontSizeMultiplier)
152+
? mMaxFontSizeMultiplier
153+
: DEFAULT_MAX_FONT_SIZE_MULTIPLIER;
154+
}
155+
132156
public String toString() {
133157
return (
134158
"TextAttributes {"
@@ -140,6 +164,8 @@ public String toString() {
140164
+ "\n getEffectiveLetterSpacing(): " + getEffectiveLetterSpacing()
141165
+ "\n getLineHeight(): " + getLineHeight()
142166
+ "\n getEffectiveLineHeight(): " + getEffectiveLineHeight()
167+
+ "\n getMaxFontSizeMultiplier(): " + getMaxFontSizeMultiplier()
168+
+ "\n getEffectiveMaxFontSizeMultiplier(): " + getEffectiveMaxFontSizeMultiplier()
143169
+ "\n}"
144170
);
145171
}

‎ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java

+7
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,13 @@ public void setFontSize(float fontSize) {
647647
applyTextAttributes();
648648
}
649649

650+
public void setMaxFontSizeMultiplier(float maxFontSizeMultiplier) {
651+
if (maxFontSizeMultiplier != mTextAttributes.getMaxFontSizeMultiplier()) {
652+
mTextAttributes.setMaxFontSizeMultiplier(maxFontSizeMultiplier);
653+
applyTextAttributes();
654+
}
655+
}
656+
650657
protected void applyTextAttributes() {
651658
// In general, the `getEffective*` functions return `Float.NaN` if the
652659
// property hasn't been set.

‎ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java

+5
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,11 @@ public void setFontFamily(ReactEditText view, String fontFamily) {
217217
view.setTypeface(newTypeface);
218218
}
219219

220+
@ReactProp(name = ViewProps.MAX_FONT_SIZE_MULTIPLIER, defaultFloat = Float.NaN)
221+
public void setMaxFontSizeMultiplier(ReactEditText view, float maxFontSizeMultiplier) {
222+
view.setMaxFontSizeMultiplier(maxFontSizeMultiplier);
223+
}
224+
220225
/**
221226
/* This code was taken from the method setFontWeight of the class ReactTextShadowNode
222227
/* TODO: Factor into a common place they can both use

0 commit comments

Comments
 (0)
Please sign in to comment.