Skip to content

Underpinnings for Caching Text Layouts #51065

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
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
2 changes: 2 additions & 0 deletions packages/react-native/ReactAndroid/api/ReactAndroid.api
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@
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;
Expand All @@ -49,6 +51,7 @@
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;
Expand Down Expand Up @@ -86,6 +89,7 @@
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;
Expand Down Expand Up @@ -648,6 +652,49 @@ private long measureMapBuffer(
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>>
*/

/**
Expand Down Expand Up @@ -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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>>
*/

/**
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>>
*/

/**
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>>
*/

/**
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>>
*/

/**
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>>
*/

/**
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
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;
Expand Down Expand Up @@ -503,7 +504,8 @@ private static void updateTextPaint(
}
}

private static Layout createLayout(
@UnstableReactNativeAPI
public static Layout createLayout(
@NonNull Context context,
MapBuffer attributedString,
MapBuffer paragraphAttributes,
Expand Down Expand Up @@ -732,6 +734,47 @@ public static long measureText(
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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>>
*/

/**
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -655,6 +666,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
makeNativeMethod(
"enableNewBackgroundAndBorderDrawables",
JReactNativeFeatureFlagsCxxInterop::enableNewBackgroundAndBorderDrawables),
makeNativeMethod(
"enablePreparedTextLayout",
JReactNativeFeatureFlagsCxxInterop::enablePreparedTextLayout),
makeNativeMethod(
"enablePropsUpdateReconciliationAndroid",
JReactNativeFeatureFlagsCxxInterop::enablePropsUpdateReconciliationAndroid),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>>
*/

/**
Expand Down Expand Up @@ -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>);

Expand Down
Loading
Loading