From c7b499b537542a63a41aa0550e030da8c2efbf2f Mon Sep 17 00:00:00 2001 From: Conner Reimers Date: Mon, 6 Oct 2025 17:48:27 -0500 Subject: [PATCH 1/3] Fix adjustsFontSizeToFit on iOS Fabric --- .../attributedstring/ParagraphAttributes.cpp | 12 +++-------- .../attributedstring/ParagraphAttributes.h | 12 ++--------- .../renderer/attributedstring/conversions.h | 19 ++---------------- .../components/text/BaseParagraphProps.cpp | 12 ----------- .../text/HostPlatformParagraphProps.cpp | 12 ----------- .../textinput/BaseTextInputProps.cpp | 10 ++-------- .../AndroidTextInputProps.cpp | 12 +++-------- .../textlayoutmanager/RCTTextLayoutManager.mm | 20 +++++++++++++++++-- 8 files changed, 30 insertions(+), 79 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.cpp b/packages/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.cpp index 87029ef120cf..85a3632442ff 100644 --- a/packages/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.cpp +++ b/packages/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.cpp @@ -31,8 +31,6 @@ bool ParagraphAttributes::operator==(const ParagraphAttributes& rhs) const { rhs.includeFontPadding, rhs.android_hyphenationFrequency, rhs.textAlignVertical) && - floatEquality(minimumFontSize, rhs.minimumFontSize) && - floatEquality(maximumFontSize, rhs.maximumFontSize) && floatEquality(minimumFontScale, rhs.minimumFontScale); } @@ -61,13 +59,9 @@ SharedDebugStringConvertibleList ParagraphAttributes::getDebugProps() const { adjustsFontSizeToFit, paragraphAttributes.adjustsFontSizeToFit), debugStringConvertibleItem( - "minimumFontSize", - minimumFontSize, - paragraphAttributes.minimumFontSize), - debugStringConvertibleItem( - "maximumFontSize", - maximumFontSize, - paragraphAttributes.maximumFontSize), + "minimumFontScale", + minimumFontScale, + paragraphAttributes.minimumFontScale), debugStringConvertibleItem( "includeFontPadding", includeFontPadding, diff --git a/packages/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.h b/packages/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.h index 35ec7a712914..eedabf640e49 100644 --- a/packages/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.h +++ b/packages/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.h @@ -63,18 +63,11 @@ class ParagraphAttributes : public DebugStringConvertible { */ HyphenationFrequency android_hyphenationFrequency{}; - /* - * In case of font size adjustment enabled, defines minimum and maximum - * font sizes. - */ - Float minimumFontSize{std::numeric_limits::quiet_NaN()}; - Float maximumFontSize{std::numeric_limits::quiet_NaN()}; - /* * Specifies the smallest possible scale a font can reach when * adjustsFontSizeToFit is enabled. (values 0.01-1.0). */ - Float minimumFontScale{std::numeric_limits::quiet_NaN()}; + Float minimumFontScale{0.0}; /* * The vertical alignment of the text, causing the glyphs to be vertically @@ -105,8 +98,7 @@ struct hash { attributes.ellipsizeMode, attributes.textBreakStrategy, attributes.adjustsFontSizeToFit, - attributes.minimumFontSize, - attributes.maximumFontSize, + attributes.minimumFontScale, attributes.includeFontPadding, attributes.android_hyphenationFrequency, attributes.minimumFontScale, diff --git a/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h b/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h index 0923e85b2c32..f2018f3f6821 100644 --- a/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h +++ b/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h @@ -952,18 +952,6 @@ inline ParagraphAttributes convertRawProp( "minimumFontScale", sourceParagraphAttributes.minimumFontScale, defaultParagraphAttributes.minimumFontScale); - paragraphAttributes.minimumFontSize = convertRawProp( - context, - rawProps, - "minimumFontSize", - sourceParagraphAttributes.minimumFontSize, - defaultParagraphAttributes.minimumFontSize); - paragraphAttributes.maximumFontSize = convertRawProp( - context, - rawProps, - "maximumFontSize", - sourceParagraphAttributes.maximumFontSize, - defaultParagraphAttributes.maximumFontSize); paragraphAttributes.includeFontPadding = convertRawProp( context, rawProps, @@ -1062,8 +1050,7 @@ constexpr static MapBuffer::Key PA_KEY_TEXT_BREAK_STRATEGY = 2; constexpr static MapBuffer::Key PA_KEY_ADJUST_FONT_SIZE_TO_FIT = 3; constexpr static MapBuffer::Key PA_KEY_INCLUDE_FONT_PADDING = 4; constexpr static MapBuffer::Key PA_KEY_HYPHENATION_FREQUENCY = 5; -constexpr static MapBuffer::Key PA_KEY_MINIMUM_FONT_SIZE = 6; -constexpr static MapBuffer::Key PA_KEY_MAXIMUM_FONT_SIZE = 7; +constexpr static MapBuffer::Key PA_KEY_MINIMUM_FONT_SCALE = 6; constexpr static MapBuffer::Key PA_KEY_TEXT_ALIGN_VERTICAL = 8; inline MapBuffer toMapBuffer(const ParagraphAttributes& paragraphAttributes) { @@ -1088,9 +1075,7 @@ inline MapBuffer toMapBuffer(const ParagraphAttributes& paragraphAttributes) { toString(*paragraphAttributes.textAlignVertical)); } builder.putDouble( - PA_KEY_MINIMUM_FONT_SIZE, paragraphAttributes.minimumFontSize); - builder.putDouble( - PA_KEY_MAXIMUM_FONT_SIZE, paragraphAttributes.maximumFontSize); + PA_KEY_MINIMUM_FONT_SCALE, paragraphAttributes.minimumFontScale); return builder.build(); } diff --git a/packages/react-native/ReactCommon/react/renderer/components/text/BaseParagraphProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/text/BaseParagraphProps.cpp index ae173251dbad..e93f3b89dae4 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/text/BaseParagraphProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/text/BaseParagraphProps.cpp @@ -102,18 +102,6 @@ void BaseParagraphProps::setProp( paragraphAttributes, minimumFontScale, "minimumFontScale"); - REBUILD_FIELD_SWITCH_CASE( - paDefaults, - value, - paragraphAttributes, - minimumFontSize, - "minimumFontSize"); - REBUILD_FIELD_SWITCH_CASE( - paDefaults, - value, - paragraphAttributes, - maximumFontSize, - "maximumFontSize"); REBUILD_FIELD_SWITCH_CASE( paDefaults, value, diff --git a/packages/react-native/ReactCommon/react/renderer/components/text/platform/android/react/renderer/components/text/HostPlatformParagraphProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/text/platform/android/react/renderer/components/text/HostPlatformParagraphProps.cpp index a1019158f665..bfa8ca57ae92 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/text/platform/android/react/renderer/components/text/HostPlatformParagraphProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/text/platform/android/react/renderer/components/text/HostPlatformParagraphProps.cpp @@ -132,18 +132,6 @@ folly::dynamic HostPlatformParagraphProps::getDiffProps( result["minimumFontScale"] = paragraphAttributes.minimumFontScale; } - if (!floatEquality( - paragraphAttributes.minimumFontSize, - oldProps->paragraphAttributes.minimumFontSize)) { - result["minimumFontSize"] = paragraphAttributes.minimumFontSize; - } - - if (!floatEquality( - paragraphAttributes.maximumFontSize, - oldProps->paragraphAttributes.maximumFontSize)) { - result["maximumFontSize"] = paragraphAttributes.maximumFontSize; - } - if (paragraphAttributes.includeFontPadding != oldProps->paragraphAttributes.includeFontPadding) { result["includeFontPadding"] = paragraphAttributes.includeFontPadding; diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/BaseTextInputProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/textinput/BaseTextInputProps.cpp index ac3adc4d4ed2..3c8f35198473 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/BaseTextInputProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/BaseTextInputProps.cpp @@ -177,14 +177,8 @@ void BaseTextInputProps::setProp( paDefaults, value, paragraphAttributes, - minimumFontSize, - "minimumFontSize"); - REBUILD_FIELD_SWITCH_CASE( - paDefaults, - value, - paragraphAttributes, - maximumFontSize, - "maximumFontSize"); + minimumFontScale, + "minimumFontScale"); REBUILD_FIELD_SWITCH_CASE( paDefaults, value, diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp index 5b88b938f899..c288b4af6ece 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp @@ -394,15 +394,9 @@ folly::dynamic AndroidTextInputProps::getDiffProps( } if (!floatEquality( - paragraphAttributes.minimumFontSize, - oldProps->paragraphAttributes.minimumFontSize)) { - result["minimumFontSize"] = paragraphAttributes.minimumFontSize; - } - - if (!floatEquality( - paragraphAttributes.maximumFontSize, - oldProps->paragraphAttributes.maximumFontSize)) { - result["maximumFontSize"] = paragraphAttributes.maximumFontSize; + paragraphAttributes.minimumFontScale, + oldProps->paragraphAttributes.minimumFontScale)) { + result["minimumFontScale"] = paragraphAttributes.minimumFontScale; } if (paragraphAttributes.includeFontPadding != diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm index 3b4109c44aec..0b5f703e8350 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm @@ -220,6 +220,22 @@ - (LinesMeasurements)getLinesForAttributedString:(facebook::react::AttributedStr return paragraphLines; } +- (CGFloat)_maximumFontSizeInAttributedString:(NSAttributedString *)attributedString +{ + __block CGFloat maximumFontSize = 0.0; + [attributedString enumerateAttribute:NSFontAttributeName + inRange:NSMakeRange(0, attributedString.length) + options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired + usingBlock:^(id _Nullable value, NSRange range, BOOL *_Nonnull stop) { + CGFloat fontSize = ((UIFont *)value).pointSize; + if (fontSize > maximumFontSize) { + maximumFontSize = fontSize; + } + }]; + + return maximumFontSize; +} + - (NSTextStorage *)_textStorageAndLayoutManagerWithAttributesString:(NSAttributedString *)attributedString paragraphAttributes:(ParagraphAttributes)paragraphAttributes size:(CGSize)size @@ -243,8 +259,8 @@ - (NSTextStorage *)_textStorageAndLayoutManagerWithAttributesString:(NSAttribute [textStorage addLayoutManager:layoutManager]; if (paragraphAttributes.adjustsFontSizeToFit) { - CGFloat minimumFontSize = !isnan(paragraphAttributes.minimumFontSize) ? paragraphAttributes.minimumFontSize : 4.0; - CGFloat maximumFontSize = !isnan(paragraphAttributes.maximumFontSize) ? paragraphAttributes.maximumFontSize : 96.0; + CGFloat maximumFontSize = [self _maximumFontSizeInAttributedString:attributedString]; + CGFloat minimumFontSize = MAX(paragraphAttributes.minimumFontScale * maximumFontSize, 4.0); [textStorage scaleFontSizeToFitSize:size minimumFontSize:minimumFontSize maximumFontSize:maximumFontSize]; } From 802adfd73d5f5315d487eb9aaa44cf6f6242e95c Mon Sep 17 00:00:00 2001 From: Conner Reimers Date: Mon, 6 Oct 2025 17:48:49 -0500 Subject: [PATCH 2/3] Add RNTester examples --- .../rn-tester/js/examples/Text/TextExample.ios.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/rn-tester/js/examples/Text/TextExample.ios.js b/packages/rn-tester/js/examples/Text/TextExample.ios.js index 5fdff36d3bc2..25e74e6f2141 100644 --- a/packages/rn-tester/js/examples/Text/TextExample.ios.js +++ b/packages/rn-tester/js/examples/Text/TextExample.ios.js @@ -211,6 +211,21 @@ class AdjustingFontSize extends React.Component< Shrinking to fit available space is much better! + + It doesn't grow + + + + Can limit how small the text becomes with minimumFontScale + + Date: Mon, 6 Oct 2025 18:37:33 -0500 Subject: [PATCH 3/3] Remove unneeded RNTester example --- packages/rn-tester/js/examples/Text/TextExample.ios.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/rn-tester/js/examples/Text/TextExample.ios.js b/packages/rn-tester/js/examples/Text/TextExample.ios.js index 25e74e6f2141..231792339639 100644 --- a/packages/rn-tester/js/examples/Text/TextExample.ios.js +++ b/packages/rn-tester/js/examples/Text/TextExample.ios.js @@ -211,13 +211,6 @@ class AdjustingFontSize extends React.Component< Shrinking to fit available space is much better! - - It doesn't grow - -