Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ class TextLayoutManagerExtended {
return TextLayoutManagerWithPreparedTextLayout<TextLayoutManagerT>;
}

static constexpr bool supportsPrepareForDrawing()
{
return requires(
const TextLayoutManagerT &textLayoutManager,
AttributedString attributedString,
ParagraphAttributes paragraphAttributes,
Size size) { textLayoutManager.prepareForDrawing(attributedString, paragraphAttributes, size); };
}

using PreparedTextLayout = typename PreparedTextLayoutT<TextLayoutManagerT>::type;

TextLayoutManagerExtended(const TextLayoutManagerT &textLayoutManager) : textLayoutManager_(textLayoutManager) {}
Expand Down Expand Up @@ -98,6 +107,18 @@ class TextLayoutManagerExtended {
LOG(FATAL) << "Platform TextLayoutManager does not support prepareLayout";
}

void prepareForDrawing(
const AttributedString &attributedString,
const ParagraphAttributes &paragraphAttributes,
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,74 @@
#import <React/NSTextStorage+FontScaling.h>
#import <React/RCTUtils.h>
#import <react/featureflags/ReactNativeFeatureFlags.h>
#import <react/renderer/textlayoutmanager/TextMeasureCache.h>
#import <react/renderer/textlayoutmanager/TextMeasurementRounding.h>
#import <react/utils/ManagedObjectWrapper.h>
#import <react/utils/SimpleThreadSafeCache.h>

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 &paragraphAttributes,
CGSize size)
{
return RCTPreparedTextLayoutKey{
.attributedString = attributedString,
.paragraphAttributes = paragraphAttributes,
.size = facebook::react::Size{
.width = static_cast<facebook::react::Float>(size.width),
.height = static_cast<facebook::react::Float>(size.height)}};
}

} // namespace facebook::react

namespace std {

template <>
struct hash<facebook::react::RCTPreparedTextLayoutKey> {
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<AttributedString, std::shared_ptr<void>, 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<RCTPreparedTextLayoutKey, std::shared_ptr<void>, 256> _preparedTextLayouts;
}

static NSLineBreakMode RCTNSLineBreakModeFromEllipsizeMode(EllipsizeMode ellipsizeMode)
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ class TextLayoutManager {
const ParagraphAttributes &paragraphAttributes,
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 &paragraphAttributes,
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,18 @@
return measurement;
}

void TextLayoutManager::prepareForDrawing(
const AttributedString &attributedString,
const ParagraphAttributes &paragraphAttributes,
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 &paragraphAttributes,
Expand Down
2 changes: 2 additions & 0 deletions scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api
Original file line number Diff line number Diff line change
Expand Up @@ -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<TextLayoutManagerT>::type;
public void prepareForDrawing(const facebook::react::AttributedString& attributedString, const facebook::react::ParagraphAttributes& paragraphAttributes, const facebook::react::Size& size) const;
}

template <typename... T>
Expand Down
4 changes: 4 additions & 0 deletions scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api
Original file line number Diff line number Diff line change
Expand Up @@ -1936,6 +1936,7 @@ interface RCTTextLayoutManager : public NSObject {
public virtual std::shared_ptr<const facebook::react::EventEmitter> 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 {
Expand Down Expand Up @@ -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<void> 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 {
Expand Down Expand Up @@ -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<TextLayoutManagerT>::type;
public void prepareForDrawing(const facebook::react::AttributedString& attributedString, const facebook::react::ParagraphAttributes& paragraphAttributes, const facebook::react::Size& size) const;
}

template <typename... T>
Expand Down
2 changes: 2 additions & 0 deletions scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api
Original file line number Diff line number Diff line change
Expand Up @@ -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<TextLayoutManagerT>::type;
public void prepareForDrawing(const facebook::react::AttributedString& attributedString, const facebook::react::ParagraphAttributes& paragraphAttributes, const facebook::react::Size& size) const;
}

template <typename... T>
Expand Down
Loading