From b726c7c417e6850c708fd0a7945470883878cc12 Mon Sep 17 00:00:00 2001 From: Rickard Zrinski Date: Tue, 12 Nov 2024 20:38:22 +0100 Subject: [PATCH 1/4] Fix support for maxFontSizeMultiplier prop in new architecture (#47499) --- .../react/views/text/TextAttributeProps.java | 17 +++++- .../attributedstring/TextAttributes.cpp | 5 ++ .../attributedstring/TextAttributes.h | 2 + .../renderer/attributedstring/conversions.h | 4 ++ .../components/text/BaseTextProps.cpp | 8 +++ .../RCTAttributedTextUtils.mm | 5 +- .../js/examples/Text/TextExample.android.js | 38 +++++++++++++ .../js/examples/Text/TextExample.ios.js | 35 ++++++++++++ .../TextInput/TextInputExample.android.js | 51 ++++++++++++++++++ .../TextInput/TextInputExample.ios.js | 54 +++++++++++++++++++ 10 files changed, 216 insertions(+), 3 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java index 999e4279a7ac7c..f0f2f6512feb24 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java @@ -61,6 +61,7 @@ public class TextAttributeProps { public static final short TA_KEY_LINE_BREAK_STRATEGY = 25; public static final short TA_KEY_ROLE = 26; public static final short TA_KEY_TEXT_TRANSFORM = 27; + public static final short TA_KEY_MAX_FONT_SIZE_MULTIPLIER = 29; public static final int UNSET = -1; @@ -81,6 +82,7 @@ public class TextAttributeProps { protected float mLineHeight = Float.NaN; protected boolean mIsColorSet = false; protected boolean mAllowFontScaling = true; + protected float mMaxFontSizeMultiplier = Float.NaN; protected int mColor; protected boolean mIsBackgroundColorSet = false; protected int mBackgroundColor; @@ -227,6 +229,9 @@ public static TextAttributeProps fromMapBuffer(MapBuffer props) { case TA_KEY_TEXT_TRANSFORM: result.setTextTransform(entry.getStringValue()); break; + case TA_KEY_MAX_FONT_SIZE_MULTIPLIER: + result.setMaxFontSizeMultiplier((float) entry.getDoubleValue()); + break; } } @@ -243,6 +248,7 @@ public static TextAttributeProps fromReadableMap(ReactStylesDiffMap props) { result.setLineHeight(getFloatProp(props, ViewProps.LINE_HEIGHT, ReactConstants.UNSET)); result.setLetterSpacing(getFloatProp(props, ViewProps.LETTER_SPACING, Float.NaN)); result.setAllowFontScaling(getBooleanProp(props, ViewProps.ALLOW_FONT_SCALING, true)); + result.setMaxFontSizeMultiplier(getFloatProp(props, ViewProps.MAX_FONT_SIZE_MULTIPLIER, Float.NaN)); result.setFontSize(getFloatProp(props, ViewProps.FONT_SIZE, ReactConstants.UNSET)); result.setColor(props.hasKey(ViewProps.COLOR) ? props.getInt(ViewProps.COLOR, 0) : null); result.setColor( @@ -411,7 +417,14 @@ private void setAllowFontScaling(boolean allowFontScaling) { mAllowFontScaling = allowFontScaling; setFontSize(mFontSizeInput); setLineHeight(mLineHeightInput); - setLetterSpacing(mLetterSpacingInput); + } + } + + private void setMaxFontSizeMultiplier(float maxFontSizeMultiplier) { + if (maxFontSizeMultiplier != mMaxFontSizeMultiplier) { + mMaxFontSizeMultiplier = maxFontSizeMultiplier; + setFontSize(mFontSizeInput); + setLineHeight(mLineHeightInput); } } @@ -420,7 +433,7 @@ private void setFontSize(float fontSize) { if (fontSize != ReactConstants.UNSET) { fontSize = mAllowFontScaling - ? (float) Math.ceil(PixelUtil.toPixelFromSP(fontSize)) + ? (float) Math.ceil(PixelUtil.toPixelFromSP(fontSize, mMaxFontSizeMultiplier)) : (float) Math.ceil(PixelUtil.toPixelFromDIP(fontSize)); } mFontSize = (int) fontSize; diff --git a/packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.cpp b/packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.cpp index 9b06f12948a654..5ec845ec59dae6 100644 --- a/packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.cpp +++ b/packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.cpp @@ -46,6 +46,9 @@ void TextAttributes::apply(TextAttributes textAttributes) { allowFontScaling = textAttributes.allowFontScaling.has_value() ? textAttributes.allowFontScaling : allowFontScaling; + maxFontSizeMultiplier = !std::isnan(textAttributes.maxFontSizeMultiplier) + ? textAttributes.maxFontSizeMultiplier + : maxFontSizeMultiplier; dynamicTypeRamp = textAttributes.dynamicTypeRamp.has_value() ? textAttributes.dynamicTypeRamp : dynamicTypeRamp; @@ -168,6 +171,7 @@ bool TextAttributes::operator==(const TextAttributes& rhs) const { rhs.accessibilityRole, rhs.role, rhs.textTransform) && + floatEquality(maxFontSizeMultiplier, rhs.maxFontSizeMultiplier) && floatEquality(opacity, rhs.opacity) && floatEquality(fontSize, rhs.fontSize) && floatEquality(fontSizeMultiplier, rhs.fontSizeMultiplier) && @@ -211,6 +215,7 @@ SharedDebugStringConvertibleList TextAttributes::getDebugProps() const { debugStringConvertibleItem("fontStyle", fontStyle), debugStringConvertibleItem("fontVariant", fontVariant), debugStringConvertibleItem("allowFontScaling", allowFontScaling), + debugStringConvertibleItem("maxFontSizeMultiplier", maxFontSizeMultiplier), debugStringConvertibleItem("dynamicTypeRamp", dynamicTypeRamp), debugStringConvertibleItem("letterSpacing", letterSpacing), diff --git a/packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.h b/packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.h index 37db36656f8d67..55b4de33223fc2 100644 --- a/packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.h +++ b/packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.h @@ -51,6 +51,7 @@ class TextAttributes : public DebugStringConvertible { std::optional fontStyle{}; std::optional fontVariant{}; std::optional allowFontScaling{}; + Float maxFontSizeMultiplier{std::numeric_limits::quiet_NaN()}; std::optional dynamicTypeRamp{}; Float letterSpacing{std::numeric_limits::quiet_NaN()}; std::optional textTransform{}; @@ -117,6 +118,7 @@ struct hash { textAttributes.opacity, textAttributes.fontFamily, textAttributes.fontSize, + textAttributes.maxFontSizeMultiplier, textAttributes.fontSizeMultiplier, textAttributes.fontWeight, textAttributes.fontStyle, diff --git a/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h b/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h index de8676939e4630..4bb23f08d2609f 100644 --- a/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h +++ b/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h @@ -910,6 +910,7 @@ constexpr static MapBuffer::Key TA_KEY_LINE_BREAK_STRATEGY = 25; constexpr static MapBuffer::Key TA_KEY_ROLE = 26; constexpr static MapBuffer::Key TA_KEY_TEXT_TRANSFORM = 27; constexpr static MapBuffer::Key TA_KEY_ALIGNMENT_VERTICAL = 28; +constexpr static MapBuffer::Key TA_KEY_MAX_FONT_SIZE_MULTIPLIER = 29; // constants for ParagraphAttributes serialization constexpr static MapBuffer::Key PA_KEY_MAX_NUMBER_OF_LINES = 0; @@ -1004,6 +1005,9 @@ inline MapBuffer toMapBuffer(const TextAttributes& textAttributes) { builder.putBool( TA_KEY_ALLOW_FONT_SCALING, *textAttributes.allowFontScaling); } + if (!std::isnan(textAttributes.maxFontSizeMultiplier)) { + builder.putDouble(TA_KEY_MAX_FONT_SIZE_MULTIPLIER, textAttributes.maxFontSizeMultiplier); + } if (!std::isnan(textAttributes.letterSpacing)) { builder.putDouble(TA_KEY_LETTER_SPACING, textAttributes.letterSpacing); } diff --git a/packages/react-native/ReactCommon/react/renderer/components/text/BaseTextProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/text/BaseTextProps.cpp index 132281e8ab8333..64ebaba00617ea 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/text/BaseTextProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/text/BaseTextProps.cpp @@ -73,6 +73,12 @@ static TextAttributes convertRawProp( "allowFontScaling", sourceTextAttributes.allowFontScaling, defaultTextAttributes.allowFontScaling); + textAttributes.maxFontSizeMultiplier = convertRawProp( + context, + rawProps, + "maxFontSizeMultiplier", + sourceTextAttributes.maxFontSizeMultiplier, + defaultTextAttributes.maxFontSizeMultiplier); textAttributes.dynamicTypeRamp = convertRawProp( context, rawProps, @@ -266,6 +272,8 @@ void BaseTextProps::setProp( defaults, value, textAttributes, fontVariant, "fontVariant"); REBUILD_FIELD_SWITCH_CASE( defaults, value, textAttributes, allowFontScaling, "allowFontScaling"); + REBUILD_FIELD_SWITCH_CASE( + defaults, value, textAttributes, maxFontSizeMultiplier, "maxFontSizeMultiplier"); REBUILD_FIELD_SWITCH_CASE( defaults, value, textAttributes, letterSpacing, "letterSpacing"); REBUILD_FIELD_SWITCH_CASE( diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm index 17eec1f275bd6a..ba7ec85030dff4 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm @@ -144,7 +144,10 @@ inline static CGFloat RCTEffectiveFontSizeMultiplierFromTextAttributes(const Tex isnan(textAttributes.fontSize) ? RCTBaseSizeForDynamicTypeRamp(dynamicTypeRamp) : textAttributes.fontSize; return [fontMetrics scaledValueForValue:requestedSize] / requestedSize; } else { - return textAttributes.fontSizeMultiplier; + Float fontSizeMultiplier = textAttributes.fontSizeMultiplier; + Float maxFontSizeMultiplier = + isnan(textAttributes.maxFontSizeMultiplier) ? 0.0 : textAttributes.maxFontSizeMultiplier; + return maxFontSizeMultiplier >= 1.0 ? fminf(maxFontSizeMultiplier, fontSizeMultiplier) : fontSizeMultiplier; } } else { return 1.0; diff --git a/packages/rn-tester/js/examples/Text/TextExample.android.js b/packages/rn-tester/js/examples/Text/TextExample.android.js index 453b9c2cfe30b9..7d4bf658083b5b 100644 --- a/packages/rn-tester/js/examples/Text/TextExample.android.js +++ b/packages/rn-tester/js/examples/Text/TextExample.android.js @@ -443,6 +443,37 @@ function AllowFontScalingExample(props: {}): React.Node { ); } +function MaxFontSizeMultiplierExample(props: {}): React.Node { + return ( + <> + + When allowFontScaling is enabled, you can use the maxFontSizeMultiplier + prop to set an upper limit on how much the font size will be scaled. + + + This text will not scale up (max 1x) + + + This text will scale up (max 1.5x) + + + Inherit max (max 1x) + + + + Override inherited max (max 1.5x) + + + + Ignore inherited max (no max) + + + ); +} + function NumberOfLinesExample(props: {}): React.Node { return ( <> @@ -1370,6 +1401,13 @@ const examples = [ return ; }, }, + { + title: 'maxFontSizeMultiplier attribute', + name: 'maxFontSizeMultiplier', + render(): React.Node { + return ; + }, + }, { title: 'selectable attribute', name: 'selectable', diff --git a/packages/rn-tester/js/examples/Text/TextExample.ios.js b/packages/rn-tester/js/examples/Text/TextExample.ios.js index 6b65d167894f26..95ed22c7eff8e7 100644 --- a/packages/rn-tester/js/examples/Text/TextExample.ios.js +++ b/packages/rn-tester/js/examples/Text/TextExample.ios.js @@ -1127,6 +1127,41 @@ const examples = [ ); }, }, + { + title: 'maxFontSizeMultiplier attribute', + name: 'maxFontSizeMultiplier', + render(): React.Node { + return ( + <> + + When allowFontScaling is enabled, you can use the + maxFontSizeMultiplier prop to set an upper limit on how much the + font size will be scaled. + + + This text will not scale up (max 1x) + + + This text will scale up (max 1.5x) + + + Inherit max (max 1x) + + + + Override inherited max (max 1.5x) + + + + Ignore inherited max (no max) + + + ); + }, + }, { title: 'Inline views', render: (): React.Node => , diff --git a/packages/rn-tester/js/examples/TextInput/TextInputExample.android.js b/packages/rn-tester/js/examples/TextInput/TextInputExample.android.js index c2c8eb7084a490..6b69832453ee6e 100644 --- a/packages/rn-tester/js/examples/TextInput/TextInputExample.android.js +++ b/packages/rn-tester/js/examples/TextInput/TextInputExample.android.js @@ -408,6 +408,57 @@ const examples: Array = [ ); }, }, + { + title: 'allowFontScaling attribute', + render: function (): React.Node { + return ( + + + By default, text will respect Text Size accessibility setting on + Android. It means that all font sizes will be increased or decreased + depending on the value of the Text Size setting in the OS's Settings + app. + + + + + ); + }, + }, + { + title: 'maxFontSizeMultiplier attribute', + name: 'maxFontSizeMultiplier', + render(): React.Node { + return ( + + + When allowFontScaling is enabled, you can use the + maxFontSizeMultiplier prop to set an upper limit on how much the + font size will be scaled. + + + + + ); + }, + }, { title: 'Auto-expanding', render: function (): React.Node { diff --git a/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js b/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js index 30b8a5dc815b3c..b6b8d5e534086b 100644 --- a/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js +++ b/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js @@ -17,6 +17,7 @@ import type { import type {KeyboardType} from 'react-native/Libraries/Components/TextInput/TextInput'; import ExampleTextInput from './ExampleTextInput'; +import {TextInput} from 'react-native'; const TextInputSharedExamples = require('./TextInputSharedExamples.js'); const React = require('react'); @@ -670,6 +671,59 @@ const textInputExamples: Array = [ ); }, }, + { + title: 'allowFontScaling attribute', + render: function (): React.Node { + return ( + + + By default, text will respect Text Size accessibility setting on + iOS. It means that all font sizes will be increased or decreased + depending on the value of Text Size setting in{' '} + + Settings.app - Display & Brightness - Text Size + + + + + + ); + }, + }, + { + title: 'maxFontSizeMultiplier attribute', + name: 'maxFontSizeMultiplier', + render(): React.Node { + return ( + + + When allowFontScaling is enabled, you can use the + maxFontSizeMultiplier prop to set an upper limit on how much the + font size will be scaled. + + + + + ); + }, + }, { title: 'Auto-expanding', render: function (): React.Node { From b7b5b623f42a0fa383e70563abc1a33e8742ff47 Mon Sep 17 00:00:00 2001 From: Rickard Zrinski Date: Thu, 14 Nov 2024 19:35:12 +0100 Subject: [PATCH 2/4] remove unused TextInput import --- packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js b/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js index d995be3adfaeec..445e796a7089b2 100644 --- a/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js +++ b/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js @@ -17,7 +17,6 @@ import type { import type {KeyboardType} from 'react-native/Libraries/Components/TextInput/TextInput'; import ExampleTextInput from './ExampleTextInput'; -import {TextInput} from 'react-native'; const TextInputSharedExamples = require('./TextInputSharedExamples.js'); const React = require('react'); From b912ffcd6858f4f1f39c38839f92e0a163a727c8 Mon Sep 17 00:00:00 2001 From: Rickard Zrinski Date: Sat, 7 Dec 2024 10:55:48 +0100 Subject: [PATCH 3/4] fix: maxFontSizeMultiplier not being taken into account when using dynamicTypeRamp --- .../RCTAttributedTextUtils.mm | 11 +++-- .../js/examples/Text/TextExample.ios.js | 45 +++++++++++++++++++ 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm index ba7ec85030dff4..ec9babe7f5f067 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm @@ -135,6 +135,7 @@ inline static CGFloat RCTBaseSizeForDynamicTypeRamp(const DynamicTypeRamp &dynam inline static CGFloat RCTEffectiveFontSizeMultiplierFromTextAttributes(const TextAttributes &textAttributes) { if (textAttributes.allowFontScaling.value_or(true)) { + CGFloat fontSizeMultiplier = !isnan(textAttributes.fontSizeMultiplier) ? textAttributes.fontSizeMultiplier : 1.0; if (textAttributes.dynamicTypeRamp.has_value()) { DynamicTypeRamp dynamicTypeRamp = textAttributes.dynamicTypeRamp.value(); UIFontMetrics *fontMetrics = @@ -142,13 +143,11 @@ inline static CGFloat RCTEffectiveFontSizeMultiplierFromTextAttributes(const Tex // Using a specific font size reduces rounding errors from -scaledValueForValue: CGFloat requestedSize = isnan(textAttributes.fontSize) ? RCTBaseSizeForDynamicTypeRamp(dynamicTypeRamp) : textAttributes.fontSize; - return [fontMetrics scaledValueForValue:requestedSize] / requestedSize; - } else { - Float fontSizeMultiplier = textAttributes.fontSizeMultiplier; - Float maxFontSizeMultiplier = - isnan(textAttributes.maxFontSizeMultiplier) ? 0.0 : textAttributes.maxFontSizeMultiplier; - return maxFontSizeMultiplier >= 1.0 ? fminf(maxFontSizeMultiplier, fontSizeMultiplier) : fontSizeMultiplier; + fontSizeMultiplier = [fontMetrics scaledValueForValue:requestedSize] / requestedSize; } + CGFloat maxFontSizeMultiplier = + isnan(textAttributes.maxFontSizeMultiplier) ? 0.0 : textAttributes.maxFontSizeMultiplier; + return maxFontSizeMultiplier >= 1.0 ? fminf(maxFontSizeMultiplier, fontSizeMultiplier) : fontSizeMultiplier; } else { return 1.0; } diff --git a/packages/rn-tester/js/examples/Text/TextExample.ios.js b/packages/rn-tester/js/examples/Text/TextExample.ios.js index 95ed22c7eff8e7..effe1ce139af34 100644 --- a/packages/rn-tester/js/examples/Text/TextExample.ios.js +++ b/packages/rn-tester/js/examples/Text/TextExample.ios.js @@ -1158,6 +1158,27 @@ const examples = [ Ignore inherited max (no max) + + This text will scale with 'title2' dynamic type ramp (no max) + + + This text will scale with 'title2' dynamic type ramp (max 1.2x) + + + This text uses 'title2' dynamic type ramp but will not scale up (max + 1x) + ); }, @@ -1427,9 +1448,27 @@ const examples = [ Title 3 + + Headline + Body + + Callout + + + Subheadline + + + Footnote + + + Caption + + + Caption 2 + Without `dynamicTypeRamp`: @@ -1437,7 +1476,13 @@ const examples = [ Title Title 2 Title 3 + Headline Body + Callout + Subheadline + Footnote + Caption + Caption 2 ); From 6c974192257a2c02fb99753a11141ef3bd1c86c6 Mon Sep 17 00:00:00 2001 From: Rickard Zrinski Date: Sat, 7 Dec 2024 11:44:24 +0100 Subject: [PATCH 4/4] be consistent with conditionals --- .../react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm index ec9babe7f5f067..2fb0f46e589189 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm @@ -146,7 +146,7 @@ inline static CGFloat RCTEffectiveFontSizeMultiplierFromTextAttributes(const Tex fontSizeMultiplier = [fontMetrics scaledValueForValue:requestedSize] / requestedSize; } CGFloat maxFontSizeMultiplier = - isnan(textAttributes.maxFontSizeMultiplier) ? 0.0 : textAttributes.maxFontSizeMultiplier; + !isnan(textAttributes.maxFontSizeMultiplier) ? textAttributes.maxFontSizeMultiplier : 0.0; return maxFontSizeMultiplier >= 1.0 ? fminf(maxFontSizeMultiplier, fontSizeMultiplier) : fontSizeMultiplier; } else { return 1.0;