diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm index e169755fe8e2..f1e1d564e404 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm @@ -27,7 +27,7 @@ @interface RCTTextLayoutManager (RCTParagraphComponentViewPrivate) -- (CGRect)drawingFrameForAttributedString:(facebook::react::AttributedString)attributedString +- (CGRect)drawingFrameForAttributedString:(const facebook::react::AttributedString &)attributedString paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes frame:(CGRect)frame containerFrame:(CGRect *)containerFrame; diff --git a/packages/react-native/ReactCommon/react/renderer/attributedstring/AttributedStringBox.cpp b/packages/react-native/ReactCommon/react/renderer/attributedstring/AttributedStringBox.cpp index f20096e571eb..e04d6f13f29e 100644 --- a/packages/react-native/ReactCommon/react/renderer/attributedstring/AttributedStringBox.cpp +++ b/packages/react-native/ReactCommon/react/renderer/attributedstring/AttributedStringBox.cpp @@ -23,6 +23,11 @@ AttributedStringBox::AttributedStringBox(const AttributedString& value) value_(std::make_shared(value)), opaquePointer_({}) {}; +AttributedStringBox::AttributedStringBox(AttributedString&& value) + : mode_(Mode::Value), + value_(std::make_shared(std::move(value))), + opaquePointer_({}) {}; + AttributedStringBox::AttributedStringBox(std::shared_ptr opaquePointer) : mode_(Mode::OpaquePointer), value_({}), diff --git a/packages/react-native/ReactCommon/react/renderer/attributedstring/AttributedStringBox.h b/packages/react-native/ReactCommon/react/renderer/attributedstring/AttributedStringBox.h index 077bf846c34d..549a7efcc986 100644 --- a/packages/react-native/ReactCommon/react/renderer/attributedstring/AttributedStringBox.h +++ b/packages/react-native/ReactCommon/react/renderer/attributedstring/AttributedStringBox.h @@ -37,6 +37,7 @@ class AttributedStringBox final { * Custom explicit constructors. */ explicit AttributedStringBox(const AttributedString &value); + explicit AttributedStringBox(AttributedString &&value); explicit AttributedStringBox(std::shared_ptr opaquePointer); /* diff --git a/packages/react-native/ReactCommon/react/renderer/attributedstring/tests/AttributedStringBoxTest.cpp b/packages/react-native/ReactCommon/react/renderer/attributedstring/tests/AttributedStringBoxTest.cpp index b6774b36f4d5..ddaa0edb5920 100644 --- a/packages/react-native/ReactCommon/react/renderer/attributedstring/tests/AttributedStringBoxTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/attributedstring/tests/AttributedStringBoxTest.cpp @@ -30,6 +30,18 @@ TEST(AttributedStringBoxTest, testValueConstructor) { EXPECT_EQ(attributedStringBox.getValue(), attributedString); } +TEST(AttributedStringBoxTest, testValueMoveConstructor) { + auto attributedString = AttributedString{}; + auto fragment = AttributedString::Fragment{}; + fragment.string = "test string"; + attributedString.appendFragment(std::move(fragment)); + auto expectedAttributedString = attributedString; + auto attributedStringBox = AttributedStringBox{std::move(attributedString)}; + + EXPECT_EQ(attributedStringBox.getMode(), AttributedStringBox::Mode::Value); + EXPECT_EQ(attributedStringBox.getValue(), expectedAttributedString); +} + TEST(AttributedStringBoxTest, testOpaquePointerConstructor) { auto string = std::make_shared("test string"); auto attributedStringBox = AttributedStringBox{string}; diff --git a/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphShadowNode.cpp index 17b14c439f16..40c120f98ff9 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphShadowNode.cpp @@ -88,7 +88,9 @@ const Content& ParagraphShadowNode::getContent( attributedString.setBaseTextAttributes(textAttributes); content_ = Content{ - attributedString, getConcreteProps().paragraphAttributes, attachments}; + std::move(attributedString), + getConcreteProps().paragraphAttributes, + std::move(attachments)}; return content_.value(); } @@ -264,7 +266,7 @@ Size ParagraphShadowNode::measureContent( auto size = textLayoutManager_ ->measure( - AttributedStringBox{content.attributedString}, + AttributedStringBox{std::move(content.attributedString)}, content.paragraphAttributes, textLayoutContext, layoutConstraints) @@ -282,7 +284,7 @@ Float ParagraphShadowNode::baseline( auto content = getContentWithMeasuredAttachments(layoutContext, layoutConstraints); - AttributedStringBox attributedStringBox{content.attributedString}; + AttributedStringBox attributedStringBox{std::move(content.attributedString)}; if constexpr (TextLayoutManagerExtended::supportsLineMeasurement()) { auto lines = @@ -346,7 +348,9 @@ void ParagraphShadowNode::layout(LayoutContext layoutContext) { .pointScaleFactor = layoutContext.pointScaleFactor, .surfaceId = getSurfaceId(), }; - AttributedStringBox attributedStringBox{content.attributedString}; + // The state update above already captured the attributed string and + // nothing below reads it, so the box can take it instead of copying it. + AttributedStringBox attributedStringBox{std::move(content.attributedString)}; if (getConcreteProps().onTextLayout) { if constexpr (TextLayoutManagerExtended::supportsLineMeasurement()) { diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.h b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.h index d30a3e3a4707..2cd7f1bce247 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.h +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.h @@ -29,7 +29,7 @@ using RCTTextLayoutFragmentEnumerationBlock = */ @interface RCTTextLayoutManager : NSObject -- (facebook::react::TextMeasurement)measureAttributedString:(facebook::react::AttributedString)attributedString +- (facebook::react::TextMeasurement)measureAttributedString:(const facebook::react::AttributedString &)attributedString paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes layoutContext:(facebook::react::TextLayoutContext)layoutContext layoutConstraints:(facebook::react::LayoutConstraints)layoutConstraints; @@ -39,23 +39,23 @@ using RCTTextLayoutFragmentEnumerationBlock = layoutContext:(facebook::react::TextLayoutContext)layoutContext layoutConstraints:(facebook::react::LayoutConstraints)layoutConstraints; -- (void)drawAttributedString:(facebook::react::AttributedString)attributedString +- (void)drawAttributedString:(const facebook::react::AttributedString &)attributedString paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes frame:(CGRect)frame drawHighlightPath:(void (^_Nullable)(UIBezierPath *highlightPath))block; -- (facebook::react::LinesMeasurements)getLinesForAttributedString:(facebook::react::AttributedString)attributedString - paragraphAttributes: - (facebook::react::ParagraphAttributes)paragraphAttributes - size:(CGSize)size; +- (facebook::react::LinesMeasurements) + getLinesForAttributedString:(const facebook::react::AttributedString &)attributedString + paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes + size:(CGSize)size; - (std::shared_ptr) - getEventEmitterWithAttributeString:(facebook::react::AttributedString)attributedString + getEventEmitterWithAttributeString:(const facebook::react::AttributedString &)attributedString paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes frame:(CGRect)frame atPoint:(CGPoint)point; -- (void)getRectWithAttributedString:(facebook::react::AttributedString)attributedString +- (void)getRectWithAttributedString:(const facebook::react::AttributedString &)attributedString paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes enumerateAttribute:(NSString *)enumerateAttribute frame:(CGRect)frame 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 33306db54922..3e93b60e64be 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 @@ -59,7 +59,7 @@ - (TextMeasurement)measureNSAttributedString:(NSAttributedString *)attributedStr return [self _measureTextStorage:textStorage paragraphAttributes:paragraphAttributes layoutContext:layoutContext]; } -- (TextMeasurement)measureAttributedString:(AttributedString)attributedString +- (TextMeasurement)measureAttributedString:(const AttributedString &)attributedString paragraphAttributes:(ParagraphAttributes)paragraphAttributes layoutContext:(TextLayoutContext)layoutContext layoutConstraints:(LayoutConstraints)layoutConstraints @@ -70,7 +70,7 @@ - (TextMeasurement)measureAttributedString:(AttributedString)attributedString layoutConstraints:layoutConstraints]; } -- (CGRect)drawingFrameForAttributedString:(AttributedString)attributedString +- (CGRect)drawingFrameForAttributedString:(const AttributedString &)attributedString paragraphAttributes:(ParagraphAttributes)paragraphAttributes frame:(CGRect)frame containerFrame:(CGRect *)containerFrame @@ -128,7 +128,7 @@ - (CGRect)drawingFrameForAttributedString:(AttributedString)attributedString return drawingFrame; } -- (void)drawAttributedString:(AttributedString)attributedString +- (void)drawAttributedString:(const AttributedString &)attributedString paragraphAttributes:(ParagraphAttributes)paragraphAttributes frame:(CGRect)frame drawHighlightPath:(void (^_Nullable)(UIBezierPath *highlightPath))block @@ -363,7 +363,7 @@ - (void)processTruncatedAttributedText:(NSTextStorage *)textStorage } } -- (LinesMeasurements)getLinesForAttributedString:(facebook::react::AttributedString)attributedString +- (LinesMeasurements)getLinesForAttributedString:(const facebook::react::AttributedString &)attributedString paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes size:(CGSize)size { @@ -467,7 +467,7 @@ - (NSTextStorage *)_textStorageAndLayoutManagerWithAttributesString:(NSAttribute return textStorage; } -- (std::shared_ptr)getEventEmitterWithAttributeString:(AttributedString)attributedString +- (std::shared_ptr)getEventEmitterWithAttributeString:(const AttributedString &)attributedString paragraphAttributes:(ParagraphAttributes)paragraphAttributes frame:(CGRect)frame atPoint:(CGPoint)point @@ -497,7 +497,7 @@ - (NSTextStorage *)_textStorageAndLayoutManagerWithAttributesString:(NSAttribute return nil; } -- (void)getRectWithAttributedString:(AttributedString)attributedString +- (void)getRectWithAttributedString:(const AttributedString &)attributedString paragraphAttributes:(ParagraphAttributes)paragraphAttributes enumerateAttribute:(NSString *)enumerateAttribute frame:(CGRect)frame @@ -539,7 +539,7 @@ - (void)getRectWithAttributedString:(AttributedString)attributedString #pragma mark - Private -- (NSAttributedString *)_nsAttributedStringFromAttributedString:(AttributedString)attributedString +- (NSAttributedString *)_nsAttributedStringFromAttributedString:(const AttributedString &)attributedString { auto sharedNSAttributedString = _cache.get(attributedString, [&]() { return wrapManagedObject(RCTNSAttributedStringFromAttributedString(attributedString)); diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/TextLayoutManager.mm b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/TextLayoutManager.mm index 82cad8fd2eed..bf4bc16c6542 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/TextLayoutManager.mm +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/TextLayoutManager.mm @@ -8,12 +8,23 @@ #import "TextLayoutManager.h" #import "RCTTextLayoutManager.h" +#import + #import #import #import namespace facebook::react { +// `ensurePlaceholderIfEmpty_DO_NOT_USE` returns a copy, so only call it for an empty string that needs one. +static std::optional placeholderIfEmpty(const AttributedString &attributedString) +{ + if (attributedString.isEmpty()) { + return ensurePlaceholderIfEmpty_DO_NOT_USE(attributedString); + } + return std::nullopt; +} + TextLayoutManager::TextLayoutManager(const std::shared_ptr & /*contextContainer*/) { nativeTextLayoutManager_ = wrapManagedObject([RCTTextLayoutManager new]); @@ -37,8 +48,10 @@ switch (attributedStringBox.getMode()) { case AttributedStringBox::Mode::Value: { - auto originalAtributedString = attributedStringBox.getValue(); - auto attributedString = ensurePlaceholderIfEmpty_DO_NOT_USE(originalAtributedString); + const auto &originalAttributedString = attributedStringBox.getValue(); + auto placeholderAttributedString = placeholderIfEmpty(originalAttributedString); + const auto &attributedString = + placeholderAttributedString ? *placeholderAttributedString : originalAttributedString; measurement = textMeasureCache_.get( {.attributedString = attributedString, @@ -60,7 +73,7 @@ // being used to represent empty string. iOS TextLayoutManager // should instead measure using `baseTextAttributes` of the // `AttributedString`. - if (originalAtributedString.isEmpty()) { + if (originalAttributedString.isEmpty()) { measurement.size.width = 0; } @@ -106,7 +119,9 @@ const Size &size) const { react_native_assert(attributedStringBox.getMode() == AttributedStringBox::Mode::Value); - auto attributedString = ensurePlaceholderIfEmpty_DO_NOT_USE(attributedStringBox.getValue()); + const auto &originalAttributedString = attributedStringBox.getValue(); + auto placeholderAttributedString = placeholderIfEmpty(originalAttributedString); + const auto &attributedString = placeholderAttributedString ? *placeholderAttributedString : originalAttributedString; RCTTextLayoutManager *textLayoutManager = (RCTTextLayoutManager *)unwrapManagedObject(nativeTextLayoutManager_); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api index e1915564eeed..ed50fb2dbcc4 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api @@ -1366,6 +1366,7 @@ class facebook::react::AttributedStringBox { public AttributedStringBox(); public AttributedStringBox(const facebook::react::AttributedString& value); public AttributedStringBox(const facebook::react::AttributedStringBox& other) = default; + public AttributedStringBox(facebook::react::AttributedString&& value); public AttributedStringBox(facebook::react::AttributedStringBox&& other) noexcept; public AttributedStringBox(std::shared_ptr opaquePointer); public const facebook::react::AttributedString& getValue() const; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api index 11c7e6873789..38d1cb6256d8 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api @@ -1939,12 +1939,12 @@ interface RCTTextInputComponentView : public RCTViewComponentView { } interface RCTTextLayoutManager : public NSObject { - public virtual facebook::react::LinesMeasurements getLinesForAttributedString:paragraphAttributes:size:(facebook::react::AttributedString attributedString, facebook::react::ParagraphAttributes paragraphAttributes, CGSize size); - public virtual facebook::react::TextMeasurement measureAttributedString:paragraphAttributes:layoutContext:layoutConstraints:(facebook::react::AttributedString attributedString, facebook::react::ParagraphAttributes paragraphAttributes, facebook::react::TextLayoutContext layoutContext, facebook::react::LayoutConstraints layoutConstraints); + public virtual facebook::react::LinesMeasurements getLinesForAttributedString:paragraphAttributes:size:(const facebook::react::AttributedString& attributedString, facebook::react::ParagraphAttributes paragraphAttributes, CGSize size); + public virtual facebook::react::TextMeasurement measureAttributedString:paragraphAttributes:layoutContext:layoutConstraints:(const facebook::react::AttributedString& attributedString, facebook::react::ParagraphAttributes paragraphAttributes, facebook::react::TextLayoutContext layoutContext, facebook::react::LayoutConstraints layoutConstraints); public virtual facebook::react::TextMeasurement measureNSAttributedString:paragraphAttributes:layoutContext:layoutConstraints:(NSAttributedString* attributedString, facebook::react::ParagraphAttributes paragraphAttributes, facebook::react::TextLayoutContext layoutContext, facebook::react::LayoutConstraints layoutConstraints); - public virtual std::shared_ptr getEventEmitterWithAttributeString:paragraphAttributes:frame:atPoint:(facebook::react::AttributedString attributedString, facebook::react::ParagraphAttributes paragraphAttributes, CGRect frame, CGPoint point); - public virtual void drawAttributedString:paragraphAttributes:frame:drawHighlightPath:(facebook::react::AttributedString attributedString, facebook::react::ParagraphAttributes paragraphAttributes, CGRect frame, void(^)(UIBezierPath* highlightPath) block); - public virtual void getRectWithAttributedString:paragraphAttributes:enumerateAttribute:frame:usingBlock:(facebook::react::AttributedString attributedString, facebook::react::ParagraphAttributes paragraphAttributes, NSString* enumerateAttribute, CGRect frame, RCTTextLayoutFragmentEnumerationBlock block); + public virtual std::shared_ptr getEventEmitterWithAttributeString:paragraphAttributes:frame:atPoint:(const facebook::react::AttributedString& attributedString, facebook::react::ParagraphAttributes paragraphAttributes, CGRect frame, CGPoint point); + public virtual void drawAttributedString:paragraphAttributes:frame:drawHighlightPath:(const facebook::react::AttributedString& attributedString, facebook::react::ParagraphAttributes paragraphAttributes, CGRect frame, void(^)(UIBezierPath* highlightPath) block); + public virtual void getRectWithAttributedString:paragraphAttributes:enumerateAttribute:frame:usingBlock:(const facebook::react::AttributedString& attributedString, facebook::react::ParagraphAttributes paragraphAttributes, NSString* enumerateAttribute, CGRect frame, RCTTextLayoutFragmentEnumerationBlock block); } interface RCTTextSelection : public NSObject { @@ -3963,6 +3963,7 @@ class facebook::react::AttributedStringBox { public AttributedStringBox(); public AttributedStringBox(const facebook::react::AttributedString& value); public AttributedStringBox(const facebook::react::AttributedStringBox& other) = default; + public AttributedStringBox(facebook::react::AttributedString&& value); public AttributedStringBox(facebook::react::AttributedStringBox&& other) noexcept; public AttributedStringBox(std::shared_ptr opaquePointer); public const facebook::react::AttributedString& getValue() const; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api index bcf1840858e3..0c37d5e6a5f9 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api @@ -709,6 +709,7 @@ class facebook::react::AttributedStringBox { public AttributedStringBox(); public AttributedStringBox(const facebook::react::AttributedString& value); public AttributedStringBox(const facebook::react::AttributedStringBox& other) = default; + public AttributedStringBox(facebook::react::AttributedString&& value); public AttributedStringBox(facebook::react::AttributedStringBox&& other) noexcept; public AttributedStringBox(std::shared_ptr opaquePointer); public const facebook::react::AttributedString& getValue() const;