From b7b87dde7e793a10ff0af4c8cdd3b9c7a16670d7 Mon Sep 17 00:00:00 2001 From: hailey Date: Wed, 21 Oct 2026 08:28:47 +0000 Subject: [PATCH] Lay out iOS text off the main thread before its first draw MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary The first time a text view draws, `drawAttributedString:` runs on the main thread and lays its text out from scratch: it builds the `NSAttributedString` (font lookups), attaches a new layout manager to it (font fixing, glyph generation) and lays out every line. Measuring already did the same work, off the main thread, and that layout is thrown away. Painting the glyphs turns out to be a small part of the cost of a draw. **Current behavior.** When new text appears, the display pass that shows it for the first time pays for that layout on the main thread. In a trace of a feed scrolling in the app that hit this, first draws of text were the largest single main-thread cost of mounting a row, about 22% of the mount transaction. React Native has `enablePreparedTextLayout` for this, but its implementation is Android only. **With this change.** `ParagraphShadowNode` asks the text layout manager to lay the text out, on the thread it is laying out on, for the frame the text view will draw in. It does that where it creates the paragraph state for that content and size, which is already the code that knows both, and is exactly when the view redraws. `RCTTextLayoutManager` keeps laid-out text storages in a thread-safe LRU cache, keyed by what the paragraph draws and the frame it draws in, so the draw finds the one made for it and only paints. `drawAttributedString:` otherwise behaves as before: when nothing was prepared for it, it builds the layout itself, as it does today. The cached object is the layout: the `NSLayoutManager` attached to the text storage holds the line fragments and glyphs, and it is the same object the current code builds on the main thread for every draw. A hit changes nothing about what is drawn. Key equivalence and hashing are the ones prepared text layouts use on Android (`areAttributedStringsEquivalentDisplayWise`): everything drawing reads, including text attributes and attachment sizes, and nothing that only matters for hit testing, such as a fragment's event emitter. Two text views that draw the same content at the same size share one layout, so repeated content in a long list is laid out once. Preparing is a new optional method on the text layout manager, reached through `TextLayoutManagerExtended` like `measureLines`, so platforms whose manager has no layout to prepare, such as the C++ one, compile it away. **Why not `enablePreparedTextLayout`.** That flag changes how paragraphs are measured (`rawContentSize()`, exact constraints, prepared layouts carried in `ParagraphState`), and its implementation lives behind Android's text pipeline. Making the iOS `TextLayoutManager` satisfy `TextLayoutManagerWithPreparedTextLayout` is a bigger change with a much wider blast radius, because it changes iOS measurement too. This change leaves measurement, `ParagraphState` and the drawing code alone, so it can be evaluated and reverted on its own. Converging the two is a reasonable follow-up. **Threading and cost.** Layouts are built on the thread that lays out the shadow tree and are immutable once built, apart from the truncated-text color fix-up that a draw applies. `SimpleThreadSafeCache::get` runs its generator under the cache's lock, so a preparation holds the lock for the length of one layout and waits on other preparations; draws only look entries up, so the main thread never waits on a layout and never blocks text views behind one another. Text is laid out at most twice, once for measurement and once for the first draw, the second on the JS thread rather than the main thread, at about a millisecond per paragraph in a commit. Text that is never displayed is still prepared, because at layout time React Native does not know which paragraphs will be drawn. ## Changelog [IOS] [CHANGED] - Lay out `Text` off the main thread before its first draw ## Test Plan - `node scripts/clang-format.js` with the pinned clang-format (21.1.2) on the changed files: passes. - The new methods are added to `scripts/cxx-api/api-snapshots/{ReactApple,ReactAndroid,ReactCommon}FrameworksCxx.api` in the order the generator sorts them (the members of each block remain ASCII-sorted). `python -m scripts.cxx-api.parser` does not run on this Linux machine (its Doxygen XML parse comes back empty), so those files were edited by hand and should be regenerated before landing. - **Not built.** This machine has no macOS, Xcode or iOS SDK, so none of this compiled here, not the ObjC++ and not the Fantom tester. A branch that has never compiled is the main risk in this PR: it needs a build and a run in RNTester on a device before it is ready. - **Not measured.** The numbers below come from the app that motivated the change, where the same approach was developed on top of 0.86.3, not from this branch. Release build, iPhone 16 Pro Max, 30s fast fling through a feed, with and without the change: | | before | after | |---|---:|---:| | main-thread time in `drawAttributedString:` per 30s | 570 ms | 160 ms | | hitches | 88 | 40 | | hitch time per second of scrolling | 40.8 ms | 14.6 ms | | JS-thread time spent preparing text per 30s | 0 ms | ~300 ms | What is left in `drawAttributedString:` is painting glyphs, background and highlight rectangles, and the truncated-text color fix-up. - Things to look at once it runs: truncated text (the fix-up that colors the "…" mutates the text storage, which is now shared, so a second draw must look the same), multi-line text, text with inline views, link press highlighting, and text at several widths in a `ScrollView`. - Known limitation: with `enableIOSCompressedTextFrameAdjustment` on, a paragraph that sets a line height is drawn in an adjusted frame, so that paragraph is laid out twice and the prepared layout is not the one drawn. Both flags are off by default and independent; making them work together is a follow-up. - Follow-up: the truncated-text color fix-up could move into the preparation, which would make prepared layouts immutable and take another ~35 ms per 30s of scrolling off the main thread. It is left out to keep this change small. --- .../components/text/ParagraphShadowNode.cpp | 8 ++ .../TextLayoutManagerExtended.h | 21 ++++ .../textlayoutmanager/RCTTextLayoutManager.h | 9 ++ .../textlayoutmanager/RCTTextLayoutManager.mm | 112 +++++++++++++++++- .../textlayoutmanager/TextLayoutManager.h | 10 ++ .../textlayoutmanager/TextLayoutManager.mm | 12 ++ .../ReactAndroidFrameworksCxx.api | 2 + .../api-snapshots/ReactAppleFrameworksCxx.api | 4 + .../ReactCommonFrameworksCxx.api | 2 + 9 files changed, 176 insertions(+), 4 deletions(-) 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 9009071df8d2..dbc4a58e3af0 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphShadowNode.cpp @@ -195,6 +195,14 @@ void ParagraphShadowNode::updateStateIfNeeded(const Content& content) { return; } + if constexpr (TextLayoutManagerExtended::supportsPrepareForDrawing()) { + // The text view lays its text out on the main thread the first time it + // draws. Lay it out here instead, for the frame that draw uses. + TextLayoutManagerExtended(*textLayoutManager_) + .prepareForDrawing( + content.attributedString, content.paragraphAttributes, layoutSize); + } + setStateData( ParagraphState{ .attributedString = content.attributedString, diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/TextLayoutManagerExtended.h b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/TextLayoutManagerExtended.h index 93677133e64d..84ecb262d9d9 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/TextLayoutManagerExtended.h +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/TextLayoutManagerExtended.h @@ -71,6 +71,15 @@ class TextLayoutManagerExtended { return TextLayoutManagerWithPreparedTextLayout; } + static constexpr bool supportsPrepareForDrawing() + { + return requires( + const TextLayoutManagerT &textLayoutManager, + AttributedString attributedString, + ParagraphAttributes paragraphAttributes, + Size size) { textLayoutManager.prepareForDrawing(attributedString, paragraphAttributes, size); }; + } + using PreparedTextLayout = typename PreparedTextLayoutT::type; TextLayoutManagerExtended(const TextLayoutManagerT &textLayoutManager) : textLayoutManager_(textLayoutManager) {} @@ -98,6 +107,18 @@ class TextLayoutManagerExtended { LOG(FATAL) << "Platform TextLayoutManager does not support prepareLayout"; } + void prepareForDrawing( + const AttributedString &attributedString, + const ParagraphAttributes ¶graphAttributes, + const Size &size) const + { + if constexpr (supportsPrepareForDrawing()) { + textLayoutManager_.prepareForDrawing(attributedString, paragraphAttributes, size); + } else { + LOG(FATAL) << "Platform TextLayoutManager does not support prepareForDrawing"; + } + } + TextMeasurement measurePreparedLayout( const PreparedTextLayout &layout, const TextLayoutContext &layoutContext, 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 2cd7f1bce247..d982f6bfefbe 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 @@ -39,6 +39,15 @@ using RCTTextLayoutFragmentEnumerationBlock = layoutContext:(facebook::react::TextLayoutContext)layoutContext layoutConstraints:(facebook::react::LayoutConstraints)layoutConstraints; +/** + * Lays `attributedString` out for a text view that draws it in a frame of `size`, + * so that the draw does not lay it out again. + * Can be called from any thread. + */ +- (void)prepareAttributedStringForDrawing:(const facebook::react::AttributedString &)attributedString + paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes + size:(CGSize)size; + - (void)drawAttributedString:(const facebook::react::AttributedString &)attributedString paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes 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 3e93b60e64be..a222ba575116 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 @@ -15,14 +15,74 @@ #import #import #import +#import #import #import #import using namespace facebook::react; +namespace facebook::react { + +/* + * Identifies the text storage a text view draws: what it draws, and the size of + * the frame it draws it in. Two paragraphs that are equivalent this way draw + * the same, so they share a text storage. Equivalence is the one prepared text + * layouts use on Android: everything drawing looks at, and nothing that only + * matters for hit testing, such as a fragment's event emitter. + * + * The frame size is all a layout depends on: it is the size the view draws in, + * already rounded to the pixel grid by Yoga, so the pixel scale factor is not + * part of the key. + */ +class RCTPreparedTextLayoutKey final { + public: + AttributedString attributedString; + ParagraphAttributes paragraphAttributes; + Size size; + + bool operator==(const RCTPreparedTextLayoutKey &rhs) const + { + return areAttributedStringsEquivalentDisplayWise(attributedString, rhs.attributedString) && + paragraphAttributes == rhs.paragraphAttributes && size == rhs.size; + } +}; + +inline RCTPreparedTextLayoutKey RCTPreparedTextLayoutKeyFor( + const AttributedString &attributedString, + const ParagraphAttributes ¶graphAttributes, + CGSize size) +{ + return RCTPreparedTextLayoutKey{ + .attributedString = attributedString, + .paragraphAttributes = paragraphAttributes, + .size = facebook::react::Size{ + .width = static_cast(size.width), + .height = static_cast(size.height)}}; +} + +} // namespace facebook::react + +namespace std { + +template <> +struct hash { + size_t operator()(const facebook::react::RCTPreparedTextLayoutKey &key) const + { + return facebook::react::hash_combine( + facebook::react::attributedStringHashDisplayWise(key.attributedString), key.paragraphAttributes, key.size); + } +}; + +} // namespace std + @implementation RCTTextLayoutManager { SimpleThreadSafeCache, 256> _cache; + /* + * Text laid out for the first draw of a text view. The text storage is the + * prepared layout: it owns the layout manager that holds the lines. + */ + SimpleThreadSafeCache, 256> _preparedTextLayouts; } static NSLineBreakMode RCTNSLineBreakModeFromEllipsizeMode(EllipsizeMode ellipsizeMode) @@ -128,15 +188,36 @@ - (CGRect)drawingFrameForAttributedString:(const AttributedString &)attributedSt return drawingFrame; } +- (void)prepareAttributedStringForDrawing:(const AttributedString &)attributedString + paragraphAttributes:(ParagraphAttributes)paragraphAttributes + size:(CGSize)size +{ + if (attributedString.isEmpty() || size.width <= 0 || size.height <= 0) { + return; + } + + // Laying the text out here holds the cache for the whole layout, which + // serializes preparations. Drawing only looks layouts up, so it never waits on + // this. + (void)_preparedTextLayouts.get(RCTPreparedTextLayoutKeyFor(attributedString, paragraphAttributes, size), [&]() { + NSAttributedString *nsAttributedString = [self _nsAttributedStringFromAttributedString:attributedString]; + NSTextStorage *textStorage = [self _textStorageAndLayoutManagerWithAttributesString:nsAttributedString + paragraphAttributes:paragraphAttributes + size:size]; + NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject; + [layoutManager ensureLayoutForTextContainer:layoutManager.textContainers.firstObject]; + return wrapManagedObject(textStorage); + }); +} + - (void)drawAttributedString:(const AttributedString &)attributedString paragraphAttributes:(ParagraphAttributes)paragraphAttributes frame:(CGRect)frame drawHighlightPath:(void (^_Nullable)(UIBezierPath *highlightPath))block { - NSTextStorage *textStorage = [self - _textStorageAndLayoutManagerWithAttributesString:[self _nsAttributedStringFromAttributedString:attributedString] - paragraphAttributes:paragraphAttributes - size:frame.size]; + NSTextStorage *textStorage = [self _textStorageForDrawingWithAttributedString:attributedString + paragraphAttributes:paragraphAttributes + size:frame.size]; NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject; NSTextContainer *textContainer = layoutManager.textContainers.firstObject; @@ -431,6 +512,29 @@ - (CGFloat)_maximumFontSizeInAttributedString:(NSAttributedString *)attributedSt return maximumFontSize; } +/* + * The text storage to draw `attributedString` with in a frame of `size`: the one + * prepared for exactly that, or a new one when none was prepared. + */ +- (NSTextStorage *)_textStorageForDrawingWithAttributedString:(const AttributedString &)attributedString + paragraphAttributes:(ParagraphAttributes)paragraphAttributes + size:(CGSize)size +{ + auto preparedTextLayout = + _preparedTextLayouts.get(RCTPreparedTextLayoutKeyFor(attributedString, paragraphAttributes, size)); + NSTextStorage *textStorage = preparedTextLayout.has_value() ? unwrapManagedObject(*preparedTextLayout) : nil; + if (textStorage != nil) { + return textStorage; + } + + // Not prepared: lay the text out here, as before. It is not added to the + // cache, so that drawing never holds it. + return [self + _textStorageAndLayoutManagerWithAttributesString:[self _nsAttributedStringFromAttributedString:attributedString] + paragraphAttributes:paragraphAttributes + size:size]; +} + - (NSTextStorage *)_textStorageAndLayoutManagerWithAttributesString:(NSAttributedString *)attributedString paragraphAttributes:(ParagraphAttributes)paragraphAttributes size:(CGSize)size 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 c97994b6068b..7fb5d06760a3 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 @@ -57,6 +57,16 @@ class TextLayoutManager { const ParagraphAttributes ¶graphAttributes, const Size &size) const; + /* + * Lays `attributedString` out for a text view that draws it in a frame of + * `size`, so that the view's first draw does not lay it out again. + * Can be called from any thread. + */ + void prepareForDrawing( + const AttributedString &attributedString, + const ParagraphAttributes ¶graphAttributes, + const Size &size) 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 bf4bc16c6542..1e7f1f6c1390 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 @@ -113,6 +113,18 @@ return measurement; } +void TextLayoutManager::prepareForDrawing( + const AttributedString &attributedString, + const ParagraphAttributes ¶graphAttributes, + const Size &size) const +{ + RCTTextLayoutManager *textLayoutManager = (RCTTextLayoutManager *)unwrapManagedObject(nativeTextLayoutManager_); + + [textLayoutManager prepareAttributedStringForDrawing:attributedString + paragraphAttributes:paragraphAttributes + size:CGSizeMake(size.width, size.height)]; +} + LinesMeasurements TextLayoutManager::measureLines( const AttributedStringBox &attributedStringBox, const ParagraphAttributes ¶graphAttributes, diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api index 69efb6e2de07..997ba5e98a89 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api @@ -8403,8 +8403,10 @@ class facebook::react::detail::TextLayoutManagerExtended { public facebook::react::TextMeasurement measurePreparedLayout(const facebook::react::detail::TextLayoutManagerExtended::PreparedTextLayout& layout, const facebook::react::TextLayoutContext& layoutContext, const facebook::react::LayoutConstraints& layoutConstraints) const; public facebook::react::detail::TextLayoutManagerExtended::PreparedTextLayout prepareLayout(const facebook::react::AttributedString& attributedString, const facebook::react::ParagraphAttributes& paragraphAttributes, const facebook::react::TextLayoutContext& layoutContext, const facebook::react::LayoutConstraints& layoutConstraints) const; public static constexpr bool supportsLineMeasurement(); + public static constexpr bool supportsPrepareForDrawing(); public static constexpr bool supportsPreparedTextLayout(); public using PreparedTextLayout = typename facebook::react::detail::PreparedTextLayoutT::type; + public void prepareForDrawing(const facebook::react::AttributedString& attributedString, const facebook::react::ParagraphAttributes& paragraphAttributes, const facebook::react::Size& size) const; } template diff --git a/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api index a21ebf068233..451dff229ef2 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api @@ -1936,6 +1936,7 @@ interface RCTTextLayoutManager : public NSObject { 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); + public virtual void prepareAttributedStringForDrawing:paragraphAttributes:size:(const facebook::react::AttributedString& attributedString, facebook::react::ParagraphAttributes paragraphAttributes, CGSize size); } interface RCTTextSelection : public NSObject { @@ -6213,6 +6214,7 @@ class facebook::react::TextLayoutManager { public facebook::react::TextLayoutManager& operator=(facebook::react::TextLayoutManager&&) = delete; public facebook::react::TextMeasurement measure(const facebook::react::AttributedStringBox& attributedStringBox, const facebook::react::ParagraphAttributes& paragraphAttributes, const facebook::react::TextLayoutContext& layoutContext, const facebook::react::LayoutConstraints& layoutConstraints) const; public std::shared_ptr getNativeTextLayoutManager() const; + public void prepareForDrawing(const facebook::react::AttributedString& attributedString, const facebook::react::ParagraphAttributes& paragraphAttributes, const facebook::react::Size& size) const; } class facebook::react::TextMeasureCacheKey { @@ -10312,8 +10314,10 @@ class facebook::react::detail::TextLayoutManagerExtended { public facebook::react::TextMeasurement measurePreparedLayout(const facebook::react::detail::TextLayoutManagerExtended::PreparedTextLayout& layout, const facebook::react::TextLayoutContext& layoutContext, const facebook::react::LayoutConstraints& layoutConstraints) const; public facebook::react::detail::TextLayoutManagerExtended::PreparedTextLayout prepareLayout(const facebook::react::AttributedString& attributedString, const facebook::react::ParagraphAttributes& paragraphAttributes, const facebook::react::TextLayoutContext& layoutContext, const facebook::react::LayoutConstraints& layoutConstraints) const; public static constexpr bool supportsLineMeasurement(); + public static constexpr bool supportsPrepareForDrawing(); public static constexpr bool supportsPreparedTextLayout(); public using PreparedTextLayout = typename facebook::react::detail::PreparedTextLayoutT::type; + public void prepareForDrawing(const facebook::react::AttributedString& attributedString, const facebook::react::ParagraphAttributes& paragraphAttributes, const facebook::react::Size& size) const; } template diff --git a/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api index 7933bdb1a433..d1cae4696906 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api @@ -5647,8 +5647,10 @@ class facebook::react::detail::TextLayoutManagerExtended { public facebook::react::TextMeasurement measurePreparedLayout(const facebook::react::detail::TextLayoutManagerExtended::PreparedTextLayout& layout, const facebook::react::TextLayoutContext& layoutContext, const facebook::react::LayoutConstraints& layoutConstraints) const; public facebook::react::detail::TextLayoutManagerExtended::PreparedTextLayout prepareLayout(const facebook::react::AttributedString& attributedString, const facebook::react::ParagraphAttributes& paragraphAttributes, const facebook::react::TextLayoutContext& layoutContext, const facebook::react::LayoutConstraints& layoutConstraints) const; public static constexpr bool supportsLineMeasurement(); + public static constexpr bool supportsPrepareForDrawing(); public static constexpr bool supportsPreparedTextLayout(); public using PreparedTextLayout = typename facebook::react::detail::PreparedTextLayoutT::type; + public void prepareForDrawing(const facebook::react::AttributedString& attributedString, const facebook::react::ParagraphAttributes& paragraphAttributes, const facebook::react::Size& size) const; } template