diff --git a/BUCK b/BUCK index f6992f1619298c..53fd7dade2fe9e 100644 --- a/BUCK +++ b/BUCK @@ -264,6 +264,7 @@ REACT_PUBLIC_HEADERS = { "React/RCTDevLoadingViewProtocol.h": RCTDEVSUPPORT_PATH + "RCTDevLoadingViewProtocol.h", "React/RCTDevLoadingViewSetEnabled.h": RCTDEVSUPPORT_PATH + "RCTDevLoadingViewSetEnabled.h", "React/RCTDisplayLink.h": RCTBASE_PATH + "RCTDisplayLink.h", + "React/RCTDynamicTypeRamp.h": RCTLIB_PATH + "Text/Text/RCTDynamicTypeRamp.h", "React/RCTErrorCustomizer.h": RCTBASE_PATH + "RCTErrorCustomizer.h", "React/RCTErrorInfo.h": RCTBASE_PATH + "RCTErrorInfo.h", # NOTE: RCTEventDispatcher.h is exported from CoreModules:CoreModulesApple diff --git a/Libraries/Text/BaseText/RCTBaseTextViewManager.m b/Libraries/Text/BaseText/RCTBaseTextViewManager.m index 059f561c2f549e..ece68768c40251 100644 --- a/Libraries/Text/BaseText/RCTBaseTextViewManager.m +++ b/Libraries/Text/BaseText/RCTBaseTextViewManager.m @@ -36,6 +36,7 @@ - (RCTShadowView *)shadowView RCT_REMAP_SHADOW_PROPERTY(fontStyle, textAttributes.fontStyle, NSString) RCT_REMAP_SHADOW_PROPERTY(fontVariant, textAttributes.fontVariant, NSArray) RCT_REMAP_SHADOW_PROPERTY(allowFontScaling, textAttributes.allowFontScaling, BOOL) +RCT_REMAP_SHADOW_PROPERTY(dynamicTypeRamp, textAttributes.dynamicTypeRamp, RCTDynamicTypeRamp) RCT_REMAP_SHADOW_PROPERTY(maxFontSizeMultiplier, textAttributes.maxFontSizeMultiplier, CGFloat) RCT_REMAP_SHADOW_PROPERTY(letterSpacing, textAttributes.letterSpacing, CGFloat) // Paragraph Styles diff --git a/Libraries/Text/RCTTextAttributes.h b/Libraries/Text/RCTTextAttributes.h index 3491d598971b8e..22fb646d434940 100644 --- a/Libraries/Text/RCTTextAttributes.h +++ b/Libraries/Text/RCTTextAttributes.h @@ -7,6 +7,7 @@ #import +#import #import #import "RCTTextTransform.h" @@ -36,6 +37,7 @@ extern NSString *const RCTTextAttributesTagAttributeName; @property (nonatomic, copy, nullable) NSString *fontStyle; @property (nonatomic, copy, nullable) NSArray *fontVariant; @property (nonatomic, assign) BOOL allowFontScaling; +@property (nonatomic, assign) RCTDynamicTypeRamp dynamicTypeRamp; @property (nonatomic, assign) CGFloat letterSpacing; // Paragraph Styles @property (nonatomic, assign) CGFloat lineHeight; diff --git a/Libraries/Text/RCTTextAttributes.m b/Libraries/Text/RCTTextAttributes.m index 1fb13011430736..91fa354afaa7c8 100644 --- a/Libraries/Text/RCTTextAttributes.m +++ b/Libraries/Text/RCTTextAttributes.m @@ -59,6 +59,7 @@ - (void)applyTextAttributes:(RCTTextAttributes *)textAttributes _fontStyle = textAttributes->_fontStyle ?: _fontStyle; _fontVariant = textAttributes->_fontVariant ?: _fontVariant; _allowFontScaling = textAttributes->_allowFontScaling || _allowFontScaling; // * + _dynamicTypeRamp = textAttributes->_dynamicTypeRamp != RCTDynamicTypeRampUndefined ? textAttributes->_dynamicTypeRamp : _dynamicTypeRamp; _letterSpacing = !isnan(textAttributes->_letterSpacing) ? textAttributes->_letterSpacing : _letterSpacing; // Paragraph Styles @@ -230,6 +231,12 @@ - (CGFloat)effectiveFontSizeMultiplier if (fontScalingEnabled) { CGFloat fontSizeMultiplier = !isnan(_fontSizeMultiplier) ? _fontSizeMultiplier : 1.0; + if (_dynamicTypeRamp != RCTDynamicTypeRampUndefined) { + UIFontMetrics *fontMetrics = RCTUIFontMetricsForDynamicTypeRamp(_dynamicTypeRamp); + // Using a specific font size reduces rounding errors from -scaledValueForValue: + CGFloat requestedSize = isnan(_fontSize) ? RCTBaseSizeForDynamicTypeRamp(_dynamicTypeRamp) : _fontSize; + fontSizeMultiplier = [fontMetrics scaledValueForValue:requestedSize] / requestedSize; + } CGFloat maxFontSizeMultiplier = !isnan(_maxFontSizeMultiplier) ? _maxFontSizeMultiplier : 0.0; return maxFontSizeMultiplier >= 1.0 ? fminf(maxFontSizeMultiplier, fontSizeMultiplier) : fontSizeMultiplier; } else { @@ -324,7 +331,7 @@ - (BOOL)isEqual:(RCTTextAttributes *)textAttributes RCTTextAttributesCompareFloats(_fontSizeMultiplier) && RCTTextAttributesCompareFloats(_maxFontSizeMultiplier) && RCTTextAttributesCompareStrings(_fontWeight) && RCTTextAttributesCompareObjects(_fontStyle) && RCTTextAttributesCompareObjects(_fontVariant) && RCTTextAttributesCompareOthers(_allowFontScaling) && - RCTTextAttributesCompareFloats(_letterSpacing) && + RCTTextAttributesCompareOthers(_dynamicTypeRamp) && RCTTextAttributesCompareFloats(_letterSpacing) && // Paragraph Styles RCTTextAttributesCompareFloats(_lineHeight) && RCTTextAttributesCompareFloats(_alignment) && RCTTextAttributesCompareOthers(_baseWritingDirection) && RCTTextAttributesCompareOthers(_lineBreakStrategy) && diff --git a/Libraries/Text/Text.d.ts b/Libraries/Text/Text.d.ts index 715123576a12c1..5ce6fecc5e72b2 100644 --- a/Libraries/Text/Text.d.ts +++ b/Libraries/Text/Text.d.ts @@ -26,6 +26,23 @@ export interface TextPropsIOS { */ adjustsFontSizeToFit?: boolean | undefined; + /** + * The Dynamic Text scale ramp to apply to this element on iOS. + */ + dynamicTypeRamp?: + | 'caption2' + | 'caption1' + | 'footnote' + | 'subheadline' + | 'callout' + | 'body' + | 'headline' + | 'title3' + | 'title2' + | 'title1' + | 'largeTitle' + | undefined; + /** * Specifies smallest possible scale a font can reach when adjustsFontSizeToFit is enabled. (values 0.01-1.0). */ diff --git a/Libraries/Text/Text/RCTDynamicTypeRamp.h b/Libraries/Text/Text/RCTDynamicTypeRamp.h new file mode 100644 index 00000000000000..49fee502671a46 --- /dev/null +++ b/Libraries/Text/Text/RCTDynamicTypeRamp.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +#import + +typedef NS_ENUM(NSInteger, RCTDynamicTypeRamp) { + RCTDynamicTypeRampUndefined, + RCTDynamicTypeRampCaption2, + RCTDynamicTypeRampCaption1, + RCTDynamicTypeRampFootnote, + RCTDynamicTypeRampSubheadline, + RCTDynamicTypeRampCallout, + RCTDynamicTypeRampBody, + RCTDynamicTypeRampHeadline, + RCTDynamicTypeRampTitle3, + RCTDynamicTypeRampTitle2, + RCTDynamicTypeRampTitle1, + RCTDynamicTypeRampLargeTitle +}; + +@interface RCTConvert (DynamicTypeRamp) + ++ (RCTDynamicTypeRamp)RCTDynamicTypeRamp:(nullable id)json; + +@end + +/// Generates a `UIFontMetrics` instance representing a particular Dynamic Type ramp. +UIFontMetrics * _Nonnull RCTUIFontMetricsForDynamicTypeRamp(RCTDynamicTypeRamp dynamicTypeRamp); +/// The "reference" size for a particular font scale ramp, equal to a text element's size under default text size settings. +CGFloat RCTBaseSizeForDynamicTypeRamp(RCTDynamicTypeRamp dynamicTypeRamp); diff --git a/Libraries/Text/Text/RCTDynamicTypeRamp.m b/Libraries/Text/Text/RCTDynamicTypeRamp.m new file mode 100644 index 00000000000000..248d8790c950dc --- /dev/null +++ b/Libraries/Text/Text/RCTDynamicTypeRamp.m @@ -0,0 +1,73 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +@implementation RCTConvert (DynamicTypeRamp) + +RCT_ENUM_CONVERTER(RCTDynamicTypeRamp, (@{ + @"caption2": @(RCTDynamicTypeRampCaption2), + @"caption1": @(RCTDynamicTypeRampCaption1), + @"footnote": @(RCTDynamicTypeRampFootnote), + @"subheadline": @(RCTDynamicTypeRampSubheadline), + @"callout": @(RCTDynamicTypeRampCallout), + @"body": @(RCTDynamicTypeRampBody), + @"headline": @(RCTDynamicTypeRampHeadline), + @"title3": @(RCTDynamicTypeRampTitle3), + @"title2": @(RCTDynamicTypeRampTitle2), + @"title1": @(RCTDynamicTypeRampTitle1), + @"largeTitle": @(RCTDynamicTypeRampLargeTitle), +}), RCTDynamicTypeRampUndefined, integerValue) + +@end + +UIFontMetrics *RCTUIFontMetricsForDynamicTypeRamp(RCTDynamicTypeRamp dynamicTypeRamp) { + static NSDictionary *mapping; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + mapping = @{ + @(RCTDynamicTypeRampCaption2): UIFontTextStyleCaption2, + @(RCTDynamicTypeRampCaption1): UIFontTextStyleCaption1, + @(RCTDynamicTypeRampFootnote): UIFontTextStyleFootnote, + @(RCTDynamicTypeRampSubheadline): UIFontTextStyleSubheadline, + @(RCTDynamicTypeRampCallout): UIFontTextStyleCallout, + @(RCTDynamicTypeRampBody): UIFontTextStyleBody, + @(RCTDynamicTypeRampHeadline): UIFontTextStyleHeadline, + @(RCTDynamicTypeRampTitle3): UIFontTextStyleTitle3, + @(RCTDynamicTypeRampTitle2): UIFontTextStyleTitle2, + @(RCTDynamicTypeRampTitle1): UIFontTextStyleTitle1, + @(RCTDynamicTypeRampLargeTitle): UIFontTextStyleLargeTitle, + }; + }); + + id textStyle = mapping[@(dynamicTypeRamp)] ?: UIFontTextStyleBody; // Default to body if we don't recognize the specified ramp + return [UIFontMetrics metricsForTextStyle:textStyle]; +} + +CGFloat RCTBaseSizeForDynamicTypeRamp(RCTDynamicTypeRamp dynamicTypeRamp) { + static NSDictionary *mapping; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // Values taken from https://developer.apple.com/design/human-interface-guidelines/foundations/typography/#specifications + mapping = @{ + @(RCTDynamicTypeRampCaption2): @11, + @(RCTDynamicTypeRampCaption1): @12, + @(RCTDynamicTypeRampFootnote): @13, + @(RCTDynamicTypeRampSubheadline): @15, + @(RCTDynamicTypeRampCallout): @16, + @(RCTDynamicTypeRampBody): @17, + @(RCTDynamicTypeRampHeadline): @17, + @(RCTDynamicTypeRampTitle3): @20, + @(RCTDynamicTypeRampTitle2): @22, + @(RCTDynamicTypeRampTitle1): @28, + @(RCTDynamicTypeRampLargeTitle): @34, + }; + }); + + NSNumber *baseSize = mapping[@(dynamicTypeRamp)] ?: @17; // Default to body size if we don't recognize the specified ramp + return CGFLOAT_IS_DOUBLE ? [baseSize doubleValue] : [baseSize floatValue]; +} diff --git a/Libraries/Text/TextNativeComponent.js b/Libraries/Text/TextNativeComponent.js index dd687c1697bf9a..0d5990455b5be0 100644 --- a/Libraries/Text/TextNativeComponent.js +++ b/Libraries/Text/TextNativeComponent.js @@ -34,6 +34,7 @@ const textViewConfig = { numberOfLines: true, ellipsizeMode: true, allowFontScaling: true, + dynamicTypeRamp: true, maxFontSizeMultiplier: true, disabled: true, selectable: true, diff --git a/Libraries/Text/TextProps.js b/Libraries/Text/TextProps.js index abbbd76da14cad..661571357ea4f5 100644 --- a/Libraries/Text/TextProps.js +++ b/Libraries/Text/TextProps.js @@ -229,6 +229,23 @@ export type TextProps = $ReadOnly<{| */ adjustsFontSizeToFit?: ?boolean, + /** + * The Dynamic Text scale ramp to apply to this element on iOS. + */ + dynamicTypeRamp?: ?( + | 'caption2' + | 'caption1' + | 'footnote' + | 'subheadline' + | 'callout' + | 'body' + | 'headline' + | 'title3' + | 'title2' + | 'title1' + | 'largeTitle' + ), + /** * Smallest possible scale a font can reach. * diff --git a/ReactCommon/react/renderer/attributedstring/TextAttributes.cpp b/ReactCommon/react/renderer/attributedstring/TextAttributes.cpp index 24f5bbd07b8072..63b5a4aab19fce 100644 --- a/ReactCommon/react/renderer/attributedstring/TextAttributes.cpp +++ b/ReactCommon/react/renderer/attributedstring/TextAttributes.cpp @@ -46,6 +46,9 @@ void TextAttributes::apply(TextAttributes textAttributes) { allowFontScaling = textAttributes.allowFontScaling.has_value() ? textAttributes.allowFontScaling : allowFontScaling; + dynamicTypeRamp = textAttributes.dynamicTypeRamp.has_value() + ? textAttributes.dynamicTypeRamp + : dynamicTypeRamp; letterSpacing = !std::isnan(textAttributes.letterSpacing) ? textAttributes.letterSpacing : letterSpacing; @@ -111,6 +114,7 @@ bool TextAttributes::operator==(const TextAttributes &rhs) const { fontStyle, fontVariant, allowFontScaling, + dynamicTypeRamp, alignment, baseWritingDirection, lineBreakStrategy, @@ -131,6 +135,7 @@ bool TextAttributes::operator==(const TextAttributes &rhs) const { rhs.fontStyle, rhs.fontVariant, rhs.allowFontScaling, + rhs.dynamicTypeRamp, rhs.alignment, rhs.baseWritingDirection, rhs.lineBreakStrategy, @@ -186,6 +191,7 @@ SharedDebugStringConvertibleList TextAttributes::getDebugProps() const { debugStringConvertibleItem("fontStyle", fontStyle), debugStringConvertibleItem("fontVariant", fontVariant), debugStringConvertibleItem("allowFontScaling", allowFontScaling), + debugStringConvertibleItem("dynamicTypeRamp", dynamicTypeRamp), debugStringConvertibleItem("letterSpacing", letterSpacing), // Paragraph Styles diff --git a/ReactCommon/react/renderer/attributedstring/TextAttributes.h b/ReactCommon/react/renderer/attributedstring/TextAttributes.h index 69400f2ed172f7..89a6ba36f247ec 100644 --- a/ReactCommon/react/renderer/attributedstring/TextAttributes.h +++ b/ReactCommon/react/renderer/attributedstring/TextAttributes.h @@ -50,6 +50,7 @@ class TextAttributes : public DebugStringConvertible { std::optional fontStyle{}; std::optional fontVariant{}; std::optional allowFontScaling{}; + std::optional dynamicTypeRamp{}; Float letterSpacing{std::numeric_limits::quiet_NaN()}; std::optional textTransform{}; diff --git a/ReactCommon/react/renderer/attributedstring/conversions.h b/ReactCommon/react/renderer/attributedstring/conversions.h index f854d6f53036ca..f768de8cd82d2f 100644 --- a/ReactCommon/react/renderer/attributedstring/conversions.h +++ b/ReactCommon/react/renderer/attributedstring/conversions.h @@ -34,6 +34,84 @@ namespace facebook { namespace react { +inline std::string toString(const DynamicTypeRamp &dynamicTypeRamp) { + switch (dynamicTypeRamp) { + case DynamicTypeRamp::Caption2: + return "caption2"; + case DynamicTypeRamp::Caption1: + return "caption1"; + case DynamicTypeRamp::Footnote: + return "footnote"; + case DynamicTypeRamp::Subheadline: + return "subheadline"; + case DynamicTypeRamp::Callout: + return "callout"; + case DynamicTypeRamp::Body: + return "body"; + case DynamicTypeRamp::Headline: + return "headline"; + case DynamicTypeRamp::Title3: + return "title3"; + case DynamicTypeRamp::Title2: + return "title2"; + case DynamicTypeRamp::Title1: + return "title1"; + case DynamicTypeRamp::LargeTitle: + return "largeTitle"; + } + + LOG(ERROR) << "Unsupported DynamicTypeRamp value"; + react_native_assert(false); + + // Sane default in case of parsing errors + return "body"; +} + +inline void fromRawValue( + const PropsParserContext &context, + const RawValue &value, + DynamicTypeRamp &result) { + react_native_assert(value.hasType()); + if (value.hasType()) { + auto string = (std::string)value; + if (string == "caption2") { + result = DynamicTypeRamp::Caption2; + } else if (string == "caption1") { + result = DynamicTypeRamp::Caption1; + } else if (string == "footnote") { + result = DynamicTypeRamp::Footnote; + } else if (string == "subheadline") { + result = DynamicTypeRamp::Subheadline; + } else if (string == "callout") { + result = DynamicTypeRamp::Callout; + } else if (string == "body") { + result = DynamicTypeRamp::Body; + } else if (string == "headline") { + result = DynamicTypeRamp::Headline; + } else if (string == "title3") { + result = DynamicTypeRamp::Title3; + } else if (string == "title2") { + result = DynamicTypeRamp::Title2; + } else if (string == "title1") { + result = DynamicTypeRamp::Title1; + } else if (string == "largeTitle") { + result = DynamicTypeRamp::LargeTitle; + } else { + // sane default + LOG(ERROR) << "Unsupported DynamicTypeRamp value: " << string; + react_native_assert(false); + result = DynamicTypeRamp::Body; + } + return; + } + + LOG(ERROR) << "Unsupported DynamicTypeRamp type"; + react_native_assert(false); + + // Sane default in case of parsing errors + result = DynamicTypeRamp::Body; +} + inline std::string toString(const EllipsizeMode &ellipsisMode) { switch (ellipsisMode) { case EllipsizeMode::Clip: diff --git a/ReactCommon/react/renderer/attributedstring/primitives.h b/ReactCommon/react/renderer/attributedstring/primitives.h index 3c6c7bda2654b6..f3ef237c9a829f 100644 --- a/ReactCommon/react/renderer/attributedstring/primitives.h +++ b/ReactCommon/react/renderer/attributedstring/primitives.h @@ -46,6 +46,20 @@ enum class FontVariant : int { ProportionalNums = 1 << 5 }; +enum class DynamicTypeRamp { + Caption2, + Caption1, + Footnote, + Subheadline, + Callout, + Body, + Headline, + Title3, + Title2, + Title1, + LargeTitle +}; + enum class EllipsizeMode { Clip, // Do not add ellipsize, simply clip. Head, // Truncate at head of line: "...wxyz". @@ -190,6 +204,13 @@ struct hash { } }; +template <> +struct hash { + size_t operator()(const facebook::react::DynamicTypeRamp &v) const { + return hash()(static_cast(v)); + } +}; + template <> struct hash { size_t operator()(const facebook::react::EllipsizeMode &v) const { diff --git a/ReactCommon/react/renderer/components/text/BaseTextProps.cpp b/ReactCommon/react/renderer/components/text/BaseTextProps.cpp index 6f4741ba69c9ee..e098e7beab2404 100644 --- a/ReactCommon/react/renderer/components/text/BaseTextProps.cpp +++ b/ReactCommon/react/renderer/components/text/BaseTextProps.cpp @@ -73,6 +73,12 @@ static TextAttributes convertRawProp( "allowFontScaling", sourceTextAttributes.allowFontScaling, defaultTextAttributes.allowFontScaling); + textAttributes.dynamicTypeRamp = convertRawProp( + context, + rawProps, + "dynamicTypeRamp", + sourceTextAttributes.dynamicTypeRamp, + defaultTextAttributes.dynamicTypeRamp); textAttributes.letterSpacing = convertRawProp( context, rawProps, diff --git a/ReactCommon/react/renderer/textlayoutmanager/TextMeasureCache.h b/ReactCommon/react/renderer/textlayoutmanager/TextMeasureCache.h index aee6ebdeceea73..23486173c68068 100644 --- a/ReactCommon/react/renderer/textlayoutmanager/TextMeasureCache.h +++ b/ReactCommon/react/renderer/textlayoutmanager/TextMeasureCache.h @@ -94,6 +94,7 @@ inline bool areTextAttributesEquivalentLayoutWise( lhs.fontStyle, lhs.fontVariant, lhs.allowFontScaling, + lhs.dynamicTypeRamp, lhs.alignment) == std::tie( rhs.fontFamily, @@ -101,6 +102,7 @@ inline bool areTextAttributesEquivalentLayoutWise( rhs.fontStyle, rhs.fontVariant, rhs.allowFontScaling, + rhs.dynamicTypeRamp, rhs.alignment) && floatEquality(lhs.fontSize, rhs.fontSize) && floatEquality(lhs.fontSizeMultiplier, rhs.fontSizeMultiplier) && @@ -121,6 +123,7 @@ inline size_t textAttributesHashLayoutWise( textAttributes.fontStyle, textAttributes.fontVariant, textAttributes.allowFontScaling, + textAttributes.dynamicTypeRamp, textAttributes.letterSpacing, textAttributes.lineHeight, textAttributes.alignment); diff --git a/ReactCommon/react/renderer/textlayoutmanager/platform/ios/RCTAttributedTextUtils.mm b/ReactCommon/react/renderer/textlayoutmanager/platform/ios/RCTAttributedTextUtils.mm index 7f5117040cc1a2..2e5777353c3623 100644 --- a/ReactCommon/react/renderer/textlayoutmanager/platform/ios/RCTAttributedTextUtils.mm +++ b/ReactCommon/react/renderer/textlayoutmanager/platform/ios/RCTAttributedTextUtils.mm @@ -55,6 +55,78 @@ inline static UIFontWeight RCTUIFontWeightFromInteger(NSInteger fontWeight) return weights[(fontWeight + 50) / 100 - 1]; } +inline static UIFontTextStyle RCTUIFontTextStyleForDynamicTypeRamp(const DynamicTypeRamp &dynamicTypeRamp) { + switch (dynamicTypeRamp) { + case DynamicTypeRamp::Caption2: + return UIFontTextStyleCaption2; + case DynamicTypeRamp::Caption1: + return UIFontTextStyleCaption1; + case DynamicTypeRamp::Footnote: + return UIFontTextStyleFootnote; + case DynamicTypeRamp::Subheadline: + return UIFontTextStyleSubheadline; + case DynamicTypeRamp::Callout: + return UIFontTextStyleCallout; + case DynamicTypeRamp::Body: + return UIFontTextStyleBody; + case DynamicTypeRamp::Headline: + return UIFontTextStyleHeadline; + case DynamicTypeRamp::Title3: + return UIFontTextStyleTitle3; + case DynamicTypeRamp::Title2: + return UIFontTextStyleTitle2; + case DynamicTypeRamp::Title1: + return UIFontTextStyleTitle1; + case DynamicTypeRamp::LargeTitle: + return UIFontTextStyleLargeTitle; + } +} + +inline static CGFloat RCTBaseSizeForDynamicTypeRamp(const DynamicTypeRamp &dynamicTypeRamp) { + // Values taken from https://developer.apple.com/design/human-interface-guidelines/foundations/typography/#specifications + switch (dynamicTypeRamp) { + case DynamicTypeRamp::Caption2: + return 11.0; + case DynamicTypeRamp::Caption1: + return 12.0; + case facebook::react::DynamicTypeRamp::Footnote: + return 13.0; + case facebook::react::DynamicTypeRamp::Subheadline: + return 15.0; + case facebook::react::DynamicTypeRamp::Callout: + return 16.0; + case facebook::react::DynamicTypeRamp::Body: + return 17.0; + case facebook::react::DynamicTypeRamp::Headline: + return 17.0; + case facebook::react::DynamicTypeRamp::Title3: + return 20.0; + case facebook::react::DynamicTypeRamp::Title2: + return 22.0; + case facebook::react::DynamicTypeRamp::Title1: + return 28.0; + case facebook::react::DynamicTypeRamp::LargeTitle: + return 34.0; + } +} + +inline static CGFloat RCTEffectiveFontSizeMultiplierFromTextAttributes(const TextAttributes &textAttributes) +{ + if (textAttributes.allowFontScaling.value_or(true)) { + if (textAttributes.dynamicTypeRamp.has_value()) { + DynamicTypeRamp dynamicTypeRamp = textAttributes.dynamicTypeRamp.value(); + UIFontMetrics *fontMetrics = [UIFontMetrics metricsForTextStyle:RCTUIFontTextStyleForDynamicTypeRamp(dynamicTypeRamp)]; + // 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 { + return textAttributes.fontSizeMultiplier; + } + } else { + return 1.0; + } +} + inline static UIFont *RCTEffectiveFontFromTextAttributes(const TextAttributes &textAttributes) { NSString *fontFamily = [NSString stringWithCString:textAttributes.fontFamily.c_str() encoding:NSUTF8StringEncoding]; @@ -71,19 +143,11 @@ inline static UIFontWeight RCTUIFontWeightFromInteger(NSInteger fontWeight) fontProperties.weight = textAttributes.fontWeight.has_value() ? RCTUIFontWeightFromInteger((NSInteger)textAttributes.fontWeight.value()) : NAN; - fontProperties.sizeMultiplier = - textAttributes.allowFontScaling.value_or(true) ? textAttributes.fontSizeMultiplier : 1; + fontProperties.sizeMultiplier = RCTEffectiveFontSizeMultiplierFromTextAttributes(textAttributes); return RCTFontWithFontProperties(fontProperties); } -inline static CGFloat RCTEffectiveFontSizeMultiplierFromTextAttributes(const TextAttributes &textAttributes) -{ - return textAttributes.allowFontScaling.value_or(true) && !isnan(textAttributes.fontSizeMultiplier) - ? textAttributes.fontSizeMultiplier - : 1.0; -} - inline static UIColor *RCTEffectiveForegroundColorFromTextAttributes(const TextAttributes &textAttributes) { UIColor *effectiveForegroundColor = RCTUIColorFromSharedColor(textAttributes.foregroundColor) ?: [UIColor blackColor]; diff --git a/packages/rn-tester/Podfile.lock b/packages/rn-tester/Podfile.lock index db2169f72cc96d..12a8d90fc9708a 100644 --- a/packages/rn-tester/Podfile.lock +++ b/packages/rn-tester/Podfile.lock @@ -98,12 +98,6 @@ PODS: - DoubleConversion - fmt (~> 6.2.1) - glog - - RCT-Folly/Futures (2021.07.22.00): - - boost - - DoubleConversion - - fmt (~> 6.2.1) - - glog - - libevent - RCTRequired (1000.0.0) - RCTTypeSafety (1000.0.0): - FBLazyVector (= 1000.0.0) @@ -783,8 +777,6 @@ DEPENDENCIES: - FlipperKit/FlipperKitUserDefaultsPlugin (= 0.125.0) - FlipperKit/SKIOSNetworkPlugin (= 0.125.0) - glog (from `../../third-party-podspecs/glog.podspec`) - - hermes-engine (from `../../sdks/hermes/hermes-engine.podspec`) - - libevent (~> 2.1.12) - OpenSSL-Universal (= 1.1.1100) - RCT-Folly (from `../../third-party-podspecs/RCT-Folly.podspec`) - RCT-Folly/Fabric (from `../../third-party-podspecs/RCT-Folly.podspec`) @@ -801,7 +793,6 @@ DEPENDENCIES: - React-cxxreact (from `../../ReactCommon/cxxreact`) - React-Fabric (from `../../ReactCommon`) - React-graphics (from `../../ReactCommon/react/renderer/graphics`) - - React-hermes (from `../../ReactCommon/hermes`) - React-jsi (from `../../ReactCommon/jsi`) - React-jsidynamic (from `../../ReactCommon/jsi`) - React-jsiexecutor (from `../../ReactCommon/jsiexecutor`) @@ -857,8 +848,6 @@ EXTERNAL SOURCES: :path: "../../React/FBReactNativeSpec" glog: :podspec: "../../third-party-podspecs/glog.podspec" - hermes-engine: - :podspec: "../../sdks/hermes/hermes-engine.podspec" RCT-Folly: :podspec: "../../third-party-podspecs/RCT-Folly.podspec" RCTRequired: @@ -883,8 +872,6 @@ EXTERNAL SOURCES: :path: "../../ReactCommon" React-graphics: :path: "../../ReactCommon/react/renderer/graphics" - React-hermes: - :path: "../../ReactCommon/hermes" React-jsi: :path: "../../ReactCommon/jsi" React-jsidynamic: @@ -991,7 +978,7 @@ SPEC CHECKSUMS: ReactCommon: b1f213aa09e3dfd0a89389b5023fdb1cd6528e96 ScreenshotManager: 06cb3d1794c8082d92b3e91813d1678d0977a4fb SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608 - Yoga: 1b1a12ff3d86a10565ea7cbe057d42f5e5fb2a07 + Yoga: e93e3ae06a7dacc927be0ed44637d77e279e2172 YogaKit: f782866e155069a2cca2517aafea43200b01fd5a PODFILE CHECKSUM: 20298ecd3f30aa788ad491637e593ed0d8c100ca diff --git a/packages/rn-tester/js/examples/Text/TextExample.ios.js b/packages/rn-tester/js/examples/Text/TextExample.ios.js index b0e6572a07722b..4a4edacc5c690f 100644 --- a/packages/rn-tester/js/examples/Text/TextExample.ios.js +++ b/packages/rn-tester/js/examples/Text/TextExample.ios.js @@ -1283,4 +1283,49 @@ exports.examples = [ ); }, }, + { + title: 'Dynamic Type (iOS only)', + render: function (): React.Node { + const boldStyle = {fontWeight: 'bold'}; + const boxStyle = { + borderWidth: 1, + padding: 8, + margin: 8, + }; + return ( + + + Adjust text size in Accessibility settings and watch how the font + sizes change relative to each other. + + + With `dynamicTypeRamp`: + + Large Title + + + Title + + + Title 2 + + + Title 3 + + + Body + + + + Without `dynamicTypeRamp`: + Large Title + Title + Title 2 + Title 3 + Body + + + ); + }, + }, ];