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 d09c6f760fd1..aafd3d44f2d5 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm @@ -122,8 +122,11 @@ - (void)drawRect:(CGRect)rect return; } - auto textLayoutManager = _state->getData().paragraphLayoutManager.getTextLayoutManager(); - auto nsTextStorage = _state->getData().paragraphLayoutManager.getHostTextStorage(); + auto textLayoutManager = _state->getData().layoutManager.lock(); + + if (!textLayoutManager) { + return; + } RCTTextLayoutManager *nativeTextLayoutManager = (RCTTextLayoutManager *)unwrapManagedObject(textLayoutManager->getNativeTextLayoutManager()); @@ -133,7 +136,6 @@ - (void)drawRect:(CGRect)rect [nativeTextLayoutManager drawAttributedString:_state->getData().attributedString paragraphAttributes:_paragraphAttributes frame:frame - textStorage:unwrapManagedObject(nsTextStorage) drawHighlightPath:^(UIBezierPath *highlightPath) { if (highlightPath) { if (!self->_highlightLayer) { @@ -179,15 +181,18 @@ - (NSArray *)accessibilityElements auto &data = _state->getData(); if (![_accessibilityProvider isUpToDate:data.attributedString]) { - auto textLayoutManager = data.paragraphLayoutManager.getTextLayoutManager(); - RCTTextLayoutManager *nativeTextLayoutManager = - (RCTTextLayoutManager *)unwrapManagedObject(textLayoutManager->getNativeTextLayoutManager()); - CGRect frame = RCTCGRectFromRect(_layoutMetrics.getContentFrame()); - _accessibilityProvider = [[RCTParagraphComponentAccessibilityProvider alloc] initWithString:data.attributedString - layoutManager:nativeTextLayoutManager - paragraphAttributes:data.paragraphAttributes - frame:frame - view:self]; + auto textLayoutManager = data.layoutManager.lock(); + if (textLayoutManager) { + RCTTextLayoutManager *nativeTextLayoutManager = + (RCTTextLayoutManager *)unwrapManagedObject(textLayoutManager->getNativeTextLayoutManager()); + CGRect frame = RCTCGRectFromRect(_layoutMetrics.getContentFrame()); + _accessibilityProvider = + [[RCTParagraphComponentAccessibilityProvider alloc] initWithString:data.attributedString + layoutManager:nativeTextLayoutManager + paragraphAttributes:data.paragraphAttributes + frame:frame + view:self]; + } } return _accessibilityProvider.accessibilityElements; @@ -206,7 +211,11 @@ - (SharedTouchEventEmitter)touchEventEmitterAtPoint:(CGPoint)point return _eventEmitter; } - auto textLayoutManager = _state->getData().paragraphLayoutManager.getTextLayoutManager(); + auto textLayoutManager = _state->getData().layoutManager.lock(); + + if (!textLayoutManager) { + return _eventEmitter; + } RCTTextLayoutManager *nativeTextLayoutManager = (RCTTextLayoutManager *)unwrapManagedObject(textLayoutManager->getNativeTextLayoutManager()); diff --git a/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphLayoutManager.cpp b/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphLayoutManager.cpp deleted file mode 100644 index ea615b500ab4..000000000000 --- a/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphLayoutManager.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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. - */ - -#include "ParagraphLayoutManager.h" -#include -#include - -namespace facebook::react { - -TextMeasurement ParagraphLayoutManager::measure( - const AttributedString& attributedString, - const ParagraphAttributes& paragraphAttributes, - const TextLayoutContext& layoutContext, - LayoutConstraints layoutConstraints) const { - if (CoreFeatures::cacheLastTextMeasurement) { - bool shouldMeasure = shouldMeasureString( - attributedString, paragraphAttributes, layoutConstraints); - - if (shouldMeasure) { - cachedTextMeasurement_ = textLayoutManager_->measure( - AttributedStringBox(attributedString), - paragraphAttributes, - layoutContext, - layoutConstraints, - hostTextStorage_); - lastAvailableWidth_ = layoutConstraints.maximumSize.width; - } - - return cachedTextMeasurement_; - } else { - return textLayoutManager_->measure( - AttributedStringBox(attributedString), - paragraphAttributes, - layoutContext, - layoutConstraints, - nullptr); - } -} - -bool ParagraphLayoutManager::shouldMeasureString( - const AttributedString& attributedString, - const ParagraphAttributes& paragraphAttributes, - LayoutConstraints layoutConstraints) const { - size_t newParagraphInputHash = - hash_combine(attributedString, paragraphAttributes); - - if (newParagraphInputHash != paragraphInputHash_) { - // AttributedString or ParagraphAttributes have changed. - // Must create new host text storage and trigger measure. - hostTextStorage_ = textLayoutManager_->getHostTextStorage( - attributedString, paragraphAttributes, layoutConstraints); - paragraphInputHash_ = newParagraphInputHash; - - return true; // Must measure again. - } - - // Detect the case when available width for Paragraph meaningfully changes. - // This is to prevent unnecessary re-creation of NSTextStorage on iOS. - // On Android, this is no-op. - bool hasMaximumSizeChanged = - layoutConstraints.maximumSize.width != lastAvailableWidth_; - Float threshold = 0.01f; - bool doesMaximumSizeMatchLastMeasurement = - std::abs( - layoutConstraints.maximumSize.width - - cachedTextMeasurement_.size.width) < threshold; - if (hasMaximumSizeChanged && !doesMaximumSizeMatchLastMeasurement) { - hostTextStorage_ = textLayoutManager_->getHostTextStorage( - attributedString, paragraphAttributes, layoutConstraints); - return true; - } - return false; -} - -LinesMeasurements ParagraphLayoutManager::measureLines( - const AttributedString& attributedString, - const ParagraphAttributes& paragraphAttributes, - Size size) const { - return textLayoutManager_->measureLines( - attributedString, paragraphAttributes, size); -} - -void ParagraphLayoutManager::setTextLayoutManager( - std::shared_ptr textLayoutManager) const { - textLayoutManager_ = std::move(textLayoutManager); -} - -std::shared_ptr -ParagraphLayoutManager::getTextLayoutManager() const { - return textLayoutManager_; -} - -std::shared_ptr ParagraphLayoutManager::getHostTextStorage() const { - return hostTextStorage_; -} -} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphLayoutManager.h b/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphLayoutManager.h deleted file mode 100644 index 43bd2936abaa..000000000000 --- a/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphLayoutManager.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include -#include -#include -#include -#include - -namespace facebook::react { - -/* - * Serves as a middle man between `ParagraphShadowNode` and `TextLayoutManager`. - * On iOS, caches `NSTextStorage` for individual `ParagraphShadowNode` to make - * sure only one `NSTextStorage` is created for every string. `NSTextStorage` - * can be re created on native views layer but it is expensive. On Android, this - * class does not cache anything. - */ -class ParagraphLayoutManager { - public: - TextMeasurement measure( - const AttributedString& attributedString, - const ParagraphAttributes& paragraphAttributes, - const TextLayoutContext& layoutContext, - LayoutConstraints layoutConstraints) const; - - LinesMeasurements measureLines( - const AttributedString& attributedString, - const ParagraphAttributes& paragraphAttributes, - Size size) const; - - void setTextLayoutManager( - std::shared_ptr textLayoutManager) const; - - /* - * Returns an opaque pointer to platform-specific `TextLayoutManager`. - * Is used on a native views layer to delegate text rendering to the manager. - */ - std::shared_ptr getTextLayoutManager() const; - - /* - * Returns opaque shared_ptr holding `NSTextStorage`. - * May be nullptr. - * Is used on a native views layer to prevent `NSTextStorage` from being - * created twice. - */ - std::shared_ptr getHostTextStorage() const; - - private: - std::shared_ptr mutable textLayoutManager_{}; - - /* - * Stores opaque pointer to `NSTextStorage` on iOS. nullptr on Android. - * TODO: In the future, we may want to cache Android's text storage. - */ - std::shared_ptr mutable hostTextStorage_{}; - - /* - * Hash of AttributedString and ParagraphAttributes last used to - * measure. Result of that measure is stored in cachedTextMeasurement_. - * The available width defined for the measurement is stored in - * lastAvailableWidth_. - */ - size_t mutable paragraphInputHash_{}; - - /* The width Yoga set as maximum width. - * Yoga calls measure twice with two - * different maximum width. One of available space. - * The other one is exact space needed for the string. - * This happens when node is dirtied but its size is not affected. - * To deal with this inefficiency, we cache `TextMeasurement` for each - * `ParagraphShadowNode`. If Yoga tries to re-measure with available width - * or exact width, we provide it with the cached value. - */ - Float mutable lastAvailableWidth_{}; - TextMeasurement mutable cachedTextMeasurement_{}; - - /* - * Checks whether the inputs into text measurement meaningfully affect - * text measurement result. Returns true if inputs have changed and measure is - * needed. - */ - bool shouldMeasureString( - const AttributedString& attributedString, - const ParagraphAttributes& paragraphAttributes, - LayoutConstraints layoutConstraints) const; -}; -} // namespace facebook::react 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 5d7d651dd013..0f78030f0709 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphShadowNode.cpp @@ -110,8 +110,7 @@ Content ParagraphShadowNode::getContentWithMeasuredAttachments( void ParagraphShadowNode::setTextLayoutManager( std::shared_ptr textLayoutManager) { ensureUnsealed(); - getStateData().paragraphLayoutManager.setTextLayoutManager( - std::move(textLayoutManager)); + textLayoutManager_ = std::move(textLayoutManager); } void ParagraphShadowNode::updateStateIfNeeded(const Content& content) { @@ -119,7 +118,7 @@ void ParagraphShadowNode::updateStateIfNeeded(const Content& content) { auto& state = getStateData(); - react_native_assert(state.paragraphLayoutManager.getTextLayoutManager()); + react_native_assert(textLayoutManager_); if (state.attributedString == content.attributedString) { return; @@ -128,7 +127,7 @@ void ParagraphShadowNode::updateStateIfNeeded(const Content& content) { setStateData(ParagraphState{ content.attributedString, content.paragraphAttributes, - state.paragraphLayoutManager}); + textLayoutManager_}); } #pragma mark - LayoutableShadowNode @@ -154,10 +153,9 @@ Size ParagraphShadowNode::measureContent( TextLayoutContext textLayoutContext{}; textLayoutContext.pointScaleFactor = layoutContext.pointScaleFactor; - return getStateData() - .paragraphLayoutManager - .measure( - attributedString, + return textLayoutManager_ + ->measure( + AttributedStringBox{attributedString}, content.paragraphAttributes, textLayoutContext, layoutConstraints) @@ -179,14 +177,14 @@ void ParagraphShadowNode::layout(LayoutContext layoutContext) { TextLayoutContext textLayoutContext{}; textLayoutContext.pointScaleFactor = layoutContext.pointScaleFactor; - auto measurement = getStateData().paragraphLayoutManager.measure( - content.attributedString, + auto measurement = textLayoutManager_->measure( + AttributedStringBox{content.attributedString}, content.paragraphAttributes, textLayoutContext, layoutConstraints); if (getConcreteProps().onTextLayout) { - auto linesMeasurements = getStateData().paragraphLayoutManager.measureLines( + auto linesMeasurements = textLayoutManager_->measureLines( content.attributedString, content.paragraphAttributes, measurement.size); diff --git a/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphShadowNode.h b/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphShadowNode.h index a5f8eeb93554..e0df8ee24376 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphShadowNode.h +++ b/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphShadowNode.h @@ -97,6 +97,8 @@ class ParagraphShadowNode final : public ConcreteViewShadowNode< */ void updateStateIfNeeded(const Content& content); + std::shared_ptr textLayoutManager_; + /* * Cached content of the subtree started from the node. */ diff --git a/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphState.h b/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphState.h index e448896e84f9..5ed723f3b1cd 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphState.h +++ b/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphState.h @@ -10,7 +10,7 @@ #include #include #include -#include +#include #ifdef ANDROID #include @@ -32,7 +32,8 @@ constexpr static MapBuffer::Key TX_STATE_KEY_MOST_RECENT_EVENT_COUNT = 3; * State for component. * Represents what to render and how to render. */ -struct ParagraphState { +class ParagraphState final { + public: /* * All content of component represented as an `AttributedString`. */ @@ -45,22 +46,22 @@ struct ParagraphState { ParagraphAttributes paragraphAttributes; /* - * `ParagraphLayoutManager` provides a connection to platform-specific + * `TextLayoutManager` provides a connection to platform-specific * text rendering infrastructure which is capable to render the * `AttributedString`. * This is not on every platform. This is not used on Android, but is * used on the iOS mounting layer. */ - ParagraphLayoutManager paragraphLayoutManager; + std::weak_ptr layoutManager; #ifdef ANDROID ParagraphState( - const AttributedString& attributedString, - const ParagraphAttributes& paragraphAttributes, - const ParagraphLayoutManager& paragraphLayoutManager) + AttributedString const& attributedString, + ParagraphAttributes const& paragraphAttributes, + std::weak_ptr const& layoutManager) : attributedString(attributedString), paragraphAttributes(paragraphAttributes), - paragraphLayoutManager(paragraphLayoutManager) {} + layoutManager(layoutManager) {} ParagraphState() = default; ParagraphState( const ParagraphState& previousState, diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput/AndroidTextInputShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput/AndroidTextInputShadowNode.cpp index 3e9e0ad9ed17..57bd7051792b 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput/AndroidTextInputShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput/AndroidTextInputShadowNode.cpp @@ -191,8 +191,7 @@ Size AndroidTextInputShadowNode::measureContent( AttributedStringBox{attributedString}, getConcreteProps().paragraphAttributes, textLayoutContext, - layoutConstraints, - nullptr) + layoutConstraints) .size; } diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/TextInputShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/TextInputShadowNode.cpp index dad61c26f2fd..731a46e65752 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/TextInputShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/TextInputShadowNode.cpp @@ -117,8 +117,7 @@ Size TextInputShadowNode::measureContent( attributedStringBoxToMeasure(layoutContext), getConcreteProps().getEffectiveParagraphAttributes(), textLayoutContext, - layoutConstraints, - nullptr) + layoutConstraints) .size; } diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp index 7a34d992dcd6..325fe670e13c 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp @@ -141,9 +141,6 @@ Scheduler::Scheduler( "react_fabric:remove_outstanding_surfaces_on_destruction_ios"); #endif - CoreFeatures::cacheLastTextMeasurement = - reactNativeConfig_->getBool("react_fabric:enable_text_measure_cache"); - CoreFeatures::enableGranularShadowTreeStateReconciliation = reactNativeConfig_->getBool( "react_fabric:enable_granular_shadow_tree_state_reconciliation"); diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/android/react/renderer/textlayoutmanager/TextLayoutManager.cpp b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/android/react/renderer/textlayoutmanager/TextLayoutManager.cpp index c5accc2932e9..dd5a09e8120b 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/android/react/renderer/textlayoutmanager/TextLayoutManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/android/react/renderer/textlayoutmanager/TextLayoutManager.cpp @@ -16,7 +16,6 @@ #include #include #include -#include using namespace facebook::jni; @@ -159,10 +158,7 @@ Size measureAndroidComponentMapBuffer( TextLayoutManager::TextLayoutManager( const ContextContainer::Shared& contextContainer) : contextContainer_(contextContainer), - measureCache_( - CoreFeatures::cacheLastTextMeasurement - ? 8096 - : kSimpleThreadSafeCacheSizeCap) {} + measureCache_(kSimpleThreadSafeCacheSizeCap) {} void* TextLayoutManager::getNativeTextLayoutManager() const { return self_; @@ -172,8 +168,7 @@ TextMeasurement TextLayoutManager::measure( const AttributedStringBox& attributedStringBox, const ParagraphAttributes& paragraphAttributes, const TextLayoutContext& layoutContext, - LayoutConstraints layoutConstraints, - std::shared_ptr /* hostTextStorage */) const { + LayoutConstraints layoutConstraints) const { auto& attributedString = attributedStringBox.getValue(); auto measurement = measureCache_.get( @@ -197,12 +192,6 @@ TextMeasurement TextLayoutManager::measure( measurement.size = layoutConstraints.clamp(measurement.size); return measurement; } -std::shared_ptr TextLayoutManager::getHostTextStorage( - const AttributedString& /* attributedStringBox */, - const ParagraphAttributes& /* paragraphAttributes */, - LayoutConstraints /* layoutConstraints */) const { - return nullptr; -} TextMeasurement TextLayoutManager::measureCachedSpannableById( int64_t cacheId, diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/android/react/renderer/textlayoutmanager/TextLayoutManager.h b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/android/react/renderer/textlayoutmanager/TextLayoutManager.h index 9f71788b39a7..39fe61e68e79 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/android/react/renderer/textlayoutmanager/TextLayoutManager.h +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/android/react/renderer/textlayoutmanager/TextLayoutManager.h @@ -47,12 +47,6 @@ class TextLayoutManager { const AttributedStringBox& attributedStringBox, const ParagraphAttributes& paragraphAttributes, const TextLayoutContext& layoutContext, - LayoutConstraints layoutConstraints, - std::shared_ptr /* hostTextStorage */) const; - - std::shared_ptr getHostTextStorage( - const AttributedString& attributedString, - const ParagraphAttributes& paragraphAttributes, LayoutConstraints layoutConstraints) const; /** diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/cxx/TextLayoutManager.cpp b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/cxx/TextLayoutManager.cpp index b3e49dfed3a8..294e886d3c9c 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/cxx/TextLayoutManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/cxx/TextLayoutManager.cpp @@ -17,8 +17,7 @@ TextMeasurement TextLayoutManager::measure( AttributedStringBox attributedStringBox, ParagraphAttributes paragraphAttributes, const TextLayoutContext& /*layoutContext*/, - LayoutConstraints layoutConstraints, - std::shared_ptr) const { + LayoutConstraints layoutConstraints) const { TextMeasurement::Attachments attachments; for (const auto& fragment : attributedStringBox.getValue().getFragments()) { if (fragment.isAttachment()) { @@ -43,11 +42,4 @@ LinesMeasurements TextLayoutManager::measureLines( return {}; }; -std::shared_ptr TextLayoutManager::getHostTextStorage( - AttributedString attributedString, - ParagraphAttributes paragraphAttributes, - LayoutConstraints layoutConstraints) const { - return nullptr; -} - } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/cxx/TextLayoutManager.h b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/cxx/TextLayoutManager.h index 0d323241190f..4b5eb7d1ff46 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/cxx/TextLayoutManager.h +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/cxx/TextLayoutManager.h @@ -39,8 +39,7 @@ class TextLayoutManager { AttributedStringBox attributedStringBox, ParagraphAttributes paragraphAttributes, const TextLayoutContext& layoutContext, - LayoutConstraints layoutConstraints, - std::shared_ptr) const; + LayoutConstraints layoutConstraints) const; /** * Measures an AttributedString on the platform, as identified by some @@ -65,11 +64,6 @@ class TextLayoutManager { * Is used on a native views layer to delegate text rendering to the manager. */ void* getNativeTextLayoutManager() const; - - virtual std::shared_ptr getHostTextStorage( - AttributedString attributedStringBox, - ParagraphAttributes paragraphAttributes, - LayoutConstraints layoutConstraints) const; }; } // namespace facebook::react 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 41809ce12ae3..7ef23cc308c3 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 @@ -28,22 +28,15 @@ using RCTTextLayoutFragmentEnumerationBlock = - (facebook::react::TextMeasurement)measureAttributedString:(facebook::react::AttributedString)attributedString paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes - layoutConstraints:(facebook::react::LayoutConstraints)layoutConstraints - textStorage:(NSTextStorage *_Nullable)textStorage; + layoutConstraints:(facebook::react::LayoutConstraints)layoutConstraints; - (facebook::react::TextMeasurement)measureNSAttributedString:(NSAttributedString *)attributedString paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes - layoutConstraints:(facebook::react::LayoutConstraints)layoutConstraints - textStorage:(NSTextStorage *_Nullable)textStorage; - -- (NSTextStorage *)textStorageForAttributesString:(facebook::react::AttributedString)attributedString - paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes - size:(CGSize)size; + layoutConstraints:(facebook::react::LayoutConstraints)layoutConstraints; - (void)drawAttributedString:(facebook::react::AttributedString)attributedString paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes frame:(CGRect)frame - textStorage:(NSTextStorage *_Nullable)textStorage drawHighlightPath:(void (^_Nullable)(UIBezierPath *highlightPath))block; - (facebook::react::LinesMeasurements)getLinesForAttributedString:(facebook::react::AttributedString)attributedString 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 1b917150074a..e3a4143d4215 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 @@ -37,7 +37,6 @@ static NSLineBreakMode RCTNSLineBreakModeFromEllipsizeMode(EllipsizeMode ellipsi - (TextMeasurement)measureNSAttributedString:(NSAttributedString *)attributedString paragraphAttributes:(ParagraphAttributes)paragraphAttributes layoutConstraints:(LayoutConstraints)layoutConstraints - textStorage:(NSTextStorage *_Nullable)textStorage { if (attributedString.length == 0) { // This is not really an optimization because that should be checked much earlier on the call stack. @@ -48,11 +47,9 @@ - (TextMeasurement)measureNSAttributedString:(NSAttributedString *)attributedStr CGSize maximumSize = CGSize{layoutConstraints.maximumSize.width, CGFLOAT_MAX}; - if (!textStorage) { - textStorage = [self _textStorageForNSAttributesString:attributedString - paragraphAttributes:paragraphAttributes - size:maximumSize]; - } + NSTextStorage *textStorage = [self _textStorageAndLayoutManagerWithAttributesString:attributedString + paragraphAttributes:paragraphAttributes + size:maximumSize]; return [self _measureTextStorage:textStorage]; } @@ -60,51 +57,23 @@ - (TextMeasurement)measureNSAttributedString:(NSAttributedString *)attributedStr - (TextMeasurement)measureAttributedString:(AttributedString)attributedString paragraphAttributes:(ParagraphAttributes)paragraphAttributes layoutConstraints:(LayoutConstraints)layoutConstraints - textStorage:(NSTextStorage *_Nullable)textStorage { - if (textStorage) { - return [self _measureTextStorage:textStorage]; - } else { - return [self measureNSAttributedString:[self _nsAttributedStringFromAttributedString:attributedString] - paragraphAttributes:paragraphAttributes - layoutConstraints:layoutConstraints - textStorage:nil]; - } + return [self measureNSAttributedString:[self _nsAttributedStringFromAttributedString:attributedString] + paragraphAttributes:paragraphAttributes + layoutConstraints:layoutConstraints]; } - (void)drawAttributedString:(AttributedString)attributedString paragraphAttributes:(ParagraphAttributes)paragraphAttributes frame:(CGRect)frame - textStorage:(NSTextStorage *_Nullable)textStorage drawHighlightPath:(void (^_Nullable)(UIBezierPath *highlightPath))block { - BOOL createdStorageForFrame = NO; - - if (!textStorage) { - textStorage = [self textStorageForAttributesString:attributedString + NSTextStorage *textStorage = [self + _textStorageAndLayoutManagerWithAttributesString:[self _nsAttributedStringFromAttributedString:attributedString] paragraphAttributes:paragraphAttributes size:frame.size]; - createdStorageForFrame = YES; - } - NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject; NSTextContainer *textContainer = layoutManager.textContainers.firstObject; - CGPoint origin = frame.origin; - - if (!createdStorageForFrame) { - CGRect rect = [layoutManager usedRectForTextContainer:textContainer]; - static auto threshold = 1.0 / RCTScreenScale() + 0.01; // Size of a pixel plus some small threshold. - - // `rect`'s width is stored in double precesion. - // `frame`'s width is also in double precesion but was stored as float in Yoga previously, precesion was lost. - if (std::abs(RCTCeilPixelValue(rect.size.width) - frame.size.width) < threshold) { - // `textStorage` passed to this method was used to calculate size of frame. If that's the case, it's - // width is the same as frame's width. Origin must be adjusted, otherwise glyhps will be painted in wrong - // place. - // We could create new `NSTextStorage` for the specific frame, but that is expensive. - origin.x -= RCTCeilPixelValue(rect.origin.x); - } - } #if TARGET_OS_MACCATALYST CGContextRef context = UIGraphicsGetCurrentContext(); @@ -113,8 +82,8 @@ - (void)drawAttributedString:(AttributedString)attributedString #endif NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; - [layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:origin]; - [layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:origin]; + [layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:frame.origin]; + [layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:frame.origin]; #if TARGET_OS_MACCATALYST CGContextRestoreGState(context); @@ -153,13 +122,14 @@ - (void)drawAttributedString:(AttributedString)attributedString } } -- (LinesMeasurements)getLinesForAttributedString:(AttributedString)attributedString - paragraphAttributes:(ParagraphAttributes)paragraphAttributes +- (LinesMeasurements)getLinesForAttributedString:(facebook::react::AttributedString)attributedString + paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes size:(CGSize)size { - NSTextStorage *textStorage = [self textStorageForAttributesString:attributedString - paragraphAttributes:paragraphAttributes - size:size]; + NSTextStorage *textStorage = [self + _textStorageAndLayoutManagerWithAttributesString:[self _nsAttributedStringFromAttributedString:attributedString] + paragraphAttributes:paragraphAttributes + size:size]; NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject; NSTextContainer *textContainer = layoutManager.textContainers.firstObject; @@ -197,13 +167,33 @@ - (LinesMeasurements)getLinesForAttributedString:(AttributedString)attributedStr return paragraphLines; } -- (NSTextStorage *)textStorageForAttributesString:(AttributedString)attributedString - paragraphAttributes:(ParagraphAttributes)paragraphAttributes - size:(CGSize)size +- (NSTextStorage *)_textStorageAndLayoutManagerWithAttributesString:(NSAttributedString *)attributedString + paragraphAttributes:(ParagraphAttributes)paragraphAttributes + size:(CGSize)size { - return [self _textStorageForNSAttributesString:[self _nsAttributedStringFromAttributedString:attributedString] - paragraphAttributes:paragraphAttributes - size:size]; + NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:size]; + + textContainer.lineFragmentPadding = 0.0; // Note, the default value is 5. + textContainer.lineBreakMode = paragraphAttributes.maximumNumberOfLines > 0 + ? RCTNSLineBreakModeFromEllipsizeMode(paragraphAttributes.ellipsizeMode) + : NSLineBreakByClipping; + textContainer.maximumNumberOfLines = paragraphAttributes.maximumNumberOfLines; + + NSLayoutManager *layoutManager = [NSLayoutManager new]; + layoutManager.usesFontLeading = NO; + [layoutManager addTextContainer:textContainer]; + + NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString]; + + [textStorage addLayoutManager:layoutManager]; + + if (paragraphAttributes.adjustsFontSizeToFit) { + CGFloat minimumFontSize = !isnan(paragraphAttributes.minimumFontSize) ? paragraphAttributes.minimumFontSize : 4.0; + CGFloat maximumFontSize = !isnan(paragraphAttributes.maximumFontSize) ? paragraphAttributes.maximumFontSize : 96.0; + [textStorage scaleFontSizeToFitSize:size minimumFontSize:minimumFontSize maximumFontSize:maximumFontSize]; + } + + return textStorage; } - (SharedEventEmitter)getEventEmitterWithAttributeString:(AttributedString)attributedString @@ -211,9 +201,10 @@ - (SharedEventEmitter)getEventEmitterWithAttributeString:(AttributedString)attri frame:(CGRect)frame atPoint:(CGPoint)point { - NSTextStorage *textStorage = [self textStorageForAttributesString:attributedString - paragraphAttributes:paragraphAttributes - size:frame.size]; + NSTextStorage *textStorage = [self + _textStorageAndLayoutManagerWithAttributesString:[self _nsAttributedStringFromAttributedString:attributedString] + paragraphAttributes:paragraphAttributes + size:frame.size]; NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject; NSTextContainer *textContainer = layoutManager.textContainers.firstObject; @@ -242,9 +233,10 @@ - (void)getRectWithAttributedString:(AttributedString)attributedString frame:(CGRect)frame usingBlock:(RCTTextLayoutFragmentEnumerationBlock)block { - NSTextStorage *textStorage = [self textStorageForAttributesString:attributedString - paragraphAttributes:paragraphAttributes - size:frame.size]; + NSTextStorage *textStorage = [self + _textStorageAndLayoutManagerWithAttributesString:[self _nsAttributedStringFromAttributedString:attributedString] + paragraphAttributes:paragraphAttributes + size:frame.size]; NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject; NSTextContainer *textContainer = layoutManager.textContainers.firstObject; diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/TextLayoutManager.h b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/TextLayoutManager.h index cfa7abc53eab..30fd816eb59e 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/TextLayoutManager.h +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/TextLayoutManager.h @@ -34,8 +34,7 @@ class TextLayoutManager { AttributedStringBox attributedStringBox, ParagraphAttributes paragraphAttributes, const TextLayoutContext& layoutContext, - LayoutConstraints layoutConstraints, - std::shared_ptr hostTextStorage) const; + LayoutConstraints layoutConstraints) const; /* * Measures lines of `attributedString` using native text rendering @@ -46,11 +45,6 @@ class TextLayoutManager { ParagraphAttributes paragraphAttributes, Size size) const; - std::shared_ptr getHostTextStorage( - AttributedString attributedString, - ParagraphAttributes paragraphAttributes, - LayoutConstraints layoutConstraints) const; - /* * Returns an opaque pointer to platform-specific TextLayoutManager. * Is used on a native views layer to delegate text rendering to the manager. 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 dcce90583805..4d9b0f3f89f8 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 @@ -24,32 +24,13 @@ return self_; } -std::shared_ptr TextLayoutManager::getHostTextStorage( - AttributedString attributedString, - ParagraphAttributes paragraphAttributes, - LayoutConstraints layoutConstraints) const -{ - RCTTextLayoutManager *textLayoutManager = (RCTTextLayoutManager *)unwrapManagedObject(self_); - CGSize maximumSize = CGSize{layoutConstraints.maximumSize.width, CGFLOAT_MAX}; - - NSTextStorage *textStorage = [textLayoutManager textStorageForAttributesString:attributedString - paragraphAttributes:paragraphAttributes - size:maximumSize]; - return wrapManagedObject(textStorage); -} - TextMeasurement TextLayoutManager::measure( AttributedStringBox attributedStringBox, ParagraphAttributes paragraphAttributes, const TextLayoutContext &layoutContext, - LayoutConstraints layoutConstraints, - std::shared_ptr hostTextStorage) const + LayoutConstraints layoutConstraints) const { RCTTextLayoutManager *textLayoutManager = (RCTTextLayoutManager *)unwrapManagedObject(self_); - NSTextStorage *textStorage; - if (hostTextStorage) { - textStorage = unwrapManagedObject(hostTextStorage); - } auto measurement = TextMeasurement{}; @@ -66,8 +47,7 @@ auto measurement = [textLayoutManager measureAttributedString:attributedString paragraphAttributes:paragraphAttributes - layoutConstraints:layoutConstraints - textStorage:textStorage]; + layoutConstraints:layoutConstraints]; if (telemetry) { telemetry->didMeasureText(); @@ -89,8 +69,7 @@ measurement = [textLayoutManager measureNSAttributedString:nsAttributedString paragraphAttributes:paragraphAttributes - layoutConstraints:layoutConstraints - textStorage:textStorage]; + layoutConstraints:layoutConstraints]; if (telemetry) { telemetry->didMeasureText(); diff --git a/packages/react-native/ReactCommon/react/utils/CoreFeatures.cpp b/packages/react-native/ReactCommon/react/utils/CoreFeatures.cpp index f29d262bd1fa..dd651affc981 100644 --- a/packages/react-native/ReactCommon/react/utils/CoreFeatures.cpp +++ b/packages/react-native/ReactCommon/react/utils/CoreFeatures.cpp @@ -10,7 +10,6 @@ namespace facebook::react { bool CoreFeatures::enablePropIteratorSetter = false; -bool CoreFeatures::cacheLastTextMeasurement = false; bool CoreFeatures::enableGranularScrollViewStateUpdatesIOS = false; bool CoreFeatures::enableGranularShadowTreeStateReconciliation = false; bool CoreFeatures::enableClonelessStateProgression = false; diff --git a/packages/react-native/ReactCommon/react/utils/CoreFeatures.h b/packages/react-native/ReactCommon/react/utils/CoreFeatures.h index 7b9731917f58..ba9e9b030f50 100644 --- a/packages/react-native/ReactCommon/react/utils/CoreFeatures.h +++ b/packages/react-native/ReactCommon/react/utils/CoreFeatures.h @@ -19,12 +19,6 @@ class CoreFeatures { // Specifies whether the iterator-style prop parsing is enabled. static bool enablePropIteratorSetter; - // Yoga might measure multiple times the same Text with the same constraints - // This flag enables a caching mechanism to avoid subsequents measurements - // of the same Text with the same constrainst. - // On iOS, we also cache NSTextStorage. - static bool cacheLastTextMeasurement; - // When enabled, RCTScrollViewComponentView will trigger ShadowTree state // updates for all changes in scroll position. static bool enableGranularScrollViewStateUpdatesIOS;