mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Underpinnings for Caching Text Layouts (#51065)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/51065 This adds infrastructure to let us start storing cached Android text layouts as part of a `ParagraphShadowNode`. After this, we will clear them out, and propagate them to state. Right now, the flag doesn't do much, apart from extra work. This is done by adding `TextLayoutManagerExtended::supportsPreparedLayout()`, and `TextLayoutManager::PreparedLayout` types, to shim between platforms, then on Android, we add a `PreparedLayout`, which is for now just an Android layout, with extra field (`maxNumberOfLines`, for some reason not exposed on recent versions). Android `TextLayoutManager` java side is split a little bit, so that we reuse all the existing logic for prepared layouts. I tried to set up the boundary, so that we don't reserialize a MapBuffer after preparation, and for simplicity, this means source of truth for attachment count, and attachment sizes, now lives on the layout. This means we need to change boundary a bit, where we are no longer able to pass in a buffer to fill from C++ side of attachment positions. Changelog: [Internal] Reviewed By: mdvacca Differential Revision: D73970149 fbshipit-source-id: ff71c227e062c16fe52a4eb3ba2acbebf3d96e56
This commit is contained in:
committed by
Facebook GitHub Bot
parent
2562440385
commit
f85b30b24b
@@ -2392,12 +2392,14 @@ public class com/facebook/react/fabric/FabricUIManager : com/facebook/react/brid
|
||||
public fun initialize ()V
|
||||
public fun invalidate ()V
|
||||
public fun markActiveTouchForTag (II)V
|
||||
public fun measurePreparedLayout (Lcom/facebook/react/views/text/PreparedLayout;FFFF)[F
|
||||
public fun onAllAnimationsComplete ()V
|
||||
public fun onAnimationStarted ()V
|
||||
public fun onHostDestroy ()V
|
||||
public fun onHostPause ()V
|
||||
public fun onHostResume ()V
|
||||
public fun onRequestEventBeat ()V
|
||||
public fun prepareLayout (ILcom/facebook/react/common/mapbuffer/ReadableMapBuffer;Lcom/facebook/react/common/mapbuffer/ReadableMapBuffer;FF)Lcom/facebook/react/views/text/PreparedLayout;
|
||||
public fun prependUIBlock (Lcom/facebook/react/fabric/interop/UIBlock;)V
|
||||
public fun profileNextBatch ()V
|
||||
public fun receiveEvent (IILjava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V
|
||||
|
||||
+47
@@ -22,11 +22,13 @@ import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.os.SystemClock;
|
||||
import android.text.Layout;
|
||||
import android.view.View;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.core.util.Preconditions;
|
||||
import androidx.core.view.ViewCompat.FocusRealDirection;
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
@@ -49,6 +51,7 @@ import com.facebook.react.bridge.UIManager;
|
||||
import com.facebook.react.bridge.UIManagerListener;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.common.annotations.UnstableReactNativeAPI;
|
||||
import com.facebook.react.common.build.ReactBuildConfig;
|
||||
import com.facebook.react.common.mapbuffer.ReadableMapBuffer;
|
||||
@@ -86,6 +89,7 @@ import com.facebook.react.uimanager.events.EventDispatcher;
|
||||
import com.facebook.react.uimanager.events.FabricEventDispatcher;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
import com.facebook.react.uimanager.events.SynchronousEventReceiver;
|
||||
import com.facebook.react.views.text.PreparedLayout;
|
||||
import com.facebook.react.views.text.TextLayoutManager;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -648,6 +652,49 @@ public class FabricUIManager
|
||||
attachmentsPositions);
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
@ThreadConfined(ANY)
|
||||
public PreparedLayout prepareLayout(
|
||||
int surfaceId,
|
||||
ReadableMapBuffer attributedString,
|
||||
ReadableMapBuffer paragraphAttributes,
|
||||
float maxWidth,
|
||||
float maxHeight) {
|
||||
SurfaceMountingManager surfaceMountingManager =
|
||||
mMountingManager.getSurfaceManagerEnforced(surfaceId, "prepareLayout");
|
||||
Layout layout =
|
||||
TextLayoutManager.createLayout(
|
||||
Preconditions.checkNotNull(surfaceMountingManager.getContext()),
|
||||
attributedString,
|
||||
paragraphAttributes,
|
||||
PixelUtil.toPixelFromDIP(maxWidth),
|
||||
PixelUtil.toPixelFromDIP(maxHeight),
|
||||
null /* T219881133: Migrate away from ReactTextViewManagerCallback */);
|
||||
|
||||
int maximumNumberOfLines =
|
||||
paragraphAttributes.contains(TextLayoutManager.PA_KEY_MAX_NUMBER_OF_LINES)
|
||||
? paragraphAttributes.getInt(TextLayoutManager.PA_KEY_MAX_NUMBER_OF_LINES)
|
||||
: ReactConstants.UNSET;
|
||||
|
||||
return new PreparedLayout(layout, maximumNumberOfLines);
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
@ThreadConfined(ANY)
|
||||
public float[] measurePreparedLayout(
|
||||
PreparedLayout preparedLayout,
|
||||
float minWidth,
|
||||
float maxWidth,
|
||||
float minHeight,
|
||||
float maxHeight) {
|
||||
return TextLayoutManager.measurePreparedLayout(
|
||||
preparedLayout,
|
||||
getYogaSize(minWidth, maxWidth),
|
||||
getYogaMeasureMode(minWidth, maxWidth),
|
||||
getYogaSize(minHeight, maxHeight),
|
||||
getYogaMeasureMode(minHeight, maxHeight));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param surfaceId {@link int} surface ID
|
||||
* @param defaultTextInputPadding {@link float[]} output parameter will contain the default theme
|
||||
|
||||
+7
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<31638ef8ac6992a354785b74af8fbe0d>>
|
||||
* @generated SignedSource<<453f8c0a593b173c197fcf54ed834a1b>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -180,6 +180,12 @@ public object ReactNativeFeatureFlags {
|
||||
@JvmStatic
|
||||
public fun enableNewBackgroundAndBorderDrawables(): Boolean = accessor.enableNewBackgroundAndBorderDrawables()
|
||||
|
||||
/**
|
||||
* Enables caching text layout artifacts for later reuse
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun enablePreparedTextLayout(): Boolean = accessor.enablePreparedTextLayout()
|
||||
|
||||
/**
|
||||
* When enabled, Android will receive prop updates based on the differences between the last rendered shadow node and the last committed shadow node.
|
||||
*/
|
||||
|
||||
+11
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<6b382661025db56592b44255f5a8694c>>
|
||||
* @generated SignedSource<<a51441451ec25033040ba044ee3371fc>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -45,6 +45,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
|
||||
private var enableNativeCSSParsingCache: Boolean? = null
|
||||
private var enableNetworkEventReportingCache: Boolean? = null
|
||||
private var enableNewBackgroundAndBorderDrawablesCache: Boolean? = null
|
||||
private var enablePreparedTextLayoutCache: Boolean? = null
|
||||
private var enablePropsUpdateReconciliationAndroidCache: Boolean? = null
|
||||
private var enableResourceTimingAPICache: Boolean? = null
|
||||
private var enableSynchronousStateUpdatesCache: Boolean? = null
|
||||
@@ -292,6 +293,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun enablePreparedTextLayout(): Boolean {
|
||||
var cached = enablePreparedTextLayoutCache
|
||||
if (cached == null) {
|
||||
cached = ReactNativeFeatureFlagsCxxInterop.enablePreparedTextLayout()
|
||||
enablePreparedTextLayoutCache = cached
|
||||
}
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun enablePropsUpdateReconciliationAndroid(): Boolean {
|
||||
var cached = enablePropsUpdateReconciliationAndroidCache
|
||||
if (cached == null) {
|
||||
|
||||
+3
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<8276fd1166cdd235f11a0b490bb7d924>>
|
||||
* @generated SignedSource<<9d0b02395a08331bca956ea600602a31>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -78,6 +78,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun enableNewBackgroundAndBorderDrawables(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun enablePreparedTextLayout(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun enablePropsUpdateReconciliationAndroid(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun enableResourceTimingAPI(): Boolean
|
||||
|
||||
+3
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<ffadd7912aed2d95b0c6199aa8902690>>
|
||||
* @generated SignedSource<<cf12cdfdfb343e79247379b5549ae92a>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -73,6 +73,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
|
||||
|
||||
override fun enableNewBackgroundAndBorderDrawables(): Boolean = true
|
||||
|
||||
override fun enablePreparedTextLayout(): Boolean = false
|
||||
|
||||
override fun enablePropsUpdateReconciliationAndroid(): Boolean = false
|
||||
|
||||
override fun enableResourceTimingAPI(): Boolean = false
|
||||
|
||||
+12
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<5dc41059d71d3a345be45a6a233b05a0>>
|
||||
* @generated SignedSource<<4c81ed8a06c192eb4007219d163650e5>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -49,6 +49,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
|
||||
private var enableNativeCSSParsingCache: Boolean? = null
|
||||
private var enableNetworkEventReportingCache: Boolean? = null
|
||||
private var enableNewBackgroundAndBorderDrawablesCache: Boolean? = null
|
||||
private var enablePreparedTextLayoutCache: Boolean? = null
|
||||
private var enablePropsUpdateReconciliationAndroidCache: Boolean? = null
|
||||
private var enableResourceTimingAPICache: Boolean? = null
|
||||
private var enableSynchronousStateUpdatesCache: Boolean? = null
|
||||
@@ -321,6 +322,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun enablePreparedTextLayout(): Boolean {
|
||||
var cached = enablePreparedTextLayoutCache
|
||||
if (cached == null) {
|
||||
cached = currentProvider.enablePreparedTextLayout()
|
||||
accessedFeatureFlags.add("enablePreparedTextLayout")
|
||||
enablePreparedTextLayoutCache = cached
|
||||
}
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun enablePropsUpdateReconciliationAndroid(): Boolean {
|
||||
var cached = enablePropsUpdateReconciliationAndroidCache
|
||||
if (cached == null) {
|
||||
|
||||
+3
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<35f6d9ae3c445c81fc4bab7509fd3179>>
|
||||
* @generated SignedSource<<2482f57e0652cfaa4806b5333c50ad9f>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -73,6 +73,8 @@ public interface ReactNativeFeatureFlagsProvider {
|
||||
|
||||
@DoNotStrip public fun enableNewBackgroundAndBorderDrawables(): Boolean
|
||||
|
||||
@DoNotStrip public fun enablePreparedTextLayout(): Boolean
|
||||
|
||||
@DoNotStrip public fun enablePropsUpdateReconciliationAndroid(): Boolean
|
||||
|
||||
@DoNotStrip public fun enableResourceTimingAPI(): Boolean
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.facebook.react.views.text
|
||||
|
||||
import android.text.Layout
|
||||
import com.facebook.proguard.annotations.DoNotStrip
|
||||
|
||||
/**
|
||||
* Encapsulates an {android.text.Layout} along with any additional state needed to render or measure
|
||||
* it.
|
||||
*/
|
||||
@DoNotStrip
|
||||
internal class PreparedLayout(public val layout: Layout, public val maximumNumberOfLines: Int)
|
||||
+44
-1
@@ -31,6 +31,7 @@ import com.facebook.react.bridge.ReactNoCrashSoftException;
|
||||
import com.facebook.react.bridge.ReactSoftExceptionLogger;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.common.annotations.UnstableReactNativeAPI;
|
||||
import com.facebook.react.common.mapbuffer.MapBuffer;
|
||||
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
@@ -503,7 +504,8 @@ public class TextLayoutManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static Layout createLayout(
|
||||
@UnstableReactNativeAPI
|
||||
public static Layout createLayout(
|
||||
@NonNull Context context,
|
||||
MapBuffer attributedString,
|
||||
MapBuffer paragraphAttributes,
|
||||
@@ -732,6 +734,47 @@ public class TextLayoutManager {
|
||||
return YogaMeasureOutput.make(widthInSP, heightInSP);
|
||||
}
|
||||
|
||||
@UnstableReactNativeAPI
|
||||
public static float[] measurePreparedLayout(
|
||||
PreparedLayout preparedLayout,
|
||||
float width,
|
||||
YogaMeasureMode widthYogaMeasureMode,
|
||||
float height,
|
||||
YogaMeasureMode heightYogaMeasureMode) {
|
||||
Layout layout = preparedLayout.getLayout();
|
||||
Spanned text = (Spanned) layout.getText();
|
||||
int maximumNumberOfLines = preparedLayout.getMaximumNumberOfLines();
|
||||
|
||||
int calculatedLineCount = calculateLineCount(layout, maximumNumberOfLines);
|
||||
float calculatedWidth =
|
||||
calculateWidth(layout, text, width, widthYogaMeasureMode, calculatedLineCount);
|
||||
float calculatedHeight =
|
||||
calculateHeight(layout, text, height, heightYogaMeasureMode, calculatedLineCount);
|
||||
|
||||
ArrayList<Float> retList = new ArrayList<>();
|
||||
retList.add(PixelUtil.toDIPFromPixel(calculatedWidth));
|
||||
retList.add(PixelUtil.toDIPFromPixel(calculatedHeight));
|
||||
|
||||
AttachmentMetrics metrics = new AttachmentMetrics();
|
||||
int lastAttachmentFoundInSpan;
|
||||
for (int i = 0; i < text.length(); i = lastAttachmentFoundInSpan) {
|
||||
lastAttachmentFoundInSpan =
|
||||
nextAttachmentMetrics(layout, text, calculatedWidth, calculatedLineCount, i, metrics);
|
||||
if (metrics.wasFound) {
|
||||
retList.add(PixelUtil.toDIPFromPixel(metrics.top));
|
||||
retList.add(PixelUtil.toDIPFromPixel(metrics.left));
|
||||
retList.add(PixelUtil.toDIPFromPixel(metrics.width));
|
||||
retList.add(PixelUtil.toDIPFromPixel(metrics.height));
|
||||
}
|
||||
}
|
||||
|
||||
float[] ret = new float[retList.size()];
|
||||
for (int i = 0; i < retList.size(); i++) {
|
||||
ret[i] = retList.get(i);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static int calculateLineCount(Layout layout, int maximumNumberOfLines) {
|
||||
return maximumNumberOfLines == ReactConstants.UNSET || maximumNumberOfLines == 0
|
||||
? layout.getLineCount()
|
||||
|
||||
+15
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<d566c157b7db07e235bada071067f11d>>
|
||||
* @generated SignedSource<<49dbfe02e06cc5d6b12683ed91ea1d13>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -189,6 +189,12 @@ class ReactNativeFeatureFlagsJavaProvider
|
||||
return method(javaProvider_);
|
||||
}
|
||||
|
||||
bool enablePreparedTextLayout() override {
|
||||
static const auto method =
|
||||
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enablePreparedTextLayout");
|
||||
return method(javaProvider_);
|
||||
}
|
||||
|
||||
bool enablePropsUpdateReconciliationAndroid() override {
|
||||
static const auto method =
|
||||
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enablePropsUpdateReconciliationAndroid");
|
||||
@@ -444,6 +450,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableNewBackgroundAndBorderDrawables(
|
||||
return ReactNativeFeatureFlags::enableNewBackgroundAndBorderDrawables();
|
||||
}
|
||||
|
||||
bool JReactNativeFeatureFlagsCxxInterop::enablePreparedTextLayout(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
|
||||
return ReactNativeFeatureFlags::enablePreparedTextLayout();
|
||||
}
|
||||
|
||||
bool JReactNativeFeatureFlagsCxxInterop::enablePropsUpdateReconciliationAndroid(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
|
||||
return ReactNativeFeatureFlags::enablePropsUpdateReconciliationAndroid();
|
||||
@@ -655,6 +666,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
|
||||
makeNativeMethod(
|
||||
"enableNewBackgroundAndBorderDrawables",
|
||||
JReactNativeFeatureFlagsCxxInterop::enableNewBackgroundAndBorderDrawables),
|
||||
makeNativeMethod(
|
||||
"enablePreparedTextLayout",
|
||||
JReactNativeFeatureFlagsCxxInterop::enablePreparedTextLayout),
|
||||
makeNativeMethod(
|
||||
"enablePropsUpdateReconciliationAndroid",
|
||||
JReactNativeFeatureFlagsCxxInterop::enablePropsUpdateReconciliationAndroid),
|
||||
|
||||
+4
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<270b461fa199f8e6365948cded0785ad>>
|
||||
* @generated SignedSource<<ae23312f2dccee934a8a91c05625662a>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -105,6 +105,9 @@ class JReactNativeFeatureFlagsCxxInterop
|
||||
static bool enableNewBackgroundAndBorderDrawables(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
|
||||
|
||||
static bool enablePreparedTextLayout(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
|
||||
|
||||
static bool enablePropsUpdateReconciliationAndroid(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<74f22c6a0302a9923e99d2455d84e231>>
|
||||
* @generated SignedSource<<8410561a80edd67b4528181b1f8557fe>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -126,6 +126,10 @@ bool ReactNativeFeatureFlags::enableNewBackgroundAndBorderDrawables() {
|
||||
return getAccessor().enableNewBackgroundAndBorderDrawables();
|
||||
}
|
||||
|
||||
bool ReactNativeFeatureFlags::enablePreparedTextLayout() {
|
||||
return getAccessor().enablePreparedTextLayout();
|
||||
}
|
||||
|
||||
bool ReactNativeFeatureFlags::enablePropsUpdateReconciliationAndroid() {
|
||||
return getAccessor().enablePropsUpdateReconciliationAndroid();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<b941543bc6f12dbb0e12ac2ae0042186>>
|
||||
* @generated SignedSource<<8b135b02d868914f6b3487f09e8955ff>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -164,6 +164,11 @@ class ReactNativeFeatureFlags {
|
||||
*/
|
||||
RN_EXPORT static bool enableNewBackgroundAndBorderDrawables();
|
||||
|
||||
/**
|
||||
* Enables caching text layout artifacts for later reuse
|
||||
*/
|
||||
RN_EXPORT static bool enablePreparedTextLayout();
|
||||
|
||||
/**
|
||||
* When enabled, Android will receive prop updates based on the differences between the last rendered shadow node and the last committed shadow node.
|
||||
*/
|
||||
|
||||
+40
-22
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<4efbb3f094edbcc89c6ac4d586169b9a>>
|
||||
* @generated SignedSource<<18cf18662a540ff1bc0f151cd53bcb0d>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -479,6 +479,24 @@ bool ReactNativeFeatureFlagsAccessor::enableNewBackgroundAndBorderDrawables() {
|
||||
return flagValue.value();
|
||||
}
|
||||
|
||||
bool ReactNativeFeatureFlagsAccessor::enablePreparedTextLayout() {
|
||||
auto flagValue = enablePreparedTextLayout_.load();
|
||||
|
||||
if (!flagValue.has_value()) {
|
||||
// This block is not exclusive but it is not necessary.
|
||||
// If multiple threads try to initialize the feature flag, we would only
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(25, "enablePreparedTextLayout");
|
||||
|
||||
flagValue = currentProvider_->enablePreparedTextLayout();
|
||||
enablePreparedTextLayout_ = flagValue;
|
||||
}
|
||||
|
||||
return flagValue.value();
|
||||
}
|
||||
|
||||
bool ReactNativeFeatureFlagsAccessor::enablePropsUpdateReconciliationAndroid() {
|
||||
auto flagValue = enablePropsUpdateReconciliationAndroid_.load();
|
||||
|
||||
@@ -488,7 +506,7 @@ bool ReactNativeFeatureFlagsAccessor::enablePropsUpdateReconciliationAndroid() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(25, "enablePropsUpdateReconciliationAndroid");
|
||||
markFlagAsAccessed(26, "enablePropsUpdateReconciliationAndroid");
|
||||
|
||||
flagValue = currentProvider_->enablePropsUpdateReconciliationAndroid();
|
||||
enablePropsUpdateReconciliationAndroid_ = flagValue;
|
||||
@@ -506,7 +524,7 @@ bool ReactNativeFeatureFlagsAccessor::enableResourceTimingAPI() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(26, "enableResourceTimingAPI");
|
||||
markFlagAsAccessed(27, "enableResourceTimingAPI");
|
||||
|
||||
flagValue = currentProvider_->enableResourceTimingAPI();
|
||||
enableResourceTimingAPI_ = flagValue;
|
||||
@@ -524,7 +542,7 @@ bool ReactNativeFeatureFlagsAccessor::enableSynchronousStateUpdates() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(27, "enableSynchronousStateUpdates");
|
||||
markFlagAsAccessed(28, "enableSynchronousStateUpdates");
|
||||
|
||||
flagValue = currentProvider_->enableSynchronousStateUpdates();
|
||||
enableSynchronousStateUpdates_ = flagValue;
|
||||
@@ -542,7 +560,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewCulling() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(28, "enableViewCulling");
|
||||
markFlagAsAccessed(29, "enableViewCulling");
|
||||
|
||||
flagValue = currentProvider_->enableViewCulling();
|
||||
enableViewCulling_ = flagValue;
|
||||
@@ -560,7 +578,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecycling() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(29, "enableViewRecycling");
|
||||
markFlagAsAccessed(30, "enableViewRecycling");
|
||||
|
||||
flagValue = currentProvider_->enableViewRecycling();
|
||||
enableViewRecycling_ = flagValue;
|
||||
@@ -578,7 +596,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForText() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(30, "enableViewRecyclingForText");
|
||||
markFlagAsAccessed(31, "enableViewRecyclingForText");
|
||||
|
||||
flagValue = currentProvider_->enableViewRecyclingForText();
|
||||
enableViewRecyclingForText_ = flagValue;
|
||||
@@ -596,7 +614,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForView() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(31, "enableViewRecyclingForView");
|
||||
markFlagAsAccessed(32, "enableViewRecyclingForView");
|
||||
|
||||
flagValue = currentProvider_->enableViewRecyclingForView();
|
||||
enableViewRecyclingForView_ = flagValue;
|
||||
@@ -614,7 +632,7 @@ bool ReactNativeFeatureFlagsAccessor::fixMappingOfEventPrioritiesBetweenFabricAn
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(32, "fixMappingOfEventPrioritiesBetweenFabricAndReact");
|
||||
markFlagAsAccessed(33, "fixMappingOfEventPrioritiesBetweenFabricAndReact");
|
||||
|
||||
flagValue = currentProvider_->fixMappingOfEventPrioritiesBetweenFabricAndReact();
|
||||
fixMappingOfEventPrioritiesBetweenFabricAndReact_ = flagValue;
|
||||
@@ -632,7 +650,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxEnabledRelease() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(33, "fuseboxEnabledRelease");
|
||||
markFlagAsAccessed(34, "fuseboxEnabledRelease");
|
||||
|
||||
flagValue = currentProvider_->fuseboxEnabledRelease();
|
||||
fuseboxEnabledRelease_ = flagValue;
|
||||
@@ -650,7 +668,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxNetworkInspectionEnabled() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(34, "fuseboxNetworkInspectionEnabled");
|
||||
markFlagAsAccessed(35, "fuseboxNetworkInspectionEnabled");
|
||||
|
||||
flagValue = currentProvider_->fuseboxNetworkInspectionEnabled();
|
||||
fuseboxNetworkInspectionEnabled_ = flagValue;
|
||||
@@ -668,7 +686,7 @@ bool ReactNativeFeatureFlagsAccessor::incorporateMaxLinesDuringAndroidLayout() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(35, "incorporateMaxLinesDuringAndroidLayout");
|
||||
markFlagAsAccessed(36, "incorporateMaxLinesDuringAndroidLayout");
|
||||
|
||||
flagValue = currentProvider_->incorporateMaxLinesDuringAndroidLayout();
|
||||
incorporateMaxLinesDuringAndroidLayout_ = flagValue;
|
||||
@@ -686,7 +704,7 @@ bool ReactNativeFeatureFlagsAccessor::traceTurboModulePromiseRejectionsOnAndroid
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(36, "traceTurboModulePromiseRejectionsOnAndroid");
|
||||
markFlagAsAccessed(37, "traceTurboModulePromiseRejectionsOnAndroid");
|
||||
|
||||
flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid();
|
||||
traceTurboModulePromiseRejectionsOnAndroid_ = flagValue;
|
||||
@@ -704,7 +722,7 @@ bool ReactNativeFeatureFlagsAccessor::updateRuntimeShadowNodeReferencesOnCommit(
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(37, "updateRuntimeShadowNodeReferencesOnCommit");
|
||||
markFlagAsAccessed(38, "updateRuntimeShadowNodeReferencesOnCommit");
|
||||
|
||||
flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit();
|
||||
updateRuntimeShadowNodeReferencesOnCommit_ = flagValue;
|
||||
@@ -722,7 +740,7 @@ bool ReactNativeFeatureFlagsAccessor::useAlwaysAvailableJSErrorHandling() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(38, "useAlwaysAvailableJSErrorHandling");
|
||||
markFlagAsAccessed(39, "useAlwaysAvailableJSErrorHandling");
|
||||
|
||||
flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling();
|
||||
useAlwaysAvailableJSErrorHandling_ = flagValue;
|
||||
@@ -740,7 +758,7 @@ bool ReactNativeFeatureFlagsAccessor::useFabricInterop() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(39, "useFabricInterop");
|
||||
markFlagAsAccessed(40, "useFabricInterop");
|
||||
|
||||
flagValue = currentProvider_->useFabricInterop();
|
||||
useFabricInterop_ = flagValue;
|
||||
@@ -758,7 +776,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeViewConfigsInBridgelessMode() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(40, "useNativeViewConfigsInBridgelessMode");
|
||||
markFlagAsAccessed(41, "useNativeViewConfigsInBridgelessMode");
|
||||
|
||||
flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode();
|
||||
useNativeViewConfigsInBridgelessMode_ = flagValue;
|
||||
@@ -776,7 +794,7 @@ bool ReactNativeFeatureFlagsAccessor::useOptimizedEventBatchingOnAndroid() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(41, "useOptimizedEventBatchingOnAndroid");
|
||||
markFlagAsAccessed(42, "useOptimizedEventBatchingOnAndroid");
|
||||
|
||||
flagValue = currentProvider_->useOptimizedEventBatchingOnAndroid();
|
||||
useOptimizedEventBatchingOnAndroid_ = flagValue;
|
||||
@@ -794,7 +812,7 @@ bool ReactNativeFeatureFlagsAccessor::useRawPropsJsiValue() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(42, "useRawPropsJsiValue");
|
||||
markFlagAsAccessed(43, "useRawPropsJsiValue");
|
||||
|
||||
flagValue = currentProvider_->useRawPropsJsiValue();
|
||||
useRawPropsJsiValue_ = flagValue;
|
||||
@@ -812,7 +830,7 @@ bool ReactNativeFeatureFlagsAccessor::useShadowNodeStateOnClone() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(43, "useShadowNodeStateOnClone");
|
||||
markFlagAsAccessed(44, "useShadowNodeStateOnClone");
|
||||
|
||||
flagValue = currentProvider_->useShadowNodeStateOnClone();
|
||||
useShadowNodeStateOnClone_ = flagValue;
|
||||
@@ -830,7 +848,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModuleInterop() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(44, "useTurboModuleInterop");
|
||||
markFlagAsAccessed(45, "useTurboModuleInterop");
|
||||
|
||||
flagValue = currentProvider_->useTurboModuleInterop();
|
||||
useTurboModuleInterop_ = flagValue;
|
||||
@@ -848,7 +866,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModules() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(45, "useTurboModules");
|
||||
markFlagAsAccessed(46, "useTurboModules");
|
||||
|
||||
flagValue = currentProvider_->useTurboModules();
|
||||
useTurboModules_ = flagValue;
|
||||
|
||||
+4
-2
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<31b5c2776ad91fd0d3bd2cfc3672575b>>
|
||||
* @generated SignedSource<<2736c730fcd0b82717faa0fea527d007>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -57,6 +57,7 @@ class ReactNativeFeatureFlagsAccessor {
|
||||
bool enableNativeCSSParsing();
|
||||
bool enableNetworkEventReporting();
|
||||
bool enableNewBackgroundAndBorderDrawables();
|
||||
bool enablePreparedTextLayout();
|
||||
bool enablePropsUpdateReconciliationAndroid();
|
||||
bool enableResourceTimingAPI();
|
||||
bool enableSynchronousStateUpdates();
|
||||
@@ -89,7 +90,7 @@ class ReactNativeFeatureFlagsAccessor {
|
||||
std::unique_ptr<ReactNativeFeatureFlagsProvider> currentProvider_;
|
||||
bool wasOverridden_;
|
||||
|
||||
std::array<std::atomic<const char*>, 46> accessedFeatureFlags_;
|
||||
std::array<std::atomic<const char*>, 47> accessedFeatureFlags_;
|
||||
|
||||
std::atomic<std::optional<bool>> commonTestFlag_;
|
||||
std::atomic<std::optional<bool>> animatedShouldSignalBatch_;
|
||||
@@ -116,6 +117,7 @@ class ReactNativeFeatureFlagsAccessor {
|
||||
std::atomic<std::optional<bool>> enableNativeCSSParsing_;
|
||||
std::atomic<std::optional<bool>> enableNetworkEventReporting_;
|
||||
std::atomic<std::optional<bool>> enableNewBackgroundAndBorderDrawables_;
|
||||
std::atomic<std::optional<bool>> enablePreparedTextLayout_;
|
||||
std::atomic<std::optional<bool>> enablePropsUpdateReconciliationAndroid_;
|
||||
std::atomic<std::optional<bool>> enableResourceTimingAPI_;
|
||||
std::atomic<std::optional<bool>> enableSynchronousStateUpdates_;
|
||||
|
||||
+5
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<9cc33ba9f5ef67d6c98700d772bbc0de>>
|
||||
* @generated SignedSource<<ef056e269a4ed514c18ebaed89885661>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -127,6 +127,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool enablePreparedTextLayout() override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool enablePropsUpdateReconciliationAndroid() override {
|
||||
return false;
|
||||
}
|
||||
|
||||
+10
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<d336ecdd8ce51394df6d7baf6ea6457a>>
|
||||
* @generated SignedSource<<53e92dc49c9d5b93de14cde2fd319e92>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -270,6 +270,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef
|
||||
return ReactNativeFeatureFlagsDefaults::enableNewBackgroundAndBorderDrawables();
|
||||
}
|
||||
|
||||
bool enablePreparedTextLayout() override {
|
||||
auto value = values_["enablePreparedTextLayout"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enablePreparedTextLayout();
|
||||
}
|
||||
|
||||
bool enablePropsUpdateReconciliationAndroid() override {
|
||||
auto value = values_["enablePropsUpdateReconciliationAndroid"];
|
||||
if (!value.isNull()) {
|
||||
|
||||
+2
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<ac9ae2b709bdc6358523ebf91fc2f78e>>
|
||||
* @generated SignedSource<<7ad3c1ab9913ee5b20d0f6217580a14c>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -50,6 +50,7 @@ class ReactNativeFeatureFlagsProvider {
|
||||
virtual bool enableNativeCSSParsing() = 0;
|
||||
virtual bool enableNetworkEventReporting() = 0;
|
||||
virtual bool enableNewBackgroundAndBorderDrawables() = 0;
|
||||
virtual bool enablePreparedTextLayout() = 0;
|
||||
virtual bool enablePropsUpdateReconciliationAndroid() = 0;
|
||||
virtual bool enableResourceTimingAPI() = 0;
|
||||
virtual bool enableSynchronousStateUpdates() = 0;
|
||||
|
||||
+6
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<82e2e7118453787377248101925b56d1>>
|
||||
* @generated SignedSource<<cf54fabb5023a846cadd14482ea80999>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -169,6 +169,11 @@ bool NativeReactNativeFeatureFlags::enableNewBackgroundAndBorderDrawables(
|
||||
return ReactNativeFeatureFlags::enableNewBackgroundAndBorderDrawables();
|
||||
}
|
||||
|
||||
bool NativeReactNativeFeatureFlags::enablePreparedTextLayout(
|
||||
jsi::Runtime& /*runtime*/) {
|
||||
return ReactNativeFeatureFlags::enablePreparedTextLayout();
|
||||
}
|
||||
|
||||
bool NativeReactNativeFeatureFlags::enablePropsUpdateReconciliationAndroid(
|
||||
jsi::Runtime& /*runtime*/) {
|
||||
return ReactNativeFeatureFlags::enablePropsUpdateReconciliationAndroid();
|
||||
|
||||
+3
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<d0d056652d60269b736d4fb0bc71ab4f>>
|
||||
* @generated SignedSource<<4169b34b13c62ab90d7976de908b5f16>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -87,6 +87,8 @@ class NativeReactNativeFeatureFlags
|
||||
|
||||
bool enableNewBackgroundAndBorderDrawables(jsi::Runtime& runtime);
|
||||
|
||||
bool enablePreparedTextLayout(jsi::Runtime& runtime);
|
||||
|
||||
bool enablePropsUpdateReconciliationAndroid(jsi::Runtime& runtime);
|
||||
|
||||
bool enableResourceTimingAPI(jsi::Runtime& runtime);
|
||||
|
||||
+45
-15
@@ -165,15 +165,23 @@ void ParagraphShadowNode::updateStateIfNeeded(const Content& content) {
|
||||
Size ParagraphShadowNode::measureContent(
|
||||
const LayoutContext& layoutContext,
|
||||
const LayoutConstraints& layoutConstraints) const {
|
||||
if constexpr (TextLayoutManagerExtended::supportsPreparedLayout()) {
|
||||
for (const auto& preparedLayout : preparedLayouts_) {
|
||||
if (preparedLayout.layoutConstraints == layoutConstraints) {
|
||||
return preparedLayout.measureSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto content =
|
||||
getContentWithMeasuredAttachments(layoutContext, layoutConstraints);
|
||||
|
||||
auto attributedString = content.attributedString;
|
||||
if (attributedString.isEmpty()) {
|
||||
// Note: `zero-width space` is insufficient in some cases (e.g. when we need
|
||||
// to measure the "height" of the font).
|
||||
// TODO T67606511: We will redefine the measurement of empty strings as part
|
||||
// of T67606511
|
||||
// Note: `zero-width space` is insufficient in some cases (e.g. when we
|
||||
// need to measure the "height" of the font).
|
||||
// TODO T67606511: We will redefine the measurement of empty strings as
|
||||
// part of T67606511
|
||||
auto string = BaseTextShadowNode::getEmptyPlaceholder();
|
||||
auto textAttributes = TextAttributes::defaultTextAttributes();
|
||||
textAttributes.fontSizeMultiplier = layoutContext.fontSizeMultiplier;
|
||||
@@ -186,6 +194,28 @@ Size ParagraphShadowNode::measureContent(
|
||||
.surfaceId = getSurfaceId(),
|
||||
};
|
||||
|
||||
if constexpr (TextLayoutManagerExtended::supportsPreparedLayout()) {
|
||||
if (ReactNativeFeatureFlags::enablePreparedTextLayout()) {
|
||||
TextLayoutManagerExtended tme(*textLayoutManager_);
|
||||
|
||||
auto preparedLayout = tme.prepareLayout(
|
||||
attributedString,
|
||||
content.paragraphAttributes,
|
||||
textLayoutContext,
|
||||
layoutConstraints);
|
||||
auto mesaurements = tme.measurePreparedLayout(
|
||||
preparedLayout, textLayoutContext, layoutConstraints);
|
||||
|
||||
preparedLayouts_.push_back(PreparedLayoutResult{
|
||||
layoutConstraints,
|
||||
mesaurements.size,
|
||||
// PreparedLayout is not trivially copyable on all platforms
|
||||
// NOLINTNEXTLINE(performance-move-const-arg)
|
||||
std::move(preparedLayout)});
|
||||
return mesaurements.size;
|
||||
}
|
||||
}
|
||||
|
||||
return textLayoutManager_
|
||||
->measure(
|
||||
AttributedStringBox{attributedString},
|
||||
@@ -206,10 +236,10 @@ Float ParagraphShadowNode::baseline(
|
||||
auto attributedString = content.attributedString;
|
||||
|
||||
if (attributedString.isEmpty()) {
|
||||
// Note: `zero-width space` is insufficient in some cases (e.g. when we need
|
||||
// to measure the "height" of the font).
|
||||
// TODO T67606511: We will redefine the measurement of empty strings as part
|
||||
// of T67606511
|
||||
// Note: `zero-width space` is insufficient in some cases (e.g. when we
|
||||
// need to measure the "height" of the font).
|
||||
// TODO T67606511: We will redefine the measurement of empty strings as
|
||||
// part of T67606511
|
||||
auto string = BaseTextShadowNode::getEmptyPlaceholder();
|
||||
auto textAttributes = TextAttributes::defaultTextAttributes();
|
||||
textAttributes.fontSizeMultiplier = layoutContext.fontSizeMultiplier;
|
||||
@@ -281,8 +311,8 @@ void ParagraphShadowNode::layout(LayoutContext layoutContext) {
|
||||
// `paragraphShadowNode` that represents clones of `this` object.
|
||||
auto paragraphShadowNode = static_cast<ParagraphShadowNode*>(this);
|
||||
// `paragraphOwningShadowNode` is owning pointer to`paragraphShadowNode`
|
||||
// (besides the initial case when `paragraphShadowNode == this`), we need this
|
||||
// only to keep it in memory for a while.
|
||||
// (besides the initial case when `paragraphShadowNode == this`), we need
|
||||
// this only to keep it in memory for a while.
|
||||
auto paragraphOwningShadowNode = ShadowNode::Unshared{};
|
||||
|
||||
react_native_assert(
|
||||
@@ -313,7 +343,7 @@ void ParagraphShadowNode::layout(LayoutContext layoutContext) {
|
||||
const auto& attachmentMeasurement = measurement.attachments[i];
|
||||
if (attachmentMeasurement.isClipped) {
|
||||
layoutableShadowNode.setLayoutMetrics(
|
||||
LayoutMetrics{.displayType = DisplayType::None});
|
||||
LayoutMetrics{.frame = {}, .displayType = DisplayType::None});
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -333,15 +363,15 @@ void ParagraphShadowNode::layout(LayoutContext layoutContext) {
|
||||
layoutableShadowNode.layoutTree(
|
||||
attachmentLayoutContext, attachmentLayoutConstrains);
|
||||
|
||||
// Altering the origin of the `ShadowNode` (which is defined by text layout,
|
||||
// not by internal styles and state).
|
||||
// Altering the origin of the `ShadowNode` (which is defined by text
|
||||
// layout, not by internal styles and state).
|
||||
auto attachmentLayoutMetrics = layoutableShadowNode.getLayoutMetrics();
|
||||
attachmentLayoutMetrics.frame.origin = attachmentOrigin;
|
||||
layoutableShadowNode.setLayoutMetrics(attachmentLayoutMetrics);
|
||||
}
|
||||
|
||||
// If we ended up cloning something, we need to update the list of children to
|
||||
// reflect the changes that we made.
|
||||
// If we ended up cloning something, we need to update the list of children
|
||||
// to reflect the changes that we made.
|
||||
if (paragraphShadowNode != this) {
|
||||
this->children_ =
|
||||
static_cast<const ParagraphShadowNode*>(paragraphShadowNode)->children_;
|
||||
|
||||
+13
@@ -15,6 +15,7 @@
|
||||
#include <react/renderer/core/LayoutContext.h>
|
||||
#include <react/renderer/core/ShadowNode.h>
|
||||
#include <react/renderer/textlayoutmanager/TextLayoutManager.h>
|
||||
#include <react/renderer/textlayoutmanager/TextLayoutManagerExtended.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
@@ -115,6 +116,18 @@ class ParagraphShadowNode final : public ConcreteViewShadowNode<
|
||||
* Cached content of the subtree started from the node.
|
||||
*/
|
||||
mutable std::optional<Content> content_{};
|
||||
|
||||
/*
|
||||
* Intermediate layout results generated during measurement, that may be
|
||||
* reused by the platform.
|
||||
*/
|
||||
struct PreparedLayoutResult {
|
||||
LayoutConstraints layoutConstraints;
|
||||
Size measureSize;
|
||||
TextLayoutManagerExtended::PreparedLayout preparedLayout{};
|
||||
};
|
||||
|
||||
mutable std::vector<PreparedLayoutResult> preparedLayouts_;
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
+63
@@ -8,6 +8,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <glog/logging.h>
|
||||
#include <cstddef>
|
||||
|
||||
#include <react/renderer/attributedstring/AttributedStringBox.h>
|
||||
#include <react/renderer/attributedstring/ParagraphAttributes.h>
|
||||
@@ -17,7 +18,36 @@
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
template <typename TextLayoutManagerT>
|
||||
concept TextLayoutManagerWithPreparedLayout = requires(
|
||||
TextLayoutManagerT textLayoutManager,
|
||||
AttributedString attributedString,
|
||||
ParagraphAttributes paragraphAttributes,
|
||||
TextLayoutContext layoutContext,
|
||||
LayoutConstraints layoutConstraints,
|
||||
typename TextLayoutManagerT::PreparedLayout preparedLayout) {
|
||||
sizeof(typename TextLayoutManagerT::PreparedLayout);
|
||||
{
|
||||
textLayoutManager.prepareLayout(
|
||||
attributedString, paragraphAttributes, layoutContext, layoutConstraints)
|
||||
} -> std::same_as<typename TextLayoutManagerT::PreparedLayout>;
|
||||
{
|
||||
textLayoutManager.measurePreparedLayout(
|
||||
preparedLayout, layoutContext, layoutConstraints)
|
||||
} -> std::same_as<TextMeasurement>;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
template <typename T>
|
||||
struct PreparedLayoutT {
|
||||
using type = std::nullptr_t;
|
||||
};
|
||||
|
||||
template <TextLayoutManagerWithPreparedLayout T>
|
||||
struct PreparedLayoutT<T> {
|
||||
using type = typename T::PreparedLayout;
|
||||
};
|
||||
|
||||
/**
|
||||
* TextLayoutManagerExtended acts as an adapter for TextLayoutManager methods
|
||||
* which may not exist for a specific platform. Callers can check at
|
||||
@@ -36,6 +66,12 @@ class TextLayoutManagerExtended {
|
||||
};
|
||||
}
|
||||
|
||||
static constexpr bool supportsPreparedLayout() {
|
||||
return TextLayoutManagerWithPreparedLayout<TextLayoutManagerT>;
|
||||
}
|
||||
|
||||
using PreparedLayout = typename PreparedLayoutT<TextLayoutManagerT>::type;
|
||||
|
||||
TextLayoutManagerExtended(const TextLayoutManagerT& textLayoutManager)
|
||||
: textLayoutManager_(textLayoutManager) {}
|
||||
|
||||
@@ -50,6 +86,33 @@ class TextLayoutManagerExtended {
|
||||
LOG(FATAL) << "Platform TextLayoutManager does not support measureLines";
|
||||
}
|
||||
|
||||
PreparedLayout prepareLayout(
|
||||
const AttributedString& attributedString,
|
||||
const ParagraphAttributes& paragraphAttributes,
|
||||
const TextLayoutContext& layoutContext,
|
||||
const LayoutConstraints& layoutConstraints) const {
|
||||
if constexpr (supportsPreparedLayout()) {
|
||||
return textLayoutManager_.prepareLayout(
|
||||
attributedString,
|
||||
paragraphAttributes,
|
||||
layoutContext,
|
||||
layoutConstraints);
|
||||
}
|
||||
LOG(FATAL) << "Platform TextLayoutManager does not support prepareLayout";
|
||||
}
|
||||
|
||||
TextMeasurement measurePreparedLayout(
|
||||
const PreparedLayout& layout,
|
||||
const TextLayoutContext& layoutContext,
|
||||
const LayoutConstraints& layoutConstraints) const {
|
||||
if constexpr (supportsPreparedLayout()) {
|
||||
return textLayoutManager_.measurePreparedLayout(
|
||||
layout, layoutContext, layoutConstraints);
|
||||
}
|
||||
LOG(FATAL)
|
||||
<< "Platform TextLayoutManager does not support measurePreparedLayout";
|
||||
}
|
||||
|
||||
private:
|
||||
const TextLayoutManagerT& textLayoutManager_;
|
||||
};
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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 <fbjni/fbjni.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
class JPreparedLayout : public jni::JavaClass<JPreparedLayout> {
|
||||
public:
|
||||
static auto constexpr kJavaDescriptor =
|
||||
"Lcom/facebook/react/views/text/PreparedLayout;";
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
+97
-2
@@ -5,21 +5,25 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "TextLayoutManager.h"
|
||||
|
||||
#include <span>
|
||||
#include <utility>
|
||||
|
||||
#include <react/common/mapbuffer/JReadableMapBuffer.h>
|
||||
#include <react/debug/react_native_assert.h>
|
||||
#include <react/jni/ReadableNativeMap.h>
|
||||
#include <react/renderer/attributedstring/conversions.h>
|
||||
#include <react/renderer/core/conversions.h>
|
||||
#include <react/renderer/mapbuffer/MapBuffer.h>
|
||||
#include <react/renderer/mapbuffer/MapBufferBuilder.h>
|
||||
#include <react/renderer/telemetry/TransactionTelemetry.h>
|
||||
#include <react/renderer/textlayoutmanager/TextLayoutManager.h>
|
||||
#include <react/renderer/textlayoutmanager/TextLayoutManagerExtended.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
static_assert(TextLayoutManagerExtended::supportsLineMeasurement());
|
||||
static_assert(TextLayoutManagerExtended::supportsPreparedLayout());
|
||||
|
||||
namespace {
|
||||
|
||||
int countAttachments(const AttributedString& attributedString) {
|
||||
@@ -289,4 +293,95 @@ LinesMeasurements TextLayoutManager::measureLines(
|
||||
return lineMeasurements;
|
||||
}
|
||||
|
||||
TextLayoutManager::PreparedLayout TextLayoutManager::prepareLayout(
|
||||
const AttributedString& attributedString,
|
||||
const ParagraphAttributes& paragraphAttributes,
|
||||
const TextLayoutContext& layoutContext,
|
||||
const LayoutConstraints& layoutConstraints) const {
|
||||
const auto& fabricUIManager =
|
||||
contextContainer_->at<jni::global_ref<jobject>>("FabricUIManager");
|
||||
|
||||
static auto prepareLayout =
|
||||
jni::findClassStatic("com/facebook/react/fabric/FabricUIManager")
|
||||
->getMethod<JPreparedLayout::javaobject(
|
||||
jint,
|
||||
JReadableMapBuffer::javaobject,
|
||||
JReadableMapBuffer::javaobject,
|
||||
jfloat,
|
||||
jfloat)>("prepareLayout");
|
||||
|
||||
auto attributedStringMB =
|
||||
JReadableMapBuffer::createWithContents(toMapBuffer(attributedString));
|
||||
auto paragraphAttributesMB =
|
||||
JReadableMapBuffer::createWithContents(toMapBuffer(paragraphAttributes));
|
||||
|
||||
// T222682416: We don't have any global cache here. We should investigate
|
||||
// whether that is desirable
|
||||
return {jni::make_global(prepareLayout(
|
||||
fabricUIManager,
|
||||
layoutContext.surfaceId,
|
||||
attributedStringMB.get(),
|
||||
paragraphAttributesMB.get(),
|
||||
layoutConstraints.maximumSize.width,
|
||||
layoutConstraints.maximumSize.height))};
|
||||
}
|
||||
|
||||
TextMeasurement TextLayoutManager::measurePreparedLayout(
|
||||
const PreparedLayout& preparedLayout,
|
||||
const TextLayoutContext& /*layoutContext*/,
|
||||
const LayoutConstraints& layoutConstraints) const {
|
||||
const auto& fabricUIManager =
|
||||
contextContainer_->at<jni::global_ref<jobject>>("FabricUIManager");
|
||||
|
||||
static auto measurePreparedLayout =
|
||||
jni::findClassStatic("com/facebook/react/fabric/FabricUIManager")
|
||||
->getMethod<jni::JArrayFloat(
|
||||
JPreparedLayout::javaobject, jfloat, jfloat, jfloat, jfloat)>(
|
||||
"measurePreparedLayout");
|
||||
|
||||
auto minimumSize = layoutConstraints.minimumSize;
|
||||
auto maximumSize = layoutConstraints.maximumSize;
|
||||
|
||||
auto measurementsArr = measurePreparedLayout(
|
||||
fabricUIManager,
|
||||
preparedLayout.get(),
|
||||
minimumSize.width,
|
||||
maximumSize.width,
|
||||
minimumSize.height,
|
||||
maximumSize.height);
|
||||
auto measurements = measurementsArr->getRegion(
|
||||
0, static_cast<jsize>(measurementsArr->size()));
|
||||
|
||||
react_native_assert(measurementsArr->size() >= 2);
|
||||
react_native_assert((measurementsArr->size() - 2) % 4 == 0);
|
||||
|
||||
TextMeasurement textMeasurement;
|
||||
|
||||
textMeasurement.size.width = measurements[0];
|
||||
textMeasurement.size.height = measurements[1];
|
||||
|
||||
if (measurementsArr->size() > 2) {
|
||||
textMeasurement.attachments.reserve((measurementsArr->size() - 2) / 4);
|
||||
for (size_t i = 2; i < measurementsArr->size(); i += 4) {
|
||||
auto top = measurements[i];
|
||||
auto left = measurements[i + 1];
|
||||
auto width = measurements[i + 2];
|
||||
auto height = measurements[i + 3];
|
||||
|
||||
if (std::isnan(top) || std::isnan(left)) {
|
||||
textMeasurement.attachments.push_back(
|
||||
TextMeasurement::Attachment{.frame = Rect{}, .isClipped = true});
|
||||
} else {
|
||||
textMeasurement.attachments.push_back(TextMeasurement::Attachment{
|
||||
.frame =
|
||||
{.origin = {.x = left, .y = top},
|
||||
.size = {.width = width, .height = height}},
|
||||
.isClipped = false});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return textMeasurement;
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
+24
@@ -7,12 +7,16 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <react/jni/SafeReleaseJniRef.h>
|
||||
#include <react/renderer/attributedstring/AttributedStringBox.h>
|
||||
#include <react/renderer/attributedstring/ParagraphAttributes.h>
|
||||
#include <react/renderer/core/LayoutConstraints.h>
|
||||
#include <react/renderer/textlayoutmanager/JPreparedLayout.h>
|
||||
#include <react/renderer/textlayoutmanager/TextLayoutContext.h>
|
||||
#include <react/renderer/textlayoutmanager/TextMeasureCache.h>
|
||||
#include <react/utils/ContextContainer.h>
|
||||
|
||||
#include <fbjni/fbjni.h>
|
||||
#include <memory>
|
||||
|
||||
namespace facebook::react {
|
||||
@@ -25,6 +29,8 @@ class TextLayoutManager;
|
||||
*/
|
||||
class TextLayoutManager {
|
||||
public:
|
||||
using PreparedLayout = SafeReleaseJniRef<jni::global_ref<JPreparedLayout>>;
|
||||
|
||||
TextLayoutManager(const ContextContainer::Shared& contextContainer);
|
||||
|
||||
/*
|
||||
@@ -67,6 +73,24 @@ class TextLayoutManager {
|
||||
const ParagraphAttributes& paragraphAttributes,
|
||||
const Size& size) const;
|
||||
|
||||
/**
|
||||
* Create a platform representation of fully laid out text, to later be
|
||||
* reused.
|
||||
*/
|
||||
PreparedLayout prepareLayout(
|
||||
const AttributedString& attributedString,
|
||||
const ParagraphAttributes& paragraphAttributes,
|
||||
const TextLayoutContext& layoutContext,
|
||||
const LayoutConstraints& layoutConstraints) const;
|
||||
|
||||
/**
|
||||
* Derive text and attachment measurements from a PreparedLayout.
|
||||
*/
|
||||
TextMeasurement measurePreparedLayout(
|
||||
const PreparedLayout& layout,
|
||||
const TextLayoutContext& layoutContext,
|
||||
const LayoutConstraints& layoutConstraints) const;
|
||||
|
||||
private:
|
||||
std::shared_ptr<const ContextContainer> contextContainer_;
|
||||
TextMeasureCache textMeasureCache_;
|
||||
|
||||
@@ -308,6 +308,16 @@ const definitions: FeatureFlagDefinitions = {
|
||||
},
|
||||
ossReleaseStage: 'none',
|
||||
},
|
||||
enablePreparedTextLayout: {
|
||||
defaultValue: false,
|
||||
metadata: {
|
||||
dateAdded: '2025-05-01',
|
||||
description: 'Enables caching text layout artifacts for later reuse',
|
||||
expectedReleaseValue: true,
|
||||
purpose: 'experimentation',
|
||||
},
|
||||
ossReleaseStage: 'none',
|
||||
},
|
||||
enablePropsUpdateReconciliationAndroid: {
|
||||
defaultValue: false,
|
||||
metadata: {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<fc8743f0fb9cb153412a1e81f585f07d>>
|
||||
* @generated SignedSource<<6667fc8e4fdd2db0b54c908155b110cf>>
|
||||
* @flow strict
|
||||
*/
|
||||
|
||||
@@ -71,6 +71,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{
|
||||
enableNativeCSSParsing: Getter<boolean>,
|
||||
enableNetworkEventReporting: Getter<boolean>,
|
||||
enableNewBackgroundAndBorderDrawables: Getter<boolean>,
|
||||
enablePreparedTextLayout: Getter<boolean>,
|
||||
enablePropsUpdateReconciliationAndroid: Getter<boolean>,
|
||||
enableResourceTimingAPI: Getter<boolean>,
|
||||
enableSynchronousStateUpdates: Getter<boolean>,
|
||||
@@ -258,6 +259,10 @@ export const enableNetworkEventReporting: Getter<boolean> = createNativeFlagGett
|
||||
* Use BackgroundDrawable and BorderDrawable instead of CSSBackgroundDrawable
|
||||
*/
|
||||
export const enableNewBackgroundAndBorderDrawables: Getter<boolean> = createNativeFlagGetter('enableNewBackgroundAndBorderDrawables', true);
|
||||
/**
|
||||
* Enables caching text layout artifacts for later reuse
|
||||
*/
|
||||
export const enablePreparedTextLayout: Getter<boolean> = createNativeFlagGetter('enablePreparedTextLayout', false);
|
||||
/**
|
||||
* When enabled, Android will receive prop updates based on the differences between the last rendered shadow node and the last committed shadow node.
|
||||
*/
|
||||
|
||||
+2
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<995633c3b12e26369518ff579f006f68>>
|
||||
* @generated SignedSource<<4bf477efaec82ad8e9e4b1bc17705fd2>>
|
||||
* @flow strict
|
||||
*/
|
||||
|
||||
@@ -49,6 +49,7 @@ export interface Spec extends TurboModule {
|
||||
+enableNativeCSSParsing?: () => boolean;
|
||||
+enableNetworkEventReporting?: () => boolean;
|
||||
+enableNewBackgroundAndBorderDrawables?: () => boolean;
|
||||
+enablePreparedTextLayout?: () => boolean;
|
||||
+enablePropsUpdateReconciliationAndroid?: () => boolean;
|
||||
+enableResourceTimingAPI?: () => boolean;
|
||||
+enableSynchronousStateUpdates?: () => boolean;
|
||||
|
||||
Reference in New Issue
Block a user