Repository navigation
Trim NSAttributedString construction for text on iOS #59154
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -138,9 +138,13 @@ inline static CGFloat RCTEffectiveFontSizeMultiplierFromTextAttributes(const Tex | |
| } | ||
| } | ||
|
|
||
| inline static UIFont *RCTEffectiveFontFromTextAttributes(const TextAttributes &textAttributes) | ||
| inline static UIFont *RCTEffectiveFontFromTextAttributes( | ||
| const TextAttributes &textAttributes, | ||
| CGFloat fontSizeMultiplier) | ||
| { | ||
| NSString *fontFamily = [NSString stringWithUTF8String:textAttributes.fontFamily.c_str()]; | ||
| // An unset family resolves to the default one; no need to build a string for it. | ||
| NSString *fontFamily = | ||
| textAttributes.fontFamily.empty() ? nil : [NSString stringWithUTF8String:textAttributes.fontFamily.c_str()]; | ||
|
|
||
| RCTFontProperties fontProperties; | ||
| fontProperties.family = fontFamily; | ||
|
|
@@ -158,7 +162,7 @@ inline static CGFloat RCTEffectiveFontSizeMultiplierFromTextAttributes(const Tex | |
| NSString *variationSettings = [NSString stringWithUTF8String:textAttributes.fontVariationSettings->c_str()]; | ||
| fontProperties.variations = RCTParseFontVariationSettings(variationSettings); | ||
| } | ||
| fontProperties.sizeMultiplier = RCTEffectiveFontSizeMultiplierFromTextAttributes(textAttributes); | ||
| fontProperties.sizeMultiplier = fontSizeMultiplier; | ||
|
|
||
| return RCTFontWithFontProperties(fontProperties); | ||
| } | ||
|
|
@@ -193,7 +197,8 @@ inline static CGFloat RCTEffectiveFontSizeMultiplierFromTextAttributes(const Tex | |
| NSMutableDictionary<NSAttributedStringKey, id> *attributes = [NSMutableDictionary dictionaryWithCapacity:10]; | ||
|
|
||
| // Font | ||
| UIFont *font = RCTEffectiveFontFromTextAttributes(textAttributes); | ||
| CGFloat fontSizeMultiplier = RCTEffectiveFontSizeMultiplierFromTextAttributes(textAttributes); | ||
| UIFont *font = RCTEffectiveFontFromTextAttributes(textAttributes, fontSizeMultiplier); | ||
| if (font) { | ||
| attributes[NSFontAttributeName] = font; | ||
| } | ||
|
|
@@ -215,42 +220,38 @@ inline static CGFloat RCTEffectiveFontSizeMultiplierFromTextAttributes(const Tex | |
| } | ||
|
|
||
| // Paragraph Style | ||
| NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new]; | ||
| BOOL isParagraphStyleUsed = NO; | ||
| const bool isRTL = textAttributes.layoutDirection == LayoutDirection::RightToLeft; | ||
| if (textAttributes.alignment.has_value() || isRTL) { | ||
| TextAlignment textAlignment = | ||
| RCTResolveTextAlignment(textAttributes.alignment.value_or(TextAlignment::Natural), isRTL); | ||
|
|
||
| paragraphStyle.alignment = RCTNSTextAlignmentFromTextAlignment(textAlignment); | ||
| isParagraphStyleUsed = YES; | ||
| } | ||
| const bool hasLineHeight = !isnan(textAttributes.lineHeight); | ||
| if (textAttributes.alignment.has_value() || isRTL || textAttributes.baseWritingDirection.has_value() || | ||
| textAttributes.lineBreakStrategy.has_value() || textAttributes.lineBreakMode.has_value() || hasLineHeight) { | ||
|
Comment on lines
+225
to
+226
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why conflating all the checks in this one if we also have to check every item singularly anyway? |
||
| NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new]; | ||
|
|
||
| if (textAttributes.alignment.has_value() || isRTL) { | ||
| TextAlignment textAlignment = | ||
| RCTResolveTextAlignment(textAttributes.alignment.value_or(TextAlignment::Natural), isRTL); | ||
| paragraphStyle.alignment = RCTNSTextAlignmentFromTextAlignment(textAlignment); | ||
| } | ||
|
|
||
| if (textAttributes.baseWritingDirection.has_value()) { | ||
| paragraphStyle.baseWritingDirection = | ||
| RCTNSWritingDirectionFromWritingDirection(textAttributes.baseWritingDirection.value()); | ||
| isParagraphStyleUsed = YES; | ||
| } | ||
| if (textAttributes.baseWritingDirection.has_value()) { | ||
| paragraphStyle.baseWritingDirection = | ||
| RCTNSWritingDirectionFromWritingDirection(textAttributes.baseWritingDirection.value()); | ||
| } | ||
|
|
||
| if (textAttributes.lineBreakStrategy.has_value()) { | ||
| paragraphStyle.lineBreakStrategy = | ||
| RCTNSLineBreakStrategyFromLineBreakStrategy(textAttributes.lineBreakStrategy.value()); | ||
| isParagraphStyleUsed = YES; | ||
| } | ||
| if (textAttributes.lineBreakStrategy.has_value()) { | ||
| paragraphStyle.lineBreakStrategy = | ||
| RCTNSLineBreakStrategyFromLineBreakStrategy(textAttributes.lineBreakStrategy.value()); | ||
| } | ||
|
|
||
| if (textAttributes.lineBreakMode.has_value()) { | ||
| paragraphStyle.lineBreakMode = RCTNSLineBreakModeFromLineBreakMode(textAttributes.lineBreakMode.value()); | ||
| isParagraphStyleUsed = YES; | ||
| } | ||
| if (textAttributes.lineBreakMode.has_value()) { | ||
| paragraphStyle.lineBreakMode = RCTNSLineBreakModeFromLineBreakMode(textAttributes.lineBreakMode.value()); | ||
| } | ||
|
|
||
| if (!isnan(textAttributes.lineHeight)) { | ||
| CGFloat lineHeight = textAttributes.lineHeight * RCTEffectiveFontSizeMultiplierFromTextAttributes(textAttributes); | ||
| paragraphStyle.minimumLineHeight = lineHeight; | ||
| paragraphStyle.maximumLineHeight = lineHeight; | ||
| isParagraphStyleUsed = YES; | ||
| } | ||
| if (hasLineHeight) { | ||
| CGFloat lineHeight = textAttributes.lineHeight * fontSizeMultiplier; | ||
| paragraphStyle.minimumLineHeight = lineHeight; | ||
| paragraphStyle.maximumLineHeight = lineHeight; | ||
| } | ||
|
|
||
| if (isParagraphStyleUsed) { | ||
| attributes[NSParagraphStyleAttributeName] = paragraphStyle; | ||
| } | ||
|
|
||
|
|
@@ -405,6 +406,9 @@ void RCTApplyBaselineOffset(NSMutableAttributedString *attributedText) | |
| const AttributedString::Fragment &fragment, | ||
| UIImage *placeholderImage) | ||
| { | ||
| id eventEmitterWrapper = | ||
| fragment.parentShadowView.componentHandle ? RCTWrapEventEmitter(fragment.parentShadowView.eventEmitter) : nil; | ||
|
|
||
| if (fragment.isAttachment()) { | ||
| auto layoutMetrics = fragment.parentShadowView.layoutMetrics; | ||
| CGRect bounds = { | ||
|
|
@@ -415,41 +419,35 @@ void RCTApplyBaselineOffset(NSMutableAttributedString *attributedText) | |
| attachment.image = placeholderImage; | ||
| attachment.bounds = bounds; | ||
|
|
||
| return [[NSMutableAttributedString attributedStringWithAttachment:attachment] mutableCopy]; | ||
| } else { | ||
| NSString *decoded = [[NSString alloc] initWithBytes:fragment.string.data() | ||
| length:fragment.string.size() | ||
| encoding:NSUTF8StringEncoding]; | ||
| NSString *string = decoded != nil ? decoded : @""; | ||
|
|
||
| if (fragment.textAttributes.textTransform.has_value()) { | ||
| auto textTransform = fragment.textAttributes.textTransform.value(); | ||
| string = RCTNSStringFromStringApplyingTextTransform(string, textTransform); | ||
| NSMutableAttributedString *nsAttributedStringFragment = | ||
| [[NSMutableAttributedString attributedStringWithAttachment:attachment] mutableCopy]; | ||
| if (eventEmitterWrapper != nil) { | ||
| [nsAttributedStringFragment addAttribute:RCTAttributedStringEventEmitterKey | ||
| value:eventEmitterWrapper | ||
| range:NSMakeRange(0, nsAttributedStringFragment.length)]; | ||
|
Comment on lines
+423
to
+427
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this changes the behavior of React Native and has nothing to do with the PR title. Before we were not attaching the event emitter if the fragment was an attachment, now we are. If you want this change, please create a separate PR with the proper title and summary. Let's have small and focused PRs, please. |
||
| } | ||
|
|
||
| return [[NSMutableAttributedString alloc] | ||
| initWithString:string | ||
| attributes:RCTNSTextAttributesFromTextAttributes(fragment.textAttributes)]; | ||
| return nsAttributedStringFragment; | ||
| } | ||
| } | ||
|
|
||
| static NSMutableAttributedString *RCTNSAttributedStringFragmentWithAttributesFromFragment( | ||
| const AttributedString::Fragment &fragment, | ||
| UIImage *placeholderImage) | ||
| { | ||
| auto nsAttributedStringFragment = RCTNSAttributedStringFragmentFromFragment(fragment, placeholderImage); | ||
|
|
||
| if (fragment.parentShadowView.componentHandle) { | ||
| auto eventEmitterWrapper = RCTWrapEventEmitter(fragment.parentShadowView.eventEmitter); | ||
| NSString *decoded = [[NSString alloc] initWithBytes:fragment.string.data() | ||
| length:fragment.string.size() | ||
| encoding:NSUTF8StringEncoding]; | ||
| NSString *string = decoded != nil ? decoded : @""; | ||
|
|
||
| NSDictionary<NSAttributedStringKey, id> *additionalTextAttributes = | ||
| @{RCTAttributedStringEventEmitterKey : eventEmitterWrapper}; | ||
| if (fragment.textAttributes.textTransform.has_value()) { | ||
| auto textTransform = fragment.textAttributes.textTransform.value(); | ||
| string = RCTNSStringFromStringApplyingTextTransform(string, textTransform); | ||
| } | ||
|
|
||
| [nsAttributedStringFragment addAttributes:additionalTextAttributes | ||
| range:NSMakeRange(0, nsAttributedStringFragment.length)]; | ||
| // The event emitter goes into the initial attributes rather than a second | ||
| // pass over the string, which would split and re-merge its attribute runs. | ||
| NSMutableDictionary<NSAttributedStringKey, id> *attributes = | ||
| RCTNSTextAttributesFromTextAttributes(fragment.textAttributes); | ||
| if (eventEmitterWrapper != nil) { | ||
| attributes[RCTAttributedStringEventEmitterKey] = eventEmitterWrapper; | ||
| } | ||
|
|
||
| return nsAttributedStringFragment; | ||
| return [[NSMutableAttributedString alloc] initWithString:string attributes:attributes]; | ||
| } | ||
|
|
||
| NSAttributedString *RCTNSAttributedStringFromAttributedString(const AttributedString &attributedString) | ||
|
|
@@ -460,15 +458,19 @@ void RCTApplyBaselineOffset(NSMutableAttributedString *attributedText) | |
| placeholderImage = [UIImage new]; | ||
| }); | ||
|
|
||
| const auto &fragments = attributedString.getFragments(); | ||
|
|
||
| // Most paragraphs are a single run of text; it needs no enclosing string. | ||
| if (fragments.size() == 1) { | ||
| return RCTNSAttributedStringFragmentFromFragment(fragments.front(), placeholderImage); | ||
| } | ||
|
|
||
| NSMutableAttributedString *nsAttributedString = [NSMutableAttributedString new]; | ||
|
|
||
| [nsAttributedString beginEditing]; | ||
|
|
||
| for (auto fragment : attributedString.getFragments()) { | ||
| NSMutableAttributedString *nsAttributedStringFragment = | ||
| RCTNSAttributedStringFragmentWithAttributesFromFragment(fragment, placeholderImage); | ||
|
|
||
| [nsAttributedString appendAttributedString:nsAttributedStringFragment]; | ||
| for (const auto &fragment : fragments) { | ||
| [nsAttributedString appendAttributedString:RCTNSAttributedStringFragmentFromFragment(fragment, placeholderImage)]; | ||
| } | ||
| [nsAttributedString endEditing]; | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why extracting this and changing the function signature?