Skip to content
Closed
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 @@ -59,6 +59,23 @@ ParagraphShadowNode::ParagraphShadowNode(
const ShadowNodeFragment& fragment)
: ConcreteViewShadowNode(sourceShadowNode, fragment) {
initialize();
// A revision with neither new props nor new children (e.g. one Yoga cloned
// because a sibling changed) renders the same content, so keep the
// attributed string and the layouts measured for it instead of rebuilding
// and re-measuring them. `getContent` drops both if the layout context they
// were built for no longer matches.
//
// Content with attachments is not kept: they point at the children the
// content was built from, and `layout` replaces those children with laid-out
// clones, so in a later revision the pointers would outlive the nodes.
if (fragment.props == nullptr && fragment.children == nullptr) {
const auto& source =
static_cast<const ParagraphShadowNode&>(sourceShadowNode);
if (source.content_.has_value() && source.content_->attachments.empty()) {
content_ = source.content_;
measuredLayouts_ = source.measuredLayouts_;
}
}
}

bool ParagraphShadowNode::shouldNewRevisionDirtyMeasurement(
Expand All @@ -69,19 +86,34 @@ bool ParagraphShadowNode::shouldNewRevisionDirtyMeasurement(

const Content& ParagraphShadowNode::getContent(
const LayoutContext& layoutContext) const {
auto layoutDirection = YGNodeLayoutGetDirection(&yogaNode_) == YGDirectionRTL
? LayoutDirection::RightToLeft
: LayoutDirection::LeftToRight;
if (content_.has_value()) {
return content_.value();
const auto& baseTextAttributes =
content_->attributedString.getBaseTextAttributes();
// The content is built from this node's props and children, and it is
// only carried over to revisions that have neither new props nor new
// children (see the copy constructor). The only other inputs are the
// font size multiplier from the layout context and the layout direction
// Yoga resolves from the parent, and those can change without new props
// or children, so they are the two things to check here.
if (baseTextAttributes.fontSizeMultiplier ==
layoutContext.fontSizeMultiplier &&
baseTextAttributes.layoutDirection == layoutDirection) {
return content_.value();
Comment thread
tomekzaw marked this conversation as resolved.
}
// Carried over from a previous revision, but built for another context.
content_.reset();
measuredLayouts_.clear();
}

ensureUnsealed();

auto textAttributes = TextAttributes::defaultTextAttributes();
textAttributes.fontSizeMultiplier = layoutContext.fontSizeMultiplier;
textAttributes.apply(getConcreteProps().textAttributes);
textAttributes.layoutDirection =
YGNodeLayoutGetDirection(&yogaNode_) == YGDirectionRTL
? LayoutDirection::RightToLeft
: LayoutDirection::LeftToRight;
textAttributes.layoutDirection = layoutDirection;
auto attributedString = AttributedString{};
auto attachments = Attachments{};
buildAttributedString(textAttributes, *this, attributedString, attachments);
Expand Down Expand Up @@ -189,7 +221,8 @@ void ParagraphShadowNode::updateStateIfNeeded(const Content& content) {
textLayoutManager_});
}

MeasuredPreparedTextLayout* ParagraphShadowNode::findUsableLayout() {
MeasuredPreparedTextLayout* ParagraphShadowNode::findUsableLayout(
Float pointScaleFactor) {
MeasuredPreparedTextLayout* ret = nullptr;

if constexpr (TextLayoutManagerExtended::supportsPreparedTextLayout()) {
Expand All @@ -201,7 +234,8 @@ MeasuredPreparedTextLayout* ParagraphShadowNode::findUsableLayout() {
// adjusted by a constraint (like min-size).
auto expectedSize = rawContentSize();
for (auto& prevLayout : measuredLayouts_) {
if (floatEquality(
if (prevLayout.pointScaleFactor == pointScaleFactor &&
floatEquality(
prevLayout.measurement.size.width, expectedSize.width) &&
floatEquality(
prevLayout.measurement.size.height, expectedSize.height)) {
Expand Down Expand Up @@ -230,8 +264,11 @@ Size ParagraphShadowNode::measureContent(
const LayoutContext& layoutContext,
const LayoutConstraints& layoutConstraints) const {
if constexpr (TextLayoutManagerExtended::supportsPreparedTextLayout()) {
// Layouts carried over from a previous revision may have been prepared
// at another pixel density, which they can't be reused for.
for (const auto& layout : measuredLayouts_) {
if (layout.layoutConstraints == layoutConstraints) {
if (layout.layoutConstraints == layoutConstraints &&
layout.pointScaleFactor == layoutContext.pointScaleFactor) {
return layout.measurement.size;
}
}
Expand Down Expand Up @@ -321,7 +358,7 @@ void ParagraphShadowNode::layout(LayoutContext layoutContext) {
auto content =
getContentWithMeasuredAttachments(layoutContext, layoutConstraints);

auto measuredLayout = findUsableLayout();
auto measuredLayout = findUsableLayout(layoutContext.pointScaleFactor);

if constexpr (
TextLayoutManagerExtended::supportsPreparedTextLayout() &&
Expand All @@ -338,7 +375,7 @@ void ParagraphShadowNode::layout(LayoutContext layoutContext) {
// culled
if (measuredLayout == nullptr) {
measureContent(layoutContext, layoutConstraints);
measuredLayout = findUsableLayout();
measuredLayout = findUsableLayout(layoutContext.pointScaleFactor);
}
react_native_assert(measuredLayout);
updateStateIfNeeded<ParagraphState>(content, *measuredLayout);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,10 @@ class ParagraphShadowNode

/**
* Returns any previously prepared layout, generated for measurement, which
* may be used, given the currently assigned layout to the ShadowNode
* may be used, given the currently assigned layout to the ShadowNode and
* the current pixel density
*/
MeasuredPreparedTextLayout *findUsableLayout();
MeasuredPreparedTextLayout *findUsableLayout(Float pointScaleFactor);

/**
* Returns the final measure of the content frame, before layout rounding
Expand Down
Loading