diff --git a/packages/react-native/ReactAndroid/gradle.properties b/packages/react-native/ReactAndroid/gradle.properties index e1253cdb0a6a..d038787fe2e6 100644 --- a/packages/react-native/ReactAndroid/gradle.properties +++ b/packages/react-native/ReactAndroid/gradle.properties @@ -1,4 +1,4 @@ -VERSION_NAME=0.86.0-discord-19 +VERSION_NAME=0.86.0-wolewicki-font-test react.internal.publishingGroup=com.facebook.react react.internal.hermesPublishingGroup=com.facebook.hermes diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp index c630cb420188..f385d11e8162 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp @@ -466,6 +466,7 @@ void YogaLayoutableShadowNode::updateYogaProps() { void YogaLayoutableShadowNode::configureYogaTree( float pointScaleFactor, + Float fontSizeMultiplier, YGErrata defaultErrata, bool swapLeftAndRight) { ensureUnsealed(); @@ -475,6 +476,18 @@ void YogaLayoutableShadowNode::configureYogaTree( YGConfigSetErrata(&yogaConfig_, errata); YGConfigSetPointScaleFactor(&yogaConfig_, pointScaleFactor); + // A measurable node's measurement depends on `fontSizeMultiplier`, but unlike + // `pointScaleFactor` it is not part of the Yoga config, so a change does not + // invalidate Yoga's layout cache. Dirty the node when it changes to force + // re-measurement. We propagate up to the root so an unchanged, still-cached + // ancestor isn't skipped by `calculateLayoutInternal` before reaching us. + if (ReactNativeFeatureFlags::enableFontScaleChangesUpdatingLayout() && + getTraits().check(ShadowNodeTraits::Trait::MeasurableYogaNode) && + !floatEquality( + getLayoutMetrics().fontSizeMultiplier, fontSizeMultiplier)) { + yogaNode_.markDirtyAndPropagate(); + } + // TODO: `swapLeftAndRight` modified backing props and cannot be undone if (swapLeftAndRight) { swapStyleLeftAndRight(); @@ -493,6 +506,8 @@ void YogaLayoutableShadowNode::configureYogaTree( if (child.yogaTreeHasBeenConfigured_ && childLayoutMetrics.pointScaleFactor == pointScaleFactor && + floatEquality( + childLayoutMetrics.fontSizeMultiplier, fontSizeMultiplier) && childLayoutMetrics.wasLeftAndRightSwapped == swapLeftAndRight && childErrata == child.resolveErrata(errata)) { continue; @@ -501,10 +516,13 @@ void YogaLayoutableShadowNode::configureYogaTree( if (doesOwn(child)) { auto& mutableChild = const_cast(child); mutableChild.configureYogaTree( - pointScaleFactor, child.resolveErrata(errata), swapLeftAndRight); + pointScaleFactor, + fontSizeMultiplier, + child.resolveErrata(errata), + swapLeftAndRight); } else { cloneChildInPlace(i).configureYogaTree( - pointScaleFactor, errata, swapLeftAndRight); + pointScaleFactor, fontSizeMultiplier, errata, swapLeftAndRight); } } } @@ -601,6 +619,7 @@ void YogaLayoutableShadowNode::layoutTree( TraceSection s2("YogaLayoutableShadowNode::configureYogaTree"); configureYogaTree( layoutContext.pointScaleFactor, + layoutContext.fontSizeMultiplier, YGErrataAll /*defaultErrata*/, swapLeftAndRight); } @@ -666,6 +685,7 @@ void YogaLayoutableShadowNode::layoutTree( if (yogaNode_.getHasNewLayout()) { auto layoutMetrics = layoutMetricsFromYogaNode(yogaNode_); layoutMetrics.pointScaleFactor = layoutContext.pointScaleFactor; + layoutMetrics.fontSizeMultiplier = layoutContext.fontSizeMultiplier; layoutMetrics.wasLeftAndRightSwapped = swapLeftAndRight; setLayoutMetrics(layoutMetrics); yogaNode_.setHasNewLayout(false); @@ -713,6 +733,7 @@ void YogaLayoutableShadowNode::layout(LayoutContext layoutContext) { auto newLayoutMetrics = layoutMetricsFromYogaNode(*childYogaNode); newLayoutMetrics.pointScaleFactor = layoutContext.pointScaleFactor; + newLayoutMetrics.fontSizeMultiplier = layoutContext.fontSizeMultiplier; newLayoutMetrics.wasLeftAndRightSwapped = layoutContext.swapLeftAndRightInRTL && newLayoutMetrics.layoutDirection == LayoutDirection::RightToLeft; diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.h b/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.h index 25714d5f489d..fff9c00e7db1 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.h @@ -137,7 +137,8 @@ class YogaLayoutableShadowNode : public LayoutableShadowNode { * ShadowTree has been constructed, but before it has been is laid out or * committed. */ - void configureYogaTree(float pointScaleFactor, YGErrata defaultErrata, bool swapLeftAndRight); + void + configureYogaTree(float pointScaleFactor, Float fontSizeMultiplier, YGErrata defaultErrata, bool swapLeftAndRight); /** * Return an errata based on a `layoutConformance` prop if given, otherwise diff --git a/packages/react-native/ReactCommon/react/renderer/core/LayoutMetrics.h b/packages/react-native/ReactCommon/react/renderer/core/LayoutMetrics.h index 794f899eacb6..cc20fae6e0f1 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/LayoutMetrics.h +++ b/packages/react-native/ReactCommon/react/renderer/core/LayoutMetrics.h @@ -40,6 +40,8 @@ struct LayoutMetrics { bool wasLeftAndRightSwapped{false}; // Pixel density. Number of device pixels per density-independent pixel. Float pointScaleFactor{1.0}; + // Surface font scale this node was last laid out with. + Float fontSizeMultiplier{1.0}; // How much the children of the node actually overflow in each direction. // Positive values indicate that children are overflowing outside of the node. // Negative values indicate that children are clipped inside the node @@ -120,6 +122,7 @@ struct hash { layoutMetrics.displayType, layoutMetrics.layoutDirection, layoutMetrics.pointScaleFactor, + layoutMetrics.fontSizeMultiplier, layoutMetrics.overflowInset); } }; diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/SurfaceHandler.cpp b/packages/react-native/ReactCommon/react/renderer/scheduler/SurfaceHandler.cpp index 3d4adc6dbdfe..930472f096e9 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/SurfaceHandler.cpp +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/SurfaceHandler.cpp @@ -9,7 +9,6 @@ #include #include -#include #include namespace facebook::react { @@ -213,78 +212,6 @@ Size SurfaceHandler::measure( return rootShadowNode->getLayoutMetrics().frame.size; } -std::shared_ptr SurfaceHandler::dirtyMeasurableNodesRecursive( - std::shared_ptr node) const { - const auto nodeHasChildren = !node->getChildren().empty(); - const auto isMeasurableYogaNode = - node->getTraits().check(ShadowNodeTraits::Trait::MeasurableYogaNode); - - // Node is not measurable and has no children, its layout will not be affected - if (!nodeHasChildren && !isMeasurableYogaNode) { - return nullptr; - } - - std::shared_ptr>> - newChildren = ShadowNodeFragment::childrenPlaceholder(); - - if (nodeHasChildren) { - std::shared_ptr>> - newChildrenMutable = nullptr; - for (size_t i = 0; i < node->getChildren().size(); i++) { - const auto& child = node->getChildren()[i]; - - if (const auto& layoutableNode = - std::dynamic_pointer_cast( - child)) { - auto newChild = dirtyMeasurableNodesRecursive(layoutableNode); - - if (newChild != nullptr) { - if (newChildrenMutable == nullptr) { - newChildrenMutable = std::make_shared< - std::vector>>( - node->getChildren()); - newChildren = newChildrenMutable; - } - - (*newChildrenMutable)[i] = newChild; - } - } - } - - // Node is not measurable and its children were not dirtied, its layout will - // not be affected - if (!isMeasurableYogaNode && newChildrenMutable == nullptr) { - return nullptr; - } - } - - const auto newNode = node->getComponentDescriptor().cloneShadowNode( - *node, - { - .children = newChildren, - // Preserve the original state of the node - .state = node->getState(), - }); - - if (isMeasurableYogaNode) { - std::static_pointer_cast(newNode)->dirtyLayout(); - } - - return newNode; -} - -void SurfaceHandler::dirtyMeasurableNodes(ShadowNode& root) const { - for (const auto& child : root.getChildren()) { - if (const auto& layoutableNode = - std::dynamic_pointer_cast(child)) { - const auto newChild = dirtyMeasurableNodesRecursive(layoutableNode); - if (newChild != nullptr) { - root.replaceChild(*child, newChild); - } - } - } -} - void SurfaceHandler::constraintLayout( const LayoutConstraints& layoutConstraints, const LayoutContext& layoutContext) const { @@ -315,19 +242,8 @@ void SurfaceHandler::constraintLayout( link_.shadowTree && "`link_.shadowTree` must not be null."); link_.shadowTree->commit( [&](const RootShadowNode& oldRootShadowNode) { - auto newRoot = oldRootShadowNode.clone( + return oldRootShadowNode.clone( propsParserContext, layoutConstraints, layoutContext); - - // Dirty all measurable nodes when the fontSizeMultiplier changes to - // trigger re-measurement. - if (ReactNativeFeatureFlags::enableFontScaleChangesUpdatingLayout() && - layoutContext.fontSizeMultiplier != - oldRootShadowNode.getConcreteProps() - .layoutContext.fontSizeMultiplier) { - dirtyMeasurableNodes(*newRoot); - } - - return newRoot; }, {/* default commit options */}); } diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/SurfaceHandler.h b/packages/react-native/ReactCommon/react/renderer/scheduler/SurfaceHandler.h index b031a51c5455..93cfb22fef84 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/SurfaceHandler.h +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/SurfaceHandler.h @@ -151,12 +151,6 @@ class SurfaceHandler { void applyDisplayMode(DisplayMode displayMode) const; - /* - * An utility for dirtying all measurable shadow nodes present in the tree. - */ - void dirtyMeasurableNodes(ShadowNode &root) const; - std::shared_ptr dirtyMeasurableNodesRecursive(std::shared_ptr node) const; - #pragma mark - Link & Parameters /* diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index 46bf19be6619..c824941c083c 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -7363,6 +7363,7 @@ struct facebook::react::LayoutMetrics { public facebook::react::EdgeInsets borderWidth; public facebook::react::EdgeInsets contentInsets; public facebook::react::EdgeInsets overflowInset; + public facebook::react::Float fontSizeMultiplier; public facebook::react::Float pointScaleFactor; public facebook::react::LayoutDirection layoutDirection; public facebook::react::PositionType positionType; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index 2f0733cb3966..eb261741fe80 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -7354,6 +7354,7 @@ struct facebook::react::LayoutMetrics { public facebook::react::EdgeInsets borderWidth; public facebook::react::EdgeInsets contentInsets; public facebook::react::EdgeInsets overflowInset; + public facebook::react::Float fontSizeMultiplier; public facebook::react::Float pointScaleFactor; public facebook::react::LayoutDirection layoutDirection; public facebook::react::PositionType positionType; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index 62d88ff75464..c13bd1f3b902 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -9829,6 +9829,7 @@ struct facebook::react::LayoutMetrics { public facebook::react::EdgeInsets borderWidth; public facebook::react::EdgeInsets contentInsets; public facebook::react::EdgeInsets overflowInset; + public facebook::react::Float fontSizeMultiplier; public facebook::react::Float pointScaleFactor; public facebook::react::LayoutDirection layoutDirection; public facebook::react::PositionType positionType; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index b8feb8d7d212..73e8f5a49934 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -9820,6 +9820,7 @@ struct facebook::react::LayoutMetrics { public facebook::react::EdgeInsets borderWidth; public facebook::react::EdgeInsets contentInsets; public facebook::react::EdgeInsets overflowInset; + public facebook::react::Float fontSizeMultiplier; public facebook::react::Float pointScaleFactor; public facebook::react::LayoutDirection layoutDirection; public facebook::react::PositionType positionType; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index 5f8fc0c410be..0ac86eee3884 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -5565,6 +5565,7 @@ struct facebook::react::LayoutContext { } struct facebook::react::LayoutMetrics { + public Float fontSizeMultiplier; public Float pointScaleFactor; public bool operator==(const facebook::react::LayoutMetrics& rhs) const = default; public bool wasLeftAndRightSwapped; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index 727e60d3be74..790b57e153b3 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -5556,6 +5556,7 @@ struct facebook::react::LayoutContext { } struct facebook::react::LayoutMetrics { + public Float fontSizeMultiplier; public Float pointScaleFactor; public bool operator==(const facebook::react::LayoutMetrics& rhs) const = default; public bool wasLeftAndRightSwapped;