diff --git a/ReactAndroid/Android-prebuilt.mk b/ReactAndroid/Android-prebuilt.mk
index 8cb0ea16e1baa1..fa41a5594e0ba4 100644
--- a/ReactAndroid/Android-prebuilt.mk
+++ b/ReactAndroid/Android-prebuilt.mk
@@ -140,6 +140,14 @@ LOCAL_EXPORT_C_INCLUDES := \
$(REACT_COMMON_DIR)/react/renderer/mounting
include $(PREBUILT_SHARED_LIBRARY)
+# react_render_mapbuffer
+include $(CLEAR_VARS)
+LOCAL_MODULE := react_render_mapbuffer
+LOCAL_SRC_FILES := $(REACT_NDK_EXPORT_DIR)/$(TARGET_ARCH_ABI)/libreact_render_mapbuffer.so
+LOCAL_EXPORT_C_INCLUDES := \
+ $(REACT_COMMON_DIR)/react/renderer/mapbuffer
+include $(PREBUILT_SHARED_LIBRARY)
+
# rrc_view
include $(CLEAR_VARS)
LOCAL_MODULE := rrc_view
diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/BUCK b/ReactAndroid/src/main/java/com/facebook/react/common/BUCK
index 596d953a230158..02246b533959ab 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/common/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/common/BUCK
@@ -2,6 +2,7 @@ load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "rn_android_build
SUB_PROJECTS = [
"network/**/*",
+ "mapbuffer/**/*",
]
rn_android_library(
diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/BUCK b/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/BUCK
new file mode 100644
index 00000000000000..58366da207e166
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/BUCK
@@ -0,0 +1,26 @@
+load("//tools/build_defs/oss:rn_defs.bzl", "FBJNI_TARGET", "react_native_dep", "react_native_target", "rn_android_library")
+
+rn_android_library(
+ name = "mapbuffer",
+ srcs = glob([
+ "*.java",
+ ]),
+ autoglob = False,
+ is_androidx = True,
+ labels = ["supermodule:xplat/default/public.react_native.infra"],
+ provided_deps = [],
+ required_for_source_only_abi = True,
+ visibility = [
+ "PUBLIC",
+ ],
+ deps = [
+ FBJNI_TARGET,
+ react_native_dep("libraries/soloader/java/com/facebook/soloader:soloader"),
+ react_native_target("java/com/facebook/react/common/mapbuffer/jni:jni"),
+ react_native_dep("libraries/fbjni:java"),
+ react_native_dep("third-party/android/androidx:annotation"),
+ react_native_dep("third-party/java/infer-annotations:infer-annotations"),
+ react_native_target("java/com/facebook/react/common:common"),
+ ],
+ exported_deps = [],
+)
diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/ReadableMapBuffer.java b/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/ReadableMapBuffer.java
new file mode 100644
index 00000000000000..e70c3c090cc7e9
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/ReadableMapBuffer.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (c) Facebook, Inc. and its 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.common.mapbuffer;
+
+import androidx.annotation.Nullable;
+import com.facebook.jni.HybridData;
+import com.facebook.proguard.annotations.DoNotStrip;
+import com.facebook.soloader.SoLoader;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Iterator;
+
+/**
+ * TODO T83483191: add documentation.
+ *
+ *
NOTE: {@link ReadableMapBuffer} is NOT thread safe.
+ */
+public class ReadableMapBuffer implements Iterable {
+
+ static {
+ SoLoader.loadLibrary("mapbufferjni");
+ }
+
+ // Value used to verify if the data is serialized with LittleEndian order.
+ private static final int ALIGNMENT = 0xFE;
+
+ // 6 bytes = 2 (alignment) + 2 (count) + 2 (size)
+ private static final int HEADER_SIZE = 6;
+
+ // key size = 2 bytes
+ private static final int KEY_SIZE = 2;
+
+ // 10 bytes = 2 bytes key + 8 bytes value
+ private static final int BUCKET_SIZE = 10;
+
+ private static final int INT_SIZE = 4;
+
+ // TODO T83483191: consider moving short to INTs, we are doing extra cast operations just because
+ // of short java operates with int
+ private static final int SHORT_SIZE = 2;
+
+ private static final short SHORT_ONE = (short) 1;
+
+ @Nullable ByteBuffer mBuffer = null;
+
+ // Size of the Serialized Data
+ @SuppressWarnings("unused")
+ private short mSizeOfData = 0;
+
+ // Amount of items serialized on the ByteBuffer
+ @SuppressWarnings("unused")
+ private short mCount = 0;
+
+ private ReadableMapBuffer(HybridData hybridData) {
+ mHybridData = hybridData;
+ }
+
+ private ReadableMapBuffer(ByteBuffer buffer) {
+ mBuffer = buffer;
+ readHeader();
+ }
+
+ private native ByteBuffer importByteBufferAllocateDirect();
+
+ private native ByteBuffer importByteBuffer();
+
+ @SuppressWarnings("unused")
+ @DoNotStrip
+ @Nullable
+ private HybridData mHybridData;
+
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ if (mHybridData != null) {
+ mHybridData.resetNative();
+ }
+ }
+
+ private int getKeyOffsetForBucketIndex(int bucketIndex) {
+ return HEADER_SIZE + BUCKET_SIZE * bucketIndex;
+ }
+
+ private int getValueOffsetForKey(short key) {
+ importByteBufferAndReadHeader();
+ int bucketIndex = getBucketIndexForKey(key);
+ if (bucketIndex == -1) {
+ // TODO T83483191: Add tests
+ throw new IllegalArgumentException("Unable to find key: " + key);
+ }
+ assertKeyExists(key, bucketIndex);
+ return getKeyOffsetForBucketIndex(bucketIndex) + KEY_SIZE;
+ }
+
+ // returns the relative offset of the first byte of dynamic data
+ private int getOffsetForDynamicData() {
+ // TODO T83483191: check if there's dynamic data?
+ return getKeyOffsetForBucketIndex(mCount);
+ }
+
+ /**
+ * @param key Key to search for
+ * @return the "bucket index" for a key or -1 if not found. It uses a binary search algorithm
+ * (log(n))
+ */
+ private int getBucketIndexForKey(short key) {
+ short lo = 0;
+ short hi = (short) (getCount() - SHORT_ONE);
+ while (lo <= hi) {
+ final short mid = (short) ((lo + hi) >>> SHORT_ONE);
+ final short midVal = readKey(getKeyOffsetForBucketIndex(mid));
+ if (midVal < key) {
+ lo = (short) (mid + SHORT_ONE);
+ } else if (midVal > key) {
+ hi = (short) (mid - SHORT_ONE);
+ } else {
+ return mid;
+ }
+ }
+ return -1;
+ }
+
+ private short readKey(int position) {
+ return mBuffer.getShort(position);
+ }
+
+ private double readDoubleValue(int bufferPosition) {
+ return mBuffer.getDouble(bufferPosition);
+ }
+
+ private int readIntValue(int bufferPosition) {
+ return mBuffer.getInt(bufferPosition);
+ }
+
+ private boolean readBooleanValue(int bufferPosition) {
+ return readIntValue(bufferPosition) == 1;
+ }
+
+ private String readStringValue(int bufferPosition) {
+ int offset = getOffsetForDynamicData() + mBuffer.getInt(bufferPosition);
+
+ int sizeOfString = mBuffer.getInt(offset);
+ byte[] result = new byte[sizeOfString];
+
+ int stringOffset = offset + INT_SIZE;
+
+ mBuffer.position(stringOffset);
+ mBuffer.get(result, 0, sizeOfString);
+
+ return new String(result);
+ }
+
+ private ReadableMapBuffer readMapBufferValue(int position) {
+ int offset = getOffsetForDynamicData() + mBuffer.getInt(position);
+
+ int sizeMapBuffer = mBuffer.getShort(offset);
+ byte[] buffer = new byte[sizeMapBuffer];
+
+ int bufferOffset = offset + SHORT_SIZE;
+
+ mBuffer.position(bufferOffset);
+ mBuffer.get(buffer, 0, sizeMapBuffer);
+
+ return new ReadableMapBuffer(ByteBuffer.wrap(buffer));
+ }
+
+ private void readHeader() {
+ // byte order
+ short storedAlignment = mBuffer.getShort();
+ if (storedAlignment != ALIGNMENT) {
+ mBuffer.order(ByteOrder.LITTLE_ENDIAN);
+ }
+ // count
+ mCount = mBuffer.getShort();
+ // size
+ mSizeOfData = mBuffer.getShort();
+ }
+
+ /**
+ * Binary search of the key inside the mapBuffer (log(n)).
+ *
+ * @param key Key to search for
+ * @return true if and only if the Key received as a parameter is stored in the MapBuffer.
+ */
+ public boolean hasKey(short key) {
+ // TODO T83483191: Add tests
+ return getBucketIndexForKey(key) != -1;
+ }
+
+ /** @return amount of elements stored into the MapBuffer */
+ public short getCount() {
+ importByteBufferAndReadHeader();
+ return mCount;
+ }
+
+ /**
+ * @param key {@link int} representing the key
+ * @return return the int associated to the Key received as a parameter.
+ */
+ public int getInt(short key) {
+ // TODO T83483191: extract common code of "get methods"
+ return readIntValue(getValueOffsetForKey(key));
+ }
+
+ /**
+ * @param key {@link int} representing the key
+ * @return return the double associated to the Key received as a parameter.
+ */
+ public double getDouble(short key) {
+ return readDoubleValue(getValueOffsetForKey(key));
+ }
+
+ /**
+ * @param key {@link int} representing the key
+ * @return return the int associated to the Key received as a parameter.
+ */
+ public String getString(short key) {
+ return readStringValue(getValueOffsetForKey(key));
+ }
+
+ public boolean getBoolean(short key) {
+ return readBooleanValue(getValueOffsetForKey(key));
+ }
+
+ /**
+ * @param key {@link int} representing the key
+ * @return return the int associated to the Key received as a parameter.
+ */
+ public ReadableMapBuffer getMapBuffer(short key) {
+ return readMapBufferValue(getValueOffsetForKey(key));
+ }
+
+ /**
+ * Import ByteBuffer from C++, read the header and move the current cursor at the start of the
+ * payload.
+ */
+ private ByteBuffer importByteBufferAndReadHeader() {
+ if (mBuffer != null) {
+ return mBuffer;
+ }
+
+ // mBuffer = importByteBufferAllocateDirect();
+ mBuffer = importByteBuffer();
+
+ readHeader();
+ return mBuffer;
+ }
+
+ private void assertKeyExists(short key, int bucketIndex) {
+ short storedKey = mBuffer.getShort(getKeyOffsetForBucketIndex(bucketIndex));
+ if (storedKey != key) {
+ throw new IllegalStateException(
+ "Stored key doesn't match parameter - expected: " + key + " - found: " + storedKey);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ ByteBuffer byteBuffer = importByteBufferAndReadHeader();
+ byteBuffer.rewind();
+ return byteBuffer.hashCode();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof ReadableMapBuffer)) {
+ return false;
+ }
+
+ ReadableMapBuffer other = (ReadableMapBuffer) obj;
+ ByteBuffer thisByteBuffer = importByteBufferAndReadHeader();
+ ByteBuffer otherByteBuffer = other.importByteBufferAndReadHeader();
+ if (thisByteBuffer == otherByteBuffer) {
+ return true;
+ }
+ thisByteBuffer.rewind();
+ otherByteBuffer.rewind();
+ return thisByteBuffer.equals(otherByteBuffer);
+ }
+
+ /** @return an {@link Iterator} for the entries of this MapBuffer. */
+ @Override
+ public Iterator iterator() {
+ return new Iterator() {
+ short current = 0;
+ short last = (short) (getCount() - SHORT_ONE);
+
+ @Override
+ public boolean hasNext() {
+ return current <= last;
+ }
+
+ @Override
+ public MapBufferEntry next() {
+ return new MapBufferEntry(getKeyOffsetForBucketIndex(current++));
+ }
+ };
+ }
+
+ /** This class represents an Entry of the {@link ReadableMapBuffer} class. */
+ public class MapBufferEntry {
+ private final int mBucketOffset;
+
+ private MapBufferEntry(int position) {
+ mBucketOffset = position;
+ }
+
+ /** @return a {@link short} that represents the key of this {@link MapBufferEntry}. */
+ public short getKey() {
+ return readKey(mBucketOffset);
+ }
+
+ /** @return the double value that is stored in this {@link MapBufferEntry}. */
+ public double getDouble(double defaultValue) {
+ // TODO T83483191 Extend serialization of MapBuffer to add type checking
+ // TODO T83483191 Extend serialization of MapBuffer to return null if there's no value
+ // stored in this MapBufferEntry.
+ return readDoubleValue(mBucketOffset + KEY_SIZE);
+ }
+
+ /** @return the int value that is stored in this {@link MapBufferEntry}. */
+ public int getInt(int defaultValue) {
+ return readIntValue(mBucketOffset + KEY_SIZE);
+ }
+
+ /** @return the boolean value that is stored in this {@link MapBufferEntry}. */
+ public boolean getBoolean(boolean defaultValue) {
+ return readBooleanValue(mBucketOffset + KEY_SIZE);
+ }
+
+ /** @return the String value that is stored in this {@link MapBufferEntry}. */
+ public @Nullable String getString() {
+ return readStringValue(mBucketOffset + KEY_SIZE);
+ }
+
+ /**
+ * @return the {@link ReadableMapBuffer} value that is stored in this {@link MapBufferEntry}.
+ */
+ public @Nullable ReadableMapBuffer getReadableMapBuffer() {
+ return readMapBufferValue(mBucketOffset + KEY_SIZE);
+ }
+ }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/jni/Android.mk b/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/jni/Android.mk
new file mode 100644
index 00000000000000..b74cc058fcf143
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/jni/Android.mk
@@ -0,0 +1,39 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+#
+# This source code is licensed under the MIT license found in the
+# LICENSE file in the root directory of this source tree.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := mapbufferjni
+
+LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/react/common/mapbuffer/*.cpp)
+
+LOCAL_SHARED_LIBRARIES := libreactconfig libyoga libglog libfb libfbjni libglog_init libfolly_json libfolly_futures libreact_utils libreact_render_mapbuffer libreact_debug
+
+LOCAL_STATIC_LIBRARIES :=
+
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/
+
+LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/
+
+LOCAL_CFLAGS := \
+ -DLOG_TAG=\"Fabric\"
+
+LOCAL_CFLAGS += -fexceptions -frtti -std=c++14 -Wall
+
+include $(BUILD_SHARED_LIBRARY)
+
+$(call import-module,fbgloginit)
+$(call import-module,folly)
+$(call import-module,fb)
+$(call import-module,fbjni)
+$(call import-module,yogajni)
+$(call import-module,glog)
+
+$(call import-module,react/utils)
+$(call import-module,react/debug)
+$(call import-module,react/config)
+$(call import-module,react/renderer/mapbuffer)
diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/jni/BUCK b/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/jni/BUCK
new file mode 100644
index 00000000000000..591ebda315470c
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/jni/BUCK
@@ -0,0 +1,33 @@
+load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "FBJNI_TARGET", "react_native_xplat_target", "rn_xplat_cxx_library", "subdir_glob")
+
+rn_xplat_cxx_library(
+ name = "jni",
+ srcs = glob(["**/*.cpp"]),
+ headers = glob(["**/*.h"]),
+ header_namespace = "",
+ exported_headers = subdir_glob(
+ [
+ ("react/common/mapbuffer", "*.h"),
+ ],
+ prefix = "react/common/mapbuffer",
+ ),
+ compiler_flags = [
+ "-fexceptions",
+ "-frtti",
+ "-std=c++14",
+ "-Wall",
+ ],
+ fbandroid_allow_jni_merging = True,
+ labels = ["supermodule:xplat/default/public.react_native.infra"],
+ platforms = (ANDROID),
+ preprocessor_flags = [
+ "-DLOG_TAG=\"ReactNative\"",
+ "-DWITH_FBSYSTRACE=1",
+ ],
+ soname = "libmapbufferjni.$(ext)",
+ visibility = ["PUBLIC"],
+ deps = [
+ react_native_xplat_target("react/renderer/mapbuffer:mapbuffer"),
+ FBJNI_TARGET,
+ ],
+)
diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/jni/react/common/mapbuffer/OnLoad.cpp b/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/jni/react/common/mapbuffer/OnLoad.cpp
new file mode 100644
index 00000000000000..76e0aa1a2c877b
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/jni/react/common/mapbuffer/OnLoad.cpp
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+#include
+
+#include "ReadableMapBuffer.h"
+
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
+ return facebook::jni::initialize(
+ vm, [] { facebook::react::ReadableMapBuffer::registerNatives(); });
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/jni/react/common/mapbuffer/ReadableMapBuffer.cpp b/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/jni/react/common/mapbuffer/ReadableMapBuffer.cpp
new file mode 100644
index 00000000000000..8f7cac20b28179
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/jni/react/common/mapbuffer/ReadableMapBuffer.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+#include "ReadableMapBuffer.h"
+
+namespace facebook {
+namespace react {
+
+void ReadableMapBuffer::registerNatives() {
+ registerHybrid({
+ makeNativeMethod("importByteBuffer", ReadableMapBuffer::importByteBuffer),
+ makeNativeMethod(
+ "importByteBufferAllocateDirect",
+ ReadableMapBuffer::importByteBufferAllocateDirect),
+ });
+}
+
+jni::local_ref
+ReadableMapBuffer::importByteBufferAllocateDirect() {
+ // TODO: Using this method is safer than "importByteBuffer" because ByteBuffer
+ // memory will be deallocated once the "Java ByteBuffer" is deallocated. Next
+ // steps:
+ // - Validate perf of this method vs importByteBuffer
+ // - Validate that there's no leaking of memory
+ return jni::JByteBuffer::allocateDirect(_serializedDataSize);
+}
+
+jni::JByteBuffer::javaobject ReadableMapBuffer::importByteBuffer() {
+ // TODO: Reevaluate what's the best approach here (allocateDirect vs
+ // DirectByteBuffer).
+ //
+ // On this method we should:
+ // - Review deallocation of serializedData (we are probably leaking
+ // _serializedData now).
+ // - Consider using allocate() or allocateDirect() methods from java instead
+ // of newDirectByteBuffer (to simplify de/allocation) :
+ // https://www.internalfb.com/intern/diffusion/FBS/browsefile/master/fbandroid/libraries/fbjni/cxx/fbjni/ByteBuffer.cpp
+ // - Add flags to describe if the data was already 'imported'
+ // - Long-term: Consider creating a big ByteBuffer that can be re-used to
+ // transfer data of multitple Maps
+ return static_cast(
+ jni::Environment::current()->NewDirectByteBuffer(
+ (void *)_serializedData, _serializedDataSize));
+}
+
+jni::local_ref
+ReadableMapBuffer::createWithContents(MapBuffer &&map) {
+ return newObjectCxxArgs(std::move(map));
+}
+
+ReadableMapBuffer::~ReadableMapBuffer() {
+ delete[] _serializedData;
+ _serializedData = nullptr;
+ _serializedDataSize = 0;
+}
+
+} // namespace react
+} // namespace facebook
diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/jni/react/common/mapbuffer/ReadableMapBuffer.h b/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/jni/react/common/mapbuffer/ReadableMapBuffer.h
new file mode 100644
index 00000000000000..555851199f563c
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/jni/react/common/mapbuffer/ReadableMapBuffer.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) Facebook, Inc. and its 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
+#include
+
+#include
+
+namespace facebook {
+namespace react {
+
+class ReadableMapBuffer : public jni::HybridClass {
+ public:
+ static auto constexpr kJavaDescriptor =
+ "Lcom/facebook/react/common/mapbuffer/ReadableMapBuffer;";
+
+ static void registerNatives();
+
+ static jni::local_ref createWithContents(MapBuffer &&map);
+
+ jni::local_ref importByteBufferAllocateDirect();
+
+ jni::JByteBuffer::javaobject importByteBuffer();
+
+ ~ReadableMapBuffer();
+
+ private:
+ uint8_t *_serializedData = nullptr;
+
+ int _serializedDataSize = 0;
+
+ friend HybridBase;
+
+ explicit ReadableMapBuffer(MapBuffer &&map) {
+ _serializedDataSize = map.getBufferSize();
+ _serializedData = new Byte[_serializedDataSize];
+ map.copy(_serializedData);
+ }
+};
+
+} // namespace react
+} // namespace facebook
diff --git a/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java b/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java
index 75e7c0f16869fa..a3ddb7cff7fe02 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java
@@ -65,4 +65,7 @@ public class ReactFeatureFlags {
/** Enables JS Responder in Fabric */
public static boolean enableJSResponder = false;
+
+ /** Enables MapBuffer Serialization */
+ public static boolean mapBufferSerializationEnabled = false;
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/BUCK b/ReactAndroid/src/main/java/com/facebook/react/fabric/BUCK
index 12ad6392e594d6..df7dee67bd64e0 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/fabric/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/BUCK
@@ -37,6 +37,7 @@ rn_android_library(
react_native_target("java/com/facebook/react/modules/core:core"),
react_native_target("java/com/facebook/react/modules/i18nmanager:i18nmanager"),
react_native_target("java/com/facebook/react/common:common"),
+ react_native_target("java/com/facebook/react/common/mapbuffer:mapbuffer"),
react_native_target("java/com/facebook/react/uimanager:uimanager"),
react_native_target("java/com/facebook/react/views/view:view"),
react_native_target("java/com/facebook/react/views/text:text"),
diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java
index abcea19b8a78cb..89ea427748dd76 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java
@@ -53,6 +53,7 @@
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.build.ReactBuildConfig;
+import com.facebook.react.common.mapbuffer.ReadableMapBuffer;
import com.facebook.react.config.ReactFeatureFlags;
import com.facebook.react.fabric.events.EventBeatManager;
import com.facebook.react.fabric.events.EventEmitterWrapper;
@@ -81,6 +82,7 @@
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.uimanager.events.EventDispatcherImpl;
import com.facebook.react.views.text.TextLayoutManager;
+import com.facebook.react.views.text.TextLayoutManagerMapBuffer;
import com.facebook.systrace.Systrace;
import java.util.ArrayList;
import java.util.Collection;
@@ -418,6 +420,21 @@ private NativeArray measureLines(
PixelUtil.toPixelFromDIP(width));
}
+ @DoNotStrip
+ @SuppressWarnings("unused")
+ private NativeArray measureLinesMapBuffer(
+ ReadableMapBuffer attributedString,
+ ReadableMapBuffer paragraphAttributes,
+ float width,
+ float height) {
+ return (NativeArray)
+ TextLayoutManagerMapBuffer.measureLines(
+ mReactApplicationContext,
+ attributedString,
+ paragraphAttributes,
+ PixelUtil.toPixelFromDIP(width));
+ }
+
@DoNotStrip
@SuppressWarnings("unused")
private long measure(
@@ -482,6 +499,44 @@ private long measure(
attachmentsPositions);
}
+ @DoNotStrip
+ @SuppressWarnings("unused")
+ private long measureMapBuffer(
+ int surfaceId,
+ String componentName,
+ ReadableMapBuffer attributedString,
+ ReadableMapBuffer paragraphAttributes,
+ float minWidth,
+ float maxWidth,
+ float minHeight,
+ float maxHeight,
+ @Nullable float[] attachmentsPositions) {
+
+ ReactContext context;
+ if (surfaceId > 0) {
+ SurfaceMountingManager surfaceMountingManager =
+ mMountingManager.getSurfaceManagerEnforced(surfaceId, "measure");
+ if (surfaceMountingManager.isStopped()) {
+ return 0;
+ }
+ context = surfaceMountingManager.getContext();
+ } else {
+ context = mReactApplicationContext;
+ }
+
+ // TODO: replace ReadableNativeMap -> ReadableMapBuffer
+ return mMountingManager.measureTextMapBuffer(
+ context,
+ componentName,
+ attributedString,
+ paragraphAttributes,
+ getYogaSize(minWidth, maxWidth),
+ getYogaMeasureMode(minWidth, maxWidth),
+ getYogaSize(minHeight, maxHeight),
+ getYogaMeasureMode(minHeight, maxHeight),
+ attachmentsPositions);
+ }
+
/**
* @param surfaceId {@link int} surface ID
* @param defaultTextInputPadding {@link float[]} output parameter will contain the default theme
diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/StateWrapperImpl.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/StateWrapperImpl.java
index 06d4459c3021fc..821dff8a5fdfb1 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/fabric/StateWrapperImpl.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/StateWrapperImpl.java
@@ -16,6 +16,7 @@
import com.facebook.react.bridge.NativeMap;
import com.facebook.react.bridge.ReadableNativeMap;
import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.common.mapbuffer.ReadableMapBuffer;
import com.facebook.react.uimanager.StateWrapper;
/**
@@ -41,6 +42,18 @@ private StateWrapperImpl() {
private native ReadableNativeMap getStateDataImpl();
+ private native ReadableMapBuffer getStateMapBufferDataImpl();
+
+ @Override
+ @Nullable
+ public ReadableMapBuffer getStatDataMapBuffer() {
+ if (mDestroyed) {
+ FLog.e(TAG, "Race between StateWrapperImpl destruction and getState");
+ return null;
+ }
+ return getStateMapBufferDataImpl();
+ }
+
@Override
@Nullable
public ReadableNativeMap getStateData() {
diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Android.mk b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Android.mk
index a20d60caf044f3..a6353d07765ba3 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Android.mk
+++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Android.mk
@@ -11,7 +11,7 @@ LOCAL_MODULE := fabricjni
LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp)
-LOCAL_SHARED_LIBRARIES := libreactconfig librrc_slider librrc_progressbar librrc_switch librrc_modal libyoga libglog libfb libfbjni libglog_init libfolly_json libfolly_futures libreact_render_mounting libreactnativeutilsjni libreact_utils libreact_render_debug libreact_render_graphics libreact_render_core libreact_render_mapbuffer react_render_componentregistry librrc_view librrc_unimplementedview librrc_root librrc_scrollview libbetter libreact_render_attributedstring libreact_render_uimanager libreact_render_templateprocessor libreact_render_scheduler libreact_render_animations libreact_render_imagemanager libreact_render_textlayoutmanager libreact_codegen_rncore rrc_text librrc_image librrc_textinput librrc_picker libreact_debug
+LOCAL_SHARED_LIBRARIES := libreactconfig librrc_slider librrc_progressbar librrc_switch librrc_modal libyoga libglog libfb libfbjni libglog_init libfolly_json libfolly_futures libreact_render_mounting libreactnativeutilsjni libreact_utils libreact_render_debug libreact_render_graphics libreact_render_core react_render_componentregistry librrc_view librrc_unimplementedview librrc_root librrc_scrollview libbetter libreact_render_attributedstring libreact_render_uimanager libreact_render_templateprocessor libreact_render_scheduler libreact_render_animations libreact_render_imagemanager libreact_render_textlayoutmanager libreact_codegen_rncore rrc_text librrc_image librrc_textinput librrc_picker libreact_debug libreact_render_mapbuffer libmapbufferjni
LOCAL_STATIC_LIBRARIES :=
diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/BUCK b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/BUCK
index 9359bfb6f77ffc..0dca065b7eb5b0 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/BUCK
@@ -27,6 +27,7 @@ rn_xplat_cxx_library(
soname = "libfabricjni.$(ext)",
visibility = ["PUBLIC"],
deps = [
+ react_native_xplat_target("react/renderer/mapbuffer:mapbuffer"),
react_native_xplat_target("react/config:config"),
react_native_xplat_target("react/renderer/animations:animations"),
react_native_xplat_target("react/renderer/uimanager:uimanager"),
diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp
index f1ff744d17de0b..847d3f10716b9f 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp
+++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp
@@ -508,6 +508,11 @@ void Binding::installFabricUIManager(
// Keep reference to config object and cache some feature flags here
reactNativeConfig_ = config;
+ contextContainer->insert(
+ "MapBufferSerializationEnabled",
+ reactNativeConfig_->getBool(
+ "react_fabric:enable_mapbuffer_serialization_android"));
+
disablePreallocateViews_ = reactNativeConfig_->getBool(
"react_fabric:disabled_view_preallocation_android");
diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/StateWrapperImpl.cpp b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/StateWrapperImpl.cpp
index 0def0aab5ff0d1..eaa9398de60b07 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/StateWrapperImpl.cpp
+++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/StateWrapperImpl.cpp
@@ -8,6 +8,8 @@
#include "StateWrapperImpl.h"
#include
#include
+#include
+#include
using namespace facebook::jni;
@@ -30,6 +32,14 @@ StateWrapperImpl::getStateDataImpl() {
return readableNativeMap;
}
+jni::local_ref
+StateWrapperImpl::getStateMapBufferDataImpl() {
+ MapBuffer map = state_->getMapBuffer();
+ auto ReadableMapBuffer =
+ ReadableMapBuffer::createWithContents(std::move(map));
+ return ReadableMapBuffer;
+}
+
void StateWrapperImpl::updateStateImpl(NativeMap *map) {
// Get folly::dynamic from map
auto dynamicMap = map->consume();
@@ -42,6 +52,9 @@ void StateWrapperImpl::registerNatives() {
makeNativeMethod("initHybrid", StateWrapperImpl::initHybrid),
makeNativeMethod("getStateDataImpl", StateWrapperImpl::getStateDataImpl),
makeNativeMethod("updateStateImpl", StateWrapperImpl::updateStateImpl),
+ makeNativeMethod(
+ "getStateMapBufferDataImpl",
+ StateWrapperImpl::getStateMapBufferDataImpl),
});
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/StateWrapperImpl.h b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/StateWrapperImpl.h
index 21e0c4c2fb18de..9193efc2518d49 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/StateWrapperImpl.h
+++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/StateWrapperImpl.h
@@ -8,6 +8,7 @@
#pragma once
#include
+#include
#include
#include
@@ -25,6 +26,7 @@ class StateWrapperImpl : public jni::HybridClass {
static void registerNatives();
+ jni::local_ref getStateMapBufferDataImpl();
jni::local_ref getStateDataImpl();
void updateStateImpl(NativeMap *map);
void updateStateWithFailureCallbackImpl(
diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java
index a45742450dc8b8..7993e76e902fbd 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java
@@ -9,6 +9,7 @@
import static com.facebook.infer.annotation.ThreadConfined.ANY;
+import android.text.Spannable;
import android.view.View;
import androidx.annotation.AnyThread;
import androidx.annotation.NonNull;
@@ -22,6 +23,7 @@
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.RetryableMountingLayerException;
import com.facebook.react.bridge.UiThreadUtil;
+import com.facebook.react.common.mapbuffer.ReadableMapBuffer;
import com.facebook.react.fabric.FabricUIManager;
import com.facebook.react.fabric.events.EventEmitterWrapper;
import com.facebook.react.fabric.mounting.mountitems.MountItem;
@@ -30,6 +32,8 @@
import com.facebook.react.uimanager.RootViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewManagerRegistry;
+import com.facebook.react.views.text.ReactTextViewManagerCallback;
+import com.facebook.react.views.text.TextLayoutManagerMapBuffer;
import com.facebook.yoga.YogaMeasureMode;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -337,6 +341,49 @@ public long measure(
attachmentsPositions);
}
+ /**
+ * Measure a component, given localData, props, state, and measurement information. This needs to
+ * remain here for now - and not in SurfaceMountingManager - because sometimes measures are made
+ * outside of the context of a Surface; especially from C++ before StartSurface is called.
+ *
+ * @param context
+ * @param componentName
+ * @param attributedString
+ * @param paragraphAttributes
+ * @param width
+ * @param widthMode
+ * @param height
+ * @param heightMode
+ * @param attachmentsPositions
+ * @return
+ */
+ @AnyThread
+ public long measureTextMapBuffer(
+ @NonNull ReactContext context,
+ @NonNull String componentName,
+ @NonNull ReadableMapBuffer attributedString,
+ @NonNull ReadableMapBuffer paragraphAttributes,
+ float width,
+ @NonNull YogaMeasureMode widthMode,
+ float height,
+ @NonNull YogaMeasureMode heightMode,
+ @Nullable float[] attachmentsPositions) {
+
+ return TextLayoutManagerMapBuffer.measureText(
+ context,
+ attributedString,
+ paragraphAttributes,
+ width,
+ widthMode,
+ height,
+ heightMode,
+ new ReactTextViewManagerCallback() {
+ @Override
+ public void onPostProcessSpannable(Spannable text) {}
+ },
+ attachmentsPositions);
+ }
+
public void initializeViewManager(String componentName) {
mViewManagerRegistry.get(componentName);
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK
index 72db6f8efc59e8..9979f6dfdde5d2 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK
@@ -39,6 +39,7 @@ rn_android_library(
react_native_target("java/com/facebook/react/bridge:bridge"),
react_native_target("java/com/facebook/react/uimanager/jni:jni"),
react_native_target("java/com/facebook/react/common:common"),
+ react_native_target("java/com/facebook/react/common/mapbuffer:mapbuffer"),
react_native_target("java/com/facebook/react/config:config"),
react_native_target("java/com/facebook/react/module/annotations:annotations"),
react_native_target("java/com/facebook/react/modules/core:core"),
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/StateWrapper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/StateWrapper.java
index 1ad52d95affb58..8bec238604f64d 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/StateWrapper.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/StateWrapper.java
@@ -9,6 +9,7 @@
import com.facebook.react.bridge.ReadableNativeMap;
import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.common.mapbuffer.ReadableMapBuffer;
import javax.annotation.Nullable;
/**
@@ -17,6 +18,15 @@
* by calling updateState, which communicates state back to the C++ layer.
*/
public interface StateWrapper {
+
+ /**
+ * Get a ReadableMapBuffer object from the C++ layer, which is a K/V map of short keys to values.
+ *
+ * Unstable API - DO NOT USE.
+ */
+ @Nullable
+ ReadableMapBuffer getStatDataMapBuffer();
+
/**
* Get a ReadableNativeMap object from the C++ layer, which is a K/V map of string keys to values.
*/
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK
index 49b14f8a7176a0..cf4fbdd897016a 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK
@@ -23,6 +23,7 @@ rn_android_library(
react_native_dep("third-party/java/jsr-305:jsr-305"),
react_native_target("java/com/facebook/react/bridge:bridge"),
react_native_target("java/com/facebook/react/common:common"),
+ react_native_target("java/com/facebook/react/common/mapbuffer:mapbuffer"),
react_native_target("java/com/facebook/react/config:config"),
react_native_target("java/com/facebook/react/module/annotations:annotations"),
react_native_target("java/com/facebook/react/uimanager:uimanager"),
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java
index 28f575e956998a..ba871e1679fc1d 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java
@@ -14,6 +14,8 @@
import com.facebook.react.bridge.ReadableNativeMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.common.annotations.VisibleForTesting;
+import com.facebook.react.common.mapbuffer.ReadableMapBuffer;
+import com.facebook.react.config.ReactFeatureFlags;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.IViewManagerWithChildren;
import com.facebook.react.uimanager.ReactStylesDiffMap;
@@ -31,6 +33,12 @@ public class ReactTextViewManager
extends ReactTextAnchorViewManager
implements IViewManagerWithChildren {
+ private static final short TX_STATE_KEY_ATTRIBUTED_STRING = 0;
+ private static final short TX_STATE_KEY_PARAGRAPH_ATTRIBUTES = 1;
+ // used for text input
+ private static final short TX_STATE_KEY_HASH = 2;
+ private static final short TX_STATE_KEY_MOST_RECENT_EVENT_COUNT = 3;
+
@VisibleForTesting public static final String REACT_CLASS = "RCTText";
protected @Nullable ReactTextViewManagerCallback mReactTextViewManagerCallback;
@@ -87,6 +95,13 @@ public Object updateState(
return null;
}
+ if (ReactFeatureFlags.mapBufferSerializationEnabled) {
+ ReadableMapBuffer stateMapBuffer = stateWrapper.getStatDataMapBuffer();
+ if (stateMapBuffer != null) {
+ return getReactTextUpdate(view, props, stateMapBuffer);
+ }
+ }
+
ReadableNativeMap state = stateWrapper.getStateData();
if (state == null) {
return null;
@@ -111,6 +126,30 @@ public Object updateState(
TextAttributeProps.getJustificationMode(props));
}
+ private Object getReactTextUpdate(
+ ReactTextView view, ReactStylesDiffMap props, ReadableMapBuffer state) {
+
+ ReadableMapBuffer attributedString = state.getMapBuffer(TX_STATE_KEY_ATTRIBUTED_STRING);
+ ReadableMapBuffer paragraphAttributes = state.getMapBuffer(TX_STATE_KEY_PARAGRAPH_ATTRIBUTES);
+ Spannable spanned =
+ TextLayoutManagerMapBuffer.getOrCreateSpannableForText(
+ view.getContext(), attributedString, mReactTextViewManagerCallback);
+ view.setSpanned(spanned);
+
+ int textBreakStrategy =
+ TextAttributeProps.getTextBreakStrategy(
+ paragraphAttributes.getString(TextLayoutManagerMapBuffer.PA_KEY_TEXT_BREAK_STRATEGY));
+
+ return new ReactTextUpdate(
+ spanned,
+ -1, // UNUSED FOR TEXT
+ false, // TODO add this into local Data
+ TextAttributeProps.getTextAlignment(
+ props, TextLayoutManagerMapBuffer.isRTL(attributedString)),
+ textBreakStrategy,
+ TextAttributeProps.getJustificationMode(props));
+ }
+
@Override
public @Nullable Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.of(
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java
index 53ae6b4e502fcf..fe926cb743c513 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java
@@ -10,23 +10,52 @@
import android.graphics.Typeface;
import android.os.Build;
import android.text.Layout;
+import android.text.TextUtils;
import android.util.LayoutDirection;
import android.view.Gravity;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.common.mapbuffer.ReadableMapBuffer;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.ReactAccessibilityDelegate;
import com.facebook.react.uimanager.ReactStylesDiffMap;
import com.facebook.react.uimanager.ViewProps;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
// TODO: T63643819 refactor naming of TextAttributeProps to make explicit that this represents
// TextAttributes and not TextProps. As part of this refactor extract methods that don't belong to
// TextAttributeProps (e.g. TextAlign)
public class TextAttributeProps {
- private static final String INLINE_IMAGE_PLACEHOLDER = "I";
+ // constants for Text Attributes serialization
+ public static final short TA_KEY_FOREGROUND_COLOR = 0;
+ public static final short TA_KEY_BACKGROUND_COLOR = 1;
+ public static final short TA_KEY_OPACITY = 2;
+ public static final short TA_KEY_FONT_FAMILY = 3;
+ public static final short TA_KEY_FONT_SIZE = 4;
+ public static final short TA_KEY_FONT_SIZE_MULTIPLIER = 5;
+ public static final short TA_KEY_FONT_WEIGHT = 6;
+ public static final short TA_KEY_FONT_STYLE = 7;
+ public static final short TA_KEY_FONT_VARIANT = 8;
+ public static final short TA_KEY_ALLOW_FONT_SCALING = 9;
+ public static final short TA_KEY_LETTER_SPACING = 10;
+ public static final short TA_KEY_LINE_HEIGHT = 11;
+ public static final short TA_KEY_ALIGNMENT = 12;
+ public static final short TA_KEY_BEST_WRITING_DIRECTION = 13;
+ public static final short TA_KEY_TEXT_DECORATION_COLOR = 14;
+ public static final short TA_KEY_TEXT_DECORATION_LINE = 15;
+ public static final short TA_KEY_TEXT_DECORATION_LINE_STYLE = 16;
+ public static final short TA_KEY_TEXT_DECORATION_LINE_PATTERN = 17;
+ public static final short TA_KEY_TEXT_SHADOW_RAIDUS = 18;
+ public static final short TA_KEY_TEXT_SHADOW_COLOR = 19;
+ public static final short TA_KEY_IS_HIGHLIGHTED = 20;
+ public static final short TA_KEY_LAYOUT_DIRECTION = 21;
+ public static final short TA_KEY_ACCESSIBILITY_ROLE = 22;
+
public static final int UNSET = -1;
private static final String PROP_SHADOW_OFFSET = "textShadowOffset";
@@ -61,7 +90,7 @@ public class TextAttributeProps {
// `UNSET` is -1 and is the same as `LayoutDirection.UNDEFINED` but the symbol isn't available.
protected int mLayoutDirection = UNSET;
- protected TextTransform mTextTransform = TextTransform.UNSET;
+ protected TextTransform mTextTransform = TextTransform.NONE;
protected float mTextShadowOffsetDx = 0;
protected float mTextShadowOffsetDy = 0;
@@ -111,36 +140,123 @@ public class TextAttributeProps {
protected boolean mContainsImages = false;
protected float mHeightOfTallestInlineImage = Float.NaN;
- private final ReactStylesDiffMap mProps;
-
- public TextAttributeProps(ReactStylesDiffMap props) {
- mProps = props;
- setNumberOfLines(getIntProp(ViewProps.NUMBER_OF_LINES, UNSET));
- setLineHeight(getFloatProp(ViewProps.LINE_HEIGHT, UNSET));
- setLetterSpacing(getFloatProp(ViewProps.LETTER_SPACING, Float.NaN));
- setAllowFontScaling(getBooleanProp(ViewProps.ALLOW_FONT_SCALING, true));
- setFontSize(getFloatProp(ViewProps.FONT_SIZE, UNSET));
- setColor(props.hasKey(ViewProps.COLOR) ? props.getInt(ViewProps.COLOR, 0) : null);
- setColor(
+ private TextAttributeProps() {}
+
+ /**
+ * Build a TextAttributeProps using data from the {@link ReadableMapBuffer} received as a
+ * parameter.
+ */
+ public static TextAttributeProps fromReadableMapBuffer(ReadableMapBuffer props) {
+ TextAttributeProps result = new TextAttributeProps();
+
+ // TODO T83483191: Review constants that are not being set!
+ Iterator iterator = props.iterator();
+ while (iterator.hasNext()) {
+ ReadableMapBuffer.MapBufferEntry entry = iterator.next();
+ switch (entry.getKey()) {
+ case TA_KEY_FOREGROUND_COLOR:
+ result.setColor(entry.getInt(0));
+ break;
+ case TA_KEY_BACKGROUND_COLOR:
+ result.setBackgroundColor(entry.getInt(0));
+ break;
+ case TA_KEY_OPACITY:
+ break;
+ case TA_KEY_FONT_FAMILY:
+ result.setFontFamily(entry.getString());
+ break;
+ case TA_KEY_FONT_SIZE:
+ result.setFontSize((float) entry.getDouble(UNSET));
+ break;
+ case TA_KEY_FONT_SIZE_MULTIPLIER:
+ break;
+ case TA_KEY_FONT_WEIGHT:
+ result.setFontWeight(entry.getString());
+ break;
+ case TA_KEY_FONT_STYLE:
+ result.setFontStyle(entry.getString());
+ break;
+ case TA_KEY_FONT_VARIANT:
+ result.setFontVariant(entry.getReadableMapBuffer());
+ break;
+ case TA_KEY_ALLOW_FONT_SCALING:
+ result.setAllowFontScaling(entry.getBoolean(true));
+ break;
+ case TA_KEY_LETTER_SPACING:
+ result.setLetterSpacing((float) entry.getDouble(Float.NaN));
+ break;
+ case TA_KEY_LINE_HEIGHT:
+ result.setLineHeight((float) entry.getDouble(UNSET));
+ break;
+ case TA_KEY_ALIGNMENT:
+ break;
+ case TA_KEY_BEST_WRITING_DIRECTION:
+ break;
+ case TA_KEY_TEXT_DECORATION_COLOR:
+ break;
+ case TA_KEY_TEXT_DECORATION_LINE:
+ result.setTextDecorationLine(entry.getString());
+ break;
+ case TA_KEY_TEXT_DECORATION_LINE_STYLE:
+ break;
+ case TA_KEY_TEXT_DECORATION_LINE_PATTERN:
+ break;
+ case TA_KEY_TEXT_SHADOW_RAIDUS:
+ result.setTextShadowRadius(entry.getInt(1));
+ break;
+ case TA_KEY_TEXT_SHADOW_COLOR:
+ result.setTextShadowColor(entry.getInt(DEFAULT_TEXT_SHADOW_COLOR));
+ break;
+ case TA_KEY_IS_HIGHLIGHTED:
+ break;
+ case TA_KEY_LAYOUT_DIRECTION:
+ result.setLayoutDirection(entry.getString());
+ break;
+ case TA_KEY_ACCESSIBILITY_ROLE:
+ result.setAccessibilityRole(entry.getString());
+ break;
+ }
+ }
+
+ // TODO T83483191: Review why the following props are not serialized:
+ // setNumberOfLines
+ // setColor
+ // setIncludeFontPadding
+ // setTextShadowOffset
+ // setTextTransform
+ return result;
+ }
+
+ public static TextAttributeProps fromReadableMap(ReactStylesDiffMap props) {
+ TextAttributeProps result = new TextAttributeProps();
+ result.setNumberOfLines(getIntProp(props, ViewProps.NUMBER_OF_LINES, UNSET));
+ result.setLineHeight(getFloatProp(props, ViewProps.LINE_HEIGHT, UNSET));
+ result.setLetterSpacing(getFloatProp(props, ViewProps.LETTER_SPACING, Float.NaN));
+ result.setAllowFontScaling(getBooleanProp(props, ViewProps.ALLOW_FONT_SCALING, true));
+ result.setFontSize(getFloatProp(props, ViewProps.FONT_SIZE, UNSET));
+ result.setColor(props.hasKey(ViewProps.COLOR) ? props.getInt(ViewProps.COLOR, 0) : null);
+ result.setColor(
props.hasKey(ViewProps.FOREGROUND_COLOR)
? props.getInt(ViewProps.FOREGROUND_COLOR, 0)
: null);
- setBackgroundColor(
+ result.setBackgroundColor(
props.hasKey(ViewProps.BACKGROUND_COLOR)
? props.getInt(ViewProps.BACKGROUND_COLOR, 0)
: null);
- setFontFamily(getStringProp(ViewProps.FONT_FAMILY));
- setFontWeight(getStringProp(ViewProps.FONT_WEIGHT));
- setFontStyle(getStringProp(ViewProps.FONT_STYLE));
- setFontVariant(getArrayProp(ViewProps.FONT_VARIANT));
- setIncludeFontPadding(getBooleanProp(ViewProps.INCLUDE_FONT_PADDING, true));
- setTextDecorationLine(getStringProp(ViewProps.TEXT_DECORATION_LINE));
- setTextShadowOffset(props.hasKey(PROP_SHADOW_OFFSET) ? props.getMap(PROP_SHADOW_OFFSET) : null);
- setTextShadowRadius(getIntProp(PROP_SHADOW_RADIUS, 1));
- setTextShadowColor(getIntProp(PROP_SHADOW_COLOR, DEFAULT_TEXT_SHADOW_COLOR));
- setTextTransform(getStringProp(PROP_TEXT_TRANSFORM));
- setLayoutDirection(getStringProp(ViewProps.LAYOUT_DIRECTION));
- setAccessibilityRole(getStringProp(ViewProps.ACCESSIBILITY_ROLE));
+ result.setFontFamily(getStringProp(props, ViewProps.FONT_FAMILY));
+ result.setFontWeight(getStringProp(props, ViewProps.FONT_WEIGHT));
+ result.setFontStyle(getStringProp(props, ViewProps.FONT_STYLE));
+ result.setFontVariant(getArrayProp(props, ViewProps.FONT_VARIANT));
+ result.setIncludeFontPadding(getBooleanProp(props, ViewProps.INCLUDE_FONT_PADDING, true));
+ result.setTextDecorationLine(getStringProp(props, ViewProps.TEXT_DECORATION_LINE));
+ result.setTextShadowOffset(
+ props.hasKey(PROP_SHADOW_OFFSET) ? props.getMap(PROP_SHADOW_OFFSET) : null);
+ result.setTextShadowRadius(getIntProp(props, PROP_SHADOW_RADIUS, 1));
+ result.setTextShadowColor(getIntProp(props, PROP_SHADOW_COLOR, DEFAULT_TEXT_SHADOW_COLOR));
+ result.setTextTransform(getStringProp(props, PROP_TEXT_TRANSFORM));
+ result.setLayoutDirection(getStringProp(props, ViewProps.LAYOUT_DIRECTION));
+ result.setAccessibilityRole(getStringProp(props, ViewProps.ACCESSIBILITY_ROLE));
+ return result;
}
public static int getTextAlignment(ReactStylesDiffMap props, boolean isRTL) {
@@ -178,7 +294,8 @@ public static int getJustificationMode(ReactStylesDiffMap props) {
return DEFAULT_JUSTIFICATION_MODE;
}
- private boolean getBooleanProp(String name, boolean defaultValue) {
+ private static boolean getBooleanProp(
+ ReactStylesDiffMap mProps, String name, boolean defaultValue) {
if (mProps.hasKey(name)) {
return mProps.getBoolean(name, defaultValue);
} else {
@@ -186,7 +303,7 @@ private boolean getBooleanProp(String name, boolean defaultValue) {
}
}
- private String getStringProp(String name) {
+ private static String getStringProp(ReactStylesDiffMap mProps, String name) {
if (mProps.hasKey(name)) {
return mProps.getString(name);
} else {
@@ -194,7 +311,7 @@ private String getStringProp(String name) {
}
}
- private int getIntProp(String name, int defaultvalue) {
+ private static int getIntProp(ReactStylesDiffMap mProps, String name, int defaultvalue) {
if (mProps.hasKey(name)) {
return mProps.getInt(name, defaultvalue);
} else {
@@ -202,7 +319,7 @@ private int getIntProp(String name, int defaultvalue) {
}
}
- private float getFloatProp(String name, float defaultvalue) {
+ private static float getFloatProp(ReactStylesDiffMap mProps, String name, float defaultvalue) {
if (mProps.hasKey(name)) {
return mProps.getFloat(name, defaultvalue);
} else {
@@ -210,7 +327,7 @@ private float getFloatProp(String name, float defaultvalue) {
}
}
- private @Nullable ReadableArray getArrayProp(String name) {
+ private static @Nullable ReadableArray getArrayProp(ReactStylesDiffMap mProps, String name) {
if (mProps.hasKey(name)) {
return mProps.getArray(name);
} else {
@@ -309,6 +426,40 @@ private void setFontVariant(@Nullable ReadableArray fontVariant) {
mFontFeatureSettings = ReactTypefaceUtils.parseFontVariant(fontVariant);
}
+ private void setFontVariant(@Nullable ReadableMapBuffer fontVariant) {
+ if (fontVariant == null || fontVariant.getCount() == 0) {
+ mFontFeatureSettings = null;
+ return;
+ }
+
+ List features = new ArrayList<>();
+ Iterator iterator = fontVariant.iterator();
+ while (iterator.hasNext()) {
+ ReadableMapBuffer.MapBufferEntry entry = iterator.next();
+ String value = entry.getString();
+ if (value != null) {
+ switch (value) {
+ case "small-caps":
+ features.add("'smcp'");
+ break;
+ case "oldstyle-nums":
+ features.add("'onum'");
+ break;
+ case "lining-nums":
+ features.add("'lnum'");
+ break;
+ case "tabular-nums":
+ features.add("'tnum'");
+ break;
+ case "proportional-nums":
+ features.add("'pnum'");
+ break;
+ }
+ }
+ }
+ mFontFeatureSettings = TextUtils.join(", ", features);
+ }
+
/**
* /* This code is duplicated in ReactTextInputManager /* TODO: Factor into a common place they
* can both use
@@ -380,17 +531,23 @@ private void setTextShadowOffset(ReadableMap offsetMap) {
}
}
- private void setLayoutDirection(@Nullable String layoutDirection) {
+ public static int getLayoutDirection(@Nullable String layoutDirection) {
+ int androidLayoutDirection;
if (layoutDirection == null || "undefined".equals(layoutDirection)) {
- mLayoutDirection = UNSET;
+ androidLayoutDirection = UNSET;
} else if ("rtl".equals(layoutDirection)) {
- mLayoutDirection = LayoutDirection.RTL;
+ androidLayoutDirection = LayoutDirection.RTL;
} else if ("ltr".equals(layoutDirection)) {
- mLayoutDirection = LayoutDirection.LTR;
+ androidLayoutDirection = LayoutDirection.LTR;
} else {
throw new JSApplicationIllegalArgumentException(
"Invalid layoutDirection: " + layoutDirection);
}
+ return androidLayoutDirection;
+ }
+
+ private void setLayoutDirection(@Nullable String layoutDirection) {
+ mLayoutDirection = getLayoutDirection(layoutDirection);
}
private void setTextShadowRadius(float textShadowRadius) {
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java
index c2f428075f451d..0b88a8a28d9fbb 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java
@@ -70,11 +70,11 @@ public class TextLayoutManager {
public static boolean isRTL(ReadableMap attributedString) {
ReadableArray fragments = attributedString.getArray("fragments");
- for (int i = 0, length = fragments.size(); i < length; i++) {
+ for (int i = 0; i < fragments.size(); i++) {
ReadableMap fragment = fragments.getMap(i);
- ReactStylesDiffMap map = new ReactStylesDiffMap(fragment.getMap("textAttributes"));
- TextAttributeProps textAttributes = new TextAttributeProps(map);
- return textAttributes.mLayoutDirection == LayoutDirection.RTL;
+ ReadableMap map = fragment.getMap("textAttributes");
+ return TextAttributeProps.getLayoutDirection(map.getString(ViewProps.LAYOUT_DIRECTION))
+ == LayoutDirection.RTL;
}
return false;
}
@@ -105,7 +105,8 @@ private static void buildSpannableFromFragment(
// ReactRawText
TextAttributeProps textAttributes =
- new TextAttributeProps(new ReactStylesDiffMap(fragment.getMap("textAttributes")));
+ TextAttributeProps.fromReadableMap(
+ new ReactStylesDiffMap(fragment.getMap("textAttributes")));
sb.append(TextTransform.apply(fragment.getString("string"), textAttributes.mTextTransform));
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java
new file mode 100644
index 00000000000000..7af9a540f8644f
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java
@@ -0,0 +1,588 @@
+/*
+ * Copyright (c) Facebook, Inc. and its 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 static com.facebook.react.views.text.TextAttributeProps.UNSET;
+
+import android.content.Context;
+import android.os.Build;
+import android.text.BoringLayout;
+import android.text.Layout;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.StaticLayout;
+import android.text.TextPaint;
+import android.util.LayoutDirection;
+import android.util.LruCache;
+import android.view.View;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import com.facebook.common.logging.FLog;
+import com.facebook.react.bridge.WritableArray;
+import com.facebook.react.common.build.ReactBuildConfig;
+import com.facebook.react.common.mapbuffer.ReadableMapBuffer;
+import com.facebook.react.uimanager.PixelUtil;
+import com.facebook.react.uimanager.ReactAccessibilityDelegate;
+import com.facebook.yoga.YogaConstants;
+import com.facebook.yoga.YogaMeasureMode;
+import com.facebook.yoga.YogaMeasureOutput;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+/** Class responsible of creating {@link Spanned} object for the JS representation of Text */
+public class TextLayoutManagerMapBuffer {
+
+ // constants for AttributedString serialization
+ public static final short AS_KEY_HASH = 0;
+ public static final short AS_KEY_STRING = 1;
+ public static final short AS_KEY_FRAGMENTS = 2;
+ public static final short AS_KEY_CACHE_ID = 3;
+
+ // constants for Fragment serialization
+ public static final short FR_KEY_STRING = 0;
+ public static final short FR_KEY_REACT_TAG = 1;
+ public static final short FR_KEY_IS_ATTACHMENT = 2;
+ public static final short FR_KEY_WIDTH = 3;
+ public static final short FR_KEY_HEIGHT = 4;
+ public static final short FR_KEY_TEXT_ATTRIBUTES = 5;
+
+ // constants for ParagraphAttributes serialization
+ public static final short PA_KEY_MAX_NUMBER_OF_LINES = 0;
+ public static final short PA_KEY_ELLIPSIZE_MODE = 1;
+ public static final short PA_KEY_TEXT_BREAK_STRATEGY = 2;
+ public static final short PA_KEY_ADJUST_FONT_SIZE_TO_FIT = 3;
+ public static final short PA_KEY_INCLUDE_FONT_PADDING = 4;
+
+ private static final boolean ENABLE_MEASURE_LOGGING = ReactBuildConfig.DEBUG && false;
+
+ private static final String TAG = TextLayoutManagerMapBuffer.class.getSimpleName();
+
+ // It's important to pass the ANTI_ALIAS_FLAG flag to the constructor rather than setting it
+ // later by calling setFlags. This is because the latter approach triggers a bug on Android 4.4.2.
+ // The bug is that unicode emoticons aren't measured properly which causes text to be clipped.
+ private static final TextPaint sTextPaintInstance = new TextPaint(TextPaint.ANTI_ALIAS_FLAG);
+
+ // Specifies the amount of spannable that are stored into the {@link sSpannableCache}.
+ private static final short spannableCacheSize = 100;
+
+ private static final String INLINE_VIEW_PLACEHOLDER = "0";
+
+ private static final Object sSpannableCacheLock = new Object();
+ private static final boolean DEFAULT_INCLUDE_FONT_PADDING = true;
+ private static final LruCache sSpannableCache =
+ new LruCache<>(spannableCacheSize);
+ private static final ConcurrentHashMap sTagToSpannableCache =
+ new ConcurrentHashMap<>();
+
+ public static void setCachedSpannabledForTag(int reactTag, @NonNull Spannable sp) {
+ if (ENABLE_MEASURE_LOGGING) {
+ FLog.e(TAG, "Set cached spannable for tag[" + reactTag + "]: " + sp.toString());
+ }
+ sTagToSpannableCache.put(reactTag, sp);
+ }
+
+ public static void deleteCachedSpannableForTag(int reactTag) {
+ if (ENABLE_MEASURE_LOGGING) {
+ FLog.e(TAG, "Delete cached spannable for tag[" + reactTag + "]");
+ }
+ sTagToSpannableCache.remove(reactTag);
+ }
+
+ public static boolean isRTL(ReadableMapBuffer attributedString) {
+ ReadableMapBuffer fragments = attributedString.getMapBuffer(AS_KEY_FRAGMENTS);
+ if (fragments.getCount() == 0) {
+ return false;
+ }
+
+ ReadableMapBuffer fragment = fragments.getMapBuffer((short) 0);
+ ReadableMapBuffer textAttributes = fragment.getMapBuffer(FR_KEY_TEXT_ATTRIBUTES);
+ return TextAttributeProps.getLayoutDirection(
+ textAttributes.getString(TextAttributeProps.TA_KEY_LAYOUT_DIRECTION))
+ == LayoutDirection.RTL;
+ }
+
+ private static void buildSpannableFromFragment(
+ Context context,
+ ReadableMapBuffer fragments,
+ SpannableStringBuilder sb,
+ List ops) {
+
+ for (short i = 0, length = fragments.getCount(); i < length; i++) {
+ ReadableMapBuffer fragment = fragments.getMapBuffer(i);
+ int start = sb.length();
+
+ TextAttributeProps textAttributes =
+ TextAttributeProps.fromReadableMapBuffer(fragment.getMapBuffer(FR_KEY_TEXT_ATTRIBUTES));
+
+ sb.append(
+ TextTransform.apply(fragment.getString(FR_KEY_STRING), textAttributes.mTextTransform));
+
+ int end = sb.length();
+ int reactTag =
+ fragment.hasKey(FR_KEY_REACT_TAG) ? fragment.getInt(FR_KEY_REACT_TAG) : View.NO_ID;
+ if (fragment.hasKey(FR_KEY_IS_ATTACHMENT) && fragment.getBoolean(FR_KEY_IS_ATTACHMENT)) {
+ float width = PixelUtil.toPixelFromSP(fragment.getDouble(FR_KEY_WIDTH));
+ float height = PixelUtil.toPixelFromSP(fragment.getDouble(FR_KEY_HEIGHT));
+ ops.add(
+ new SetSpanOperation(
+ sb.length() - INLINE_VIEW_PLACEHOLDER.length(),
+ sb.length(),
+ new TextInlineViewPlaceholderSpan(reactTag, (int) width, (int) height)));
+ } else if (end >= start) {
+ if (ReactAccessibilityDelegate.AccessibilityRole.LINK.equals(
+ textAttributes.mAccessibilityRole)) {
+ ops.add(
+ new SetSpanOperation(
+ start, end, new ReactClickableSpan(reactTag, textAttributes.mColor)));
+ } else if (textAttributes.mIsColorSet) {
+ ops.add(
+ new SetSpanOperation(
+ start, end, new ReactForegroundColorSpan(textAttributes.mColor)));
+ }
+ if (textAttributes.mIsBackgroundColorSet) {
+ ops.add(
+ new SetSpanOperation(
+ start, end, new ReactBackgroundColorSpan(textAttributes.mBackgroundColor)));
+ }
+ if (!Float.isNaN(textAttributes.getLetterSpacing())) {
+ ops.add(
+ new SetSpanOperation(
+ start, end, new CustomLetterSpacingSpan(textAttributes.getLetterSpacing())));
+ }
+ ops.add(
+ new SetSpanOperation(start, end, new ReactAbsoluteSizeSpan(textAttributes.mFontSize)));
+ if (textAttributes.mFontStyle != UNSET
+ || textAttributes.mFontWeight != UNSET
+ || textAttributes.mFontFamily != null) {
+ ops.add(
+ new SetSpanOperation(
+ start,
+ end,
+ new CustomStyleSpan(
+ textAttributes.mFontStyle,
+ textAttributes.mFontWeight,
+ textAttributes.mFontFeatureSettings,
+ textAttributes.mFontFamily,
+ context.getAssets())));
+ }
+ if (textAttributes.mIsUnderlineTextDecorationSet) {
+ ops.add(new SetSpanOperation(start, end, new ReactUnderlineSpan()));
+ }
+ if (textAttributes.mIsLineThroughTextDecorationSet) {
+ ops.add(new SetSpanOperation(start, end, new ReactStrikethroughSpan()));
+ }
+ if (textAttributes.mTextShadowOffsetDx != 0 || textAttributes.mTextShadowOffsetDy != 0) {
+ ops.add(
+ new SetSpanOperation(
+ start,
+ end,
+ new ShadowStyleSpan(
+ textAttributes.mTextShadowOffsetDx,
+ textAttributes.mTextShadowOffsetDy,
+ textAttributes.mTextShadowRadius,
+ textAttributes.mTextShadowColor)));
+ }
+ if (!Float.isNaN(textAttributes.getEffectiveLineHeight())) {
+ ops.add(
+ new SetSpanOperation(
+ start, end, new CustomLineHeightSpan(textAttributes.getEffectiveLineHeight())));
+ }
+
+ ops.add(new SetSpanOperation(start, end, new ReactTagSpan(reactTag)));
+ }
+ }
+ }
+
+ // public because both ReactTextViewManager and ReactTextInputManager need to use this
+ public static Spannable getOrCreateSpannableForText(
+ Context context,
+ ReadableMapBuffer attributedString,
+ @Nullable ReactTextViewManagerCallback reactTextViewManagerCallback) {
+
+ Spannable preparedSpannableText;
+
+ synchronized (sSpannableCacheLock) {
+ preparedSpannableText = sSpannableCache.get(attributedString);
+ if (preparedSpannableText != null) {
+ return preparedSpannableText;
+ }
+ }
+
+ preparedSpannableText =
+ createSpannableFromAttributedString(
+ context, attributedString, reactTextViewManagerCallback);
+
+ synchronized (sSpannableCacheLock) {
+ sSpannableCache.put(attributedString, preparedSpannableText);
+ }
+
+ return preparedSpannableText;
+ }
+
+ private static Spannable createSpannableFromAttributedString(
+ Context context,
+ ReadableMapBuffer attributedString,
+ @Nullable ReactTextViewManagerCallback reactTextViewManagerCallback) {
+
+ SpannableStringBuilder sb = new SpannableStringBuilder();
+
+ // The {@link SpannableStringBuilder} implementation require setSpan operation to be called
+ // up-to-bottom, otherwise all the spannables that are within the region for which one may set
+ // a new spannable will be wiped out
+ List ops = new ArrayList<>();
+
+ buildSpannableFromFragment(context, attributedString.getMapBuffer(AS_KEY_FRAGMENTS), sb, ops);
+
+ // TODO T31905686: add support for inline Images
+ // While setting the Spans on the final text, we also check whether any of them are images.
+ int priority = 0;
+ for (SetSpanOperation op : ops) {
+ // Actual order of calling {@code execute} does NOT matter,
+ // but the {@code priority} DOES matter.
+ op.execute(sb, priority);
+ priority++;
+ }
+
+ if (reactTextViewManagerCallback != null) {
+ reactTextViewManagerCallback.onPostProcessSpannable(sb);
+ }
+ return sb;
+ }
+
+ private static Layout createLayout(
+ Spannable text,
+ BoringLayout.Metrics boring,
+ float width,
+ YogaMeasureMode widthYogaMeasureMode,
+ boolean includeFontPadding,
+ int textBreakStrategy) {
+ Layout layout;
+ int spanLength = text.length();
+ boolean unconstrainedWidth = widthYogaMeasureMode == YogaMeasureMode.UNDEFINED || width < 0;
+ TextPaint textPaint = sTextPaintInstance;
+ float desiredWidth = boring == null ? Layout.getDesiredWidth(text, textPaint) : Float.NaN;
+
+ if (boring == null
+ && (unconstrainedWidth
+ || (!YogaConstants.isUndefined(desiredWidth) && desiredWidth <= width))) {
+ // Is used when the width is not known and the text is not boring, ie. if it contains
+ // unicode characters.
+
+ int hintWidth = (int) Math.ceil(desiredWidth);
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+ layout =
+ new StaticLayout(
+ text,
+ textPaint,
+ hintWidth,
+ Layout.Alignment.ALIGN_NORMAL,
+ 1.f,
+ 0.f,
+ includeFontPadding);
+ } else {
+ layout =
+ StaticLayout.Builder.obtain(text, 0, spanLength, textPaint, hintWidth)
+ .setAlignment(Layout.Alignment.ALIGN_NORMAL)
+ .setLineSpacing(0.f, 1.f)
+ .setIncludePad(includeFontPadding)
+ .setBreakStrategy(textBreakStrategy)
+ .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+ .build();
+ }
+
+ } else if (boring != null && (unconstrainedWidth || boring.width <= width)) {
+ // Is used for single-line, boring text when the width is either unknown or bigger
+ // than the width of the text.
+ layout =
+ BoringLayout.make(
+ text,
+ textPaint,
+ boring.width,
+ Layout.Alignment.ALIGN_NORMAL,
+ 1.f,
+ 0.f,
+ boring,
+ includeFontPadding);
+ } else {
+ // Is used for multiline, boring text and the width is known.
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+ layout =
+ new StaticLayout(
+ text,
+ textPaint,
+ (int) width,
+ Layout.Alignment.ALIGN_NORMAL,
+ 1.f,
+ 0.f,
+ includeFontPadding);
+ } else {
+ StaticLayout.Builder builder =
+ StaticLayout.Builder.obtain(text, 0, spanLength, textPaint, (int) width)
+ .setAlignment(Layout.Alignment.ALIGN_NORMAL)
+ .setLineSpacing(0.f, 1.f)
+ .setIncludePad(includeFontPadding)
+ .setBreakStrategy(textBreakStrategy)
+ .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ builder.setUseLineSpacingFromFallbacks(true);
+ }
+
+ layout = builder.build();
+ }
+ }
+ return layout;
+ }
+
+ public static long measureText(
+ Context context,
+ ReadableMapBuffer attributedString,
+ ReadableMapBuffer paragraphAttributes,
+ float width,
+ YogaMeasureMode widthYogaMeasureMode,
+ float height,
+ YogaMeasureMode heightYogaMeasureMode,
+ ReactTextViewManagerCallback reactTextViewManagerCallback,
+ @Nullable float[] attachmentsPositions) {
+
+ // TODO(5578671): Handle text direction (see View#getTextDirectionHeuristic)
+ TextPaint textPaint = sTextPaintInstance;
+ Spannable text;
+ if (attributedString.hasKey(AS_KEY_CACHE_ID)) {
+ int cacheId = attributedString.getInt(AS_KEY_CACHE_ID);
+ if (ENABLE_MEASURE_LOGGING) {
+ FLog.e(TAG, "Get cached spannable for cacheId[" + cacheId + "]");
+ }
+ if (sTagToSpannableCache.containsKey(cacheId)) {
+ text = sTagToSpannableCache.get(cacheId);
+ if (ENABLE_MEASURE_LOGGING) {
+ FLog.e(TAG, "Text for spannable found for cacheId[" + cacheId + "]: " + text.toString());
+ }
+ } else {
+ if (ENABLE_MEASURE_LOGGING) {
+ FLog.e(TAG, "No cached spannable found for cacheId[" + cacheId + "]");
+ }
+ return 0;
+ }
+ } else {
+ text = getOrCreateSpannableForText(context, attributedString, reactTextViewManagerCallback);
+ }
+
+ int textBreakStrategy =
+ TextAttributeProps.getTextBreakStrategy(
+ paragraphAttributes.getString(PA_KEY_TEXT_BREAK_STRATEGY));
+ boolean includeFontPadding =
+ paragraphAttributes.hasKey(PA_KEY_INCLUDE_FONT_PADDING)
+ ? paragraphAttributes.getBoolean(PA_KEY_INCLUDE_FONT_PADDING)
+ : DEFAULT_INCLUDE_FONT_PADDING;
+
+ if (text == null) {
+ throw new IllegalStateException("Spannable element has not been prepared in onBeforeLayout");
+ }
+
+ BoringLayout.Metrics boring = BoringLayout.isBoring(text, textPaint);
+ float desiredWidth = boring == null ? Layout.getDesiredWidth(text, textPaint) : Float.NaN;
+
+ // technically, width should never be negative, but there is currently a bug in
+ boolean unconstrainedWidth = widthYogaMeasureMode == YogaMeasureMode.UNDEFINED || width < 0;
+
+ Layout layout =
+ createLayout(
+ text, boring, width, widthYogaMeasureMode, includeFontPadding, textBreakStrategy);
+
+ int maximumNumberOfLines =
+ paragraphAttributes.hasKey(PA_KEY_MAX_NUMBER_OF_LINES)
+ ? paragraphAttributes.getInt(PA_KEY_MAX_NUMBER_OF_LINES)
+ : UNSET;
+
+ int calculatedLineCount =
+ maximumNumberOfLines == UNSET || maximumNumberOfLines == 0
+ ? layout.getLineCount()
+ : Math.min(maximumNumberOfLines, layout.getLineCount());
+
+ // Instead of using `layout.getWidth()` (which may yield a significantly larger width for
+ // text that is wrapping), compute width using the longest line.
+ float calculatedWidth = 0;
+ if (widthYogaMeasureMode == YogaMeasureMode.EXACTLY) {
+ calculatedWidth = width;
+ } else {
+ for (int lineIndex = 0; lineIndex < calculatedLineCount; lineIndex++) {
+ float lineWidth = layout.getLineWidth(lineIndex);
+ if (lineWidth > calculatedWidth) {
+ calculatedWidth = lineWidth;
+ }
+ }
+ if (widthYogaMeasureMode == YogaMeasureMode.AT_MOST && calculatedWidth > width) {
+ calculatedWidth = width;
+ }
+ }
+
+ float calculatedHeight = height;
+ if (heightYogaMeasureMode != YogaMeasureMode.EXACTLY) {
+ calculatedHeight = layout.getLineBottom(calculatedLineCount - 1);
+ if (heightYogaMeasureMode == YogaMeasureMode.AT_MOST && calculatedHeight > height) {
+ calculatedHeight = height;
+ }
+ }
+
+ // Calculate the positions of the attachments (views) that will be rendered inside the
+ // Spanned Text. The following logic is only executed when a text contains views inside.
+ // This follows a similar logic than used in pre-fabric (see ReactTextView.onLayout method).
+ int attachmentIndex = 0;
+ int lastAttachmentFoundInSpan;
+ for (int i = 0; i < text.length(); i = lastAttachmentFoundInSpan) {
+ lastAttachmentFoundInSpan =
+ text.nextSpanTransition(i, text.length(), TextInlineViewPlaceholderSpan.class);
+ TextInlineViewPlaceholderSpan[] placeholders =
+ text.getSpans(i, lastAttachmentFoundInSpan, TextInlineViewPlaceholderSpan.class);
+ for (TextInlineViewPlaceholderSpan placeholder : placeholders) {
+ int start = text.getSpanStart(placeholder);
+ int line = layout.getLineForOffset(start);
+ boolean isLineTruncated = layout.getEllipsisCount(line) > 0;
+ // This truncation check works well on recent versions of Android (tested on 5.1.1 and
+ // 6.0.1) but not on Android 4.4.4. The reason is that getEllipsisCount is buggy on
+ // Android 4.4.4. Specifically, it incorrectly returns 0 if an inline view is the
+ // first thing to be truncated.
+ if (!(isLineTruncated && start >= layout.getLineStart(line) + layout.getEllipsisStart(line))
+ || start >= layout.getLineEnd(line)) {
+ float placeholderWidth = placeholder.getWidth();
+ float placeholderHeight = placeholder.getHeight();
+ // Calculate if the direction of the placeholder character is Right-To-Left.
+ boolean isRtlChar = layout.isRtlCharAt(start);
+ boolean isRtlParagraph = layout.getParagraphDirection(line) == Layout.DIR_RIGHT_TO_LEFT;
+ float placeholderLeftPosition;
+ // There's a bug on Samsung devices where calling getPrimaryHorizontal on
+ // the last offset in the layout will result in an endless loop. Work around
+ // this bug by avoiding getPrimaryHorizontal in that case.
+ if (start == text.length() - 1) {
+ placeholderLeftPosition =
+ isRtlParagraph
+ // Equivalent to `layout.getLineLeft(line)` but `getLineLeft` returns
+ // incorrect
+ // values when the paragraph is RTL and `setSingleLine(true)`.
+ ? calculatedWidth - layout.getLineWidth(line)
+ : layout.getLineRight(line) - placeholderWidth;
+ } else {
+ // The direction of the paragraph may not be exactly the direction the string is
+ // heading
+ // in at the
+ // position of the placeholder. So, if the direction of the character is the same
+ // as the
+ // paragraph
+ // use primary, secondary otherwise.
+ boolean characterAndParagraphDirectionMatch = isRtlParagraph == isRtlChar;
+ placeholderLeftPosition =
+ characterAndParagraphDirectionMatch
+ ? layout.getPrimaryHorizontal(start)
+ : layout.getSecondaryHorizontal(start);
+ if (isRtlParagraph) {
+ // Adjust `placeholderLeftPosition` to work around an Android bug.
+ // The bug is when the paragraph is RTL and `setSingleLine(true)`, some layout
+ // methods such as `getPrimaryHorizontal`, `getSecondaryHorizontal`, and
+ // `getLineRight` return incorrect values. Their return values seem to be off
+ // by the same number of pixels so subtracting these values cancels out the
+ // error.
+ //
+ // The result is equivalent to bugless versions of
+ // `getPrimaryHorizontal`/`getSecondaryHorizontal`.
+ placeholderLeftPosition =
+ calculatedWidth - (layout.getLineRight(line) - placeholderLeftPosition);
+ }
+ if (isRtlChar) {
+ placeholderLeftPosition -= placeholderWidth;
+ }
+ }
+ // Vertically align the inline view to the baseline of the line of text.
+ float placeholderTopPosition = layout.getLineBaseline(line) - placeholderHeight;
+ int attachmentPosition = attachmentIndex * 2;
+
+ // The attachment array returns the positions of each of the attachments as
+ attachmentsPositions[attachmentPosition] =
+ PixelUtil.toSPFromPixel(placeholderTopPosition);
+ attachmentsPositions[attachmentPosition + 1] =
+ PixelUtil.toSPFromPixel(placeholderLeftPosition);
+ attachmentIndex++;
+ }
+ }
+ }
+
+ float widthInSP = PixelUtil.toSPFromPixel(calculatedWidth);
+ float heightInSP = PixelUtil.toSPFromPixel(calculatedHeight);
+
+ if (ENABLE_MEASURE_LOGGING) {
+ FLog.e(
+ TAG,
+ "TextMeasure call ('"
+ + text
+ + "'): w: "
+ + calculatedWidth
+ + " px - h: "
+ + calculatedHeight
+ + " px - w : "
+ + widthInSP
+ + " sp - h: "
+ + heightInSP
+ + " sp");
+ }
+
+ return YogaMeasureOutput.make(widthInSP, heightInSP);
+ }
+
+ public static WritableArray measureLines(
+ @NonNull Context context,
+ ReadableMapBuffer attributedString,
+ ReadableMapBuffer paragraphAttributes,
+ float width) {
+
+ TextPaint textPaint = sTextPaintInstance;
+ Spannable text = getOrCreateSpannableForText(context, attributedString, null);
+ BoringLayout.Metrics boring = BoringLayout.isBoring(text, textPaint);
+
+ int textBreakStrategy =
+ TextAttributeProps.getTextBreakStrategy(
+ paragraphAttributes.getString(PA_KEY_TEXT_BREAK_STRATEGY));
+ boolean includeFontPadding =
+ paragraphAttributes.hasKey(PA_KEY_INCLUDE_FONT_PADDING)
+ ? paragraphAttributes.getBoolean(PA_KEY_INCLUDE_FONT_PADDING)
+ : DEFAULT_INCLUDE_FONT_PADDING;
+
+ Layout layout =
+ createLayout(
+ text, boring, width, YogaMeasureMode.EXACTLY, includeFontPadding, textBreakStrategy);
+ return FontMetricsUtil.getFontMetrics(text, layout, sTextPaintInstance, context);
+ }
+
+ // TODO T31905686: This class should be private
+ public static class SetSpanOperation {
+ protected int start, end;
+ protected ReactSpan what;
+
+ public SetSpanOperation(int start, int end, ReactSpan what) {
+ this.start = start;
+ this.end = end;
+ this.what = what;
+ }
+
+ public void execute(Spannable sb, int priority) {
+ // All spans will automatically extend to the right of the text, but not the left - except
+ // for spans that start at the beginning of the text.
+ int spanFlags = Spannable.SPAN_EXCLUSIVE_INCLUSIVE;
+ if (start == 0) {
+ spanFlags = Spannable.SPAN_INCLUSIVE_INCLUSIVE;
+ }
+
+ spanFlags &= ~Spannable.SPAN_PRIORITY;
+ spanFlags |= (priority << Spannable.SPAN_PRIORITY_SHIFT) & Spannable.SPAN_PRIORITY;
+
+ sb.setSpan(what, start, end, spanFlags);
+ }
+ }
+}
diff --git a/ReactAndroid/src/main/jni/react/jni/Android.mk b/ReactAndroid/src/main/jni/react/jni/Android.mk
index 9831c1aeef2c0e..8b24a2fdae571d 100644
--- a/ReactAndroid/src/main/jni/react/jni/Android.mk
+++ b/ReactAndroid/src/main/jni/react/jni/Android.mk
@@ -135,6 +135,7 @@ include $(REACT_SRC_DIR)/reactperflogger/jni/Android.mk
# Note: Update this only when ready to minimize breaking changes.
include $(REACT_SRC_DIR)/turbomodule/core/jni/Android.mk
include $(REACT_SRC_DIR)/fabric/jni/Android.mk
+include $(REACT_SRC_DIR)/common/mapbuffer/jni/Android.mk
# TODO(ramanpreet):
# Why doesn't this import-module call generate a jscexecutor.so file?
diff --git a/ReactCommon/react/renderer/attributedstring/Android.mk b/ReactCommon/react/renderer/attributedstring/Android.mk
index 2b90b0378380cb..1eee7957d277b5 100644
--- a/ReactCommon/react/renderer/attributedstring/Android.mk
+++ b/ReactCommon/react/renderer/attributedstring/Android.mk
@@ -21,7 +21,7 @@ LOCAL_CFLAGS += -fexceptions -frtti -std=c++14 -Wall
LOCAL_STATIC_LIBRARIES :=
-LOCAL_SHARED_LIBRARIES := libbetter libreact_render_graphics libyoga libfolly_futures glog libfolly_json libglog_init libreact_render_core libreact_render_debug librrc_view libreact_utils libreact_debug
+LOCAL_SHARED_LIBRARIES := libbetter libreact_render_graphics libyoga libfolly_futures glog libfolly_json libglog_init libreact_render_core libreact_render_debug librrc_view libreact_utils libreact_debug libreact_render_mapbuffer
include $(BUILD_SHARED_LIBRARY)
@@ -37,3 +37,4 @@ $(call import-module,react/renderer/graphics)
$(call import-module,react/utils)
$(call import-module,react/debug)
$(call import-module,yogajni)
+$(call import-module,react/renderer/mapbuffer)
diff --git a/ReactCommon/react/renderer/attributedstring/BUCK b/ReactCommon/react/renderer/attributedstring/BUCK
index a793db14709e85..9010d94c8834c0 100644
--- a/ReactCommon/react/renderer/attributedstring/BUCK
+++ b/ReactCommon/react/renderer/attributedstring/BUCK
@@ -37,6 +37,9 @@ rn_xplat_cxx_library(
"-std=c++14",
"-Wall",
],
+ fbandroid_deps = [
+ react_native_xplat_target("react/renderer/mapbuffer:mapbuffer"),
+ ],
fbobjc_compiler_flags = APPLE_COMPILER_FLAGS,
fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(),
force_static = True,
diff --git a/ReactCommon/react/renderer/attributedstring/conversions.h b/ReactCommon/react/renderer/attributedstring/conversions.h
index 203cc367ec8015..f1bce4debe1844 100644
--- a/ReactCommon/react/renderer/attributedstring/conversions.h
+++ b/ReactCommon/react/renderer/attributedstring/conversions.h
@@ -23,6 +23,11 @@
#include
#include
+#ifdef ANDROID
+#include
+#include
+#endif
+
#include
namespace facebook {
@@ -39,6 +44,7 @@ inline std::string toString(const EllipsizeMode &ellipsisMode) {
case EllipsizeMode::Middle:
return "middle";
}
+ abort();
}
inline void fromRawValue(const RawValue &value, EllipsizeMode &result) {
@@ -71,6 +77,7 @@ inline std::string toString(const TextBreakStrategy &textBreakStrategy) {
case TextBreakStrategy::Balanced:
return "balanced";
}
+ abort();
}
inline void fromRawValue(const RawValue &value, TextBreakStrategy &result) {
@@ -173,6 +180,7 @@ inline std::string toString(const FontStyle &fontStyle) {
case FontStyle::Oblique:
return "oblique";
}
+ abort();
}
inline void fromRawValue(const RawValue &value, FontVariant &result) {
@@ -267,6 +275,7 @@ inline std::string toString(const TextAlignment &textAlignment) {
case TextAlignment::Justified:
return "justified";
}
+ abort();
}
inline void fromRawValue(const RawValue &value, WritingDirection &result) {
@@ -295,6 +304,7 @@ inline std::string toString(const WritingDirection &writingDirection) {
case WritingDirection::RightToLeft:
return "rtl";
}
+ abort();
}
inline void fromRawValue(
@@ -337,6 +347,7 @@ inline std::string toString(
case TextDecorationLineType::UnderlineStrikethrough:
return "underline-strikethrough";
}
+ abort();
}
inline void fromRawValue(
@@ -368,6 +379,7 @@ inline std::string toString(
case TextDecorationLineStyle::Double:
return "double";
}
+ abort();
}
inline void fromRawValue(
@@ -411,6 +423,7 @@ inline std::string toString(
case TextDecorationLinePattern::DashDotDot:
return "dash-dot-dot";
}
+ abort();
}
inline std::string toString(const AccessibilityRole &accessibilityRole) {
@@ -470,6 +483,7 @@ inline std::string toString(const AccessibilityRole &accessibilityRole) {
case AccessibilityRole::Toolbar:
return "toolbar";
}
+ abort();
}
inline void fromRawValue(const RawValue &value, AccessibilityRole &result) {
@@ -809,6 +823,224 @@ inline folly::dynamic toDynamic(AttributedString::Range const &range) {
return dynamicValue;
}
+// constants for AttributedString serialization
+constexpr static Key AS_KEY_HASH = 0;
+constexpr static Key AS_KEY_STRING = 1;
+constexpr static Key AS_KEY_FRAGMENTS = 2;
+constexpr static Key AS_KEY_CACHE_ID = 3;
+
+// constants for Fragment serialization
+constexpr static Key FR_KEY_STRING = 0;
+constexpr static Key FR_KEY_REACT_TAG = 1;
+constexpr static Key FR_KEY_IS_ATTACHMENT = 2;
+constexpr static Key FR_KEY_WIDTH = 3;
+constexpr static Key FR_KEY_HEIGHT = 4;
+constexpr static Key FR_KEY_TEXT_ATTRIBUTES = 5;
+
+// constants for Text Attributes serialization
+constexpr static Key TA_KEY_FOREGROUND_COLOR = 0;
+constexpr static Key TA_KEY_BACKGROUND_COLOR = 1;
+constexpr static Key TA_KEY_OPACITY = 2;
+constexpr static Key TA_KEY_FONT_FAMILY = 3;
+constexpr static Key TA_KEY_FONT_SIZE = 4;
+constexpr static Key TA_KEY_FONT_SIZE_MULTIPLIER = 5;
+constexpr static Key TA_KEY_FONT_WEIGHT = 6;
+constexpr static Key TA_KEY_FONT_STYLE = 7;
+constexpr static Key TA_KEY_FONT_VARIANT = 8;
+constexpr static Key TA_KEY_ALLOW_FONT_SCALING = 9;
+constexpr static Key TA_KEY_LETTER_SPACING = 10;
+constexpr static Key TA_KEY_LINE_HEIGHT = 11;
+constexpr static Key TA_KEY_ALIGNMENT = 12;
+constexpr static Key TA_KEY_BEST_WRITING_DIRECTION = 13;
+constexpr static Key TA_KEY_TEXT_DECORATION_COLOR = 14;
+constexpr static Key TA_KEY_TEXT_DECORATION_LINE = 15;
+constexpr static Key TA_KEY_TEXT_DECORATION_LINE_STYLE = 16;
+constexpr static Key TA_KEY_TEXT_DECORATION_LINE_PATTERN = 17;
+constexpr static Key TA_KEY_TEXT_SHADOW_RAIDUS = 18;
+constexpr static Key TA_KEY_TEXT_SHADOW_COLOR = 19;
+constexpr static Key TA_KEY_IS_HIGHLIGHTED = 20;
+constexpr static Key TA_KEY_LAYOUT_DIRECTION = 21;
+constexpr static Key TA_KEY_ACCESSIBILITY_ROLE = 22;
+
+// constants for ParagraphAttributes serialization
+constexpr static Key PA_KEY_MAX_NUMBER_OF_LINES = 0;
+constexpr static Key PA_KEY_ELLIPSIZE_MODE = 1;
+constexpr static Key PA_KEY_TEXT_BREAK_STRATEGY = 2;
+constexpr static Key PA_KEY_ADJUST_FONT_SIZE_TO_FIT = 3;
+constexpr static Key PA_KEY_INCLUDE_FONT_PADDING = 4;
+
+inline MapBuffer toMapBuffer(const ParagraphAttributes ¶graphAttributes) {
+ auto builder = MapBufferBuilder();
+ builder.putInt(
+ PA_KEY_MAX_NUMBER_OF_LINES, paragraphAttributes.maximumNumberOfLines);
+ builder.putString(
+ PA_KEY_ELLIPSIZE_MODE, toString(paragraphAttributes.ellipsizeMode));
+ builder.putString(
+ PA_KEY_TEXT_BREAK_STRATEGY,
+ toString(paragraphAttributes.textBreakStrategy));
+ builder.putBool(
+ PA_KEY_ADJUST_FONT_SIZE_TO_FIT, paragraphAttributes.adjustsFontSizeToFit);
+ builder.putBool(
+ PA_KEY_INCLUDE_FONT_PADDING, paragraphAttributes.includeFontPadding);
+
+ return builder.build();
+}
+
+inline MapBuffer toMapBuffer(const FontVariant &fontVariant) {
+ auto builder = MapBufferBuilder();
+ int index = 0;
+ if ((int)fontVariant & (int)FontVariant::SmallCaps) {
+ builder.putString(index++, "small-caps");
+ }
+ if ((int)fontVariant & (int)FontVariant::OldstyleNums) {
+ builder.putString(index++, "oldstyle-nums");
+ }
+ if ((int)fontVariant & (int)FontVariant::LiningNums) {
+ builder.putString(index++, "lining-nums");
+ }
+ if ((int)fontVariant & (int)FontVariant::TabularNums) {
+ builder.putString(index++, "tabular-nums");
+ }
+ if ((int)fontVariant & (int)FontVariant::ProportionalNums) {
+ builder.putString(index++, "proportional-nums");
+ }
+
+ return builder.build();
+}
+
+inline MapBuffer toMapBuffer(const TextAttributes &textAttributes) {
+ auto builder = MapBufferBuilder();
+ if (textAttributes.foregroundColor) {
+ builder.putInt(
+ TA_KEY_FOREGROUND_COLOR, toMapBuffer(textAttributes.foregroundColor));
+ }
+ if (textAttributes.backgroundColor) {
+ builder.putInt(
+ TA_KEY_BACKGROUND_COLOR, toMapBuffer(textAttributes.backgroundColor));
+ }
+ if (!std::isnan(textAttributes.opacity)) {
+ builder.putDouble(TA_KEY_OPACITY, textAttributes.opacity);
+ }
+ if (!textAttributes.fontFamily.empty()) {
+ builder.putString(TA_KEY_FONT_FAMILY, textAttributes.fontFamily);
+ }
+ if (!std::isnan(textAttributes.fontSize)) {
+ builder.putDouble(TA_KEY_FONT_SIZE, textAttributes.fontSize);
+ }
+ if (!std::isnan(textAttributes.fontSizeMultiplier)) {
+ builder.putDouble(
+ TA_KEY_FONT_SIZE_MULTIPLIER, textAttributes.fontSizeMultiplier);
+ }
+ if (textAttributes.fontWeight.has_value()) {
+ builder.putString(TA_KEY_FONT_WEIGHT, toString(*textAttributes.fontWeight));
+ }
+ if (textAttributes.fontStyle.has_value()) {
+ builder.putString(TA_KEY_FONT_STYLE, toString(*textAttributes.fontStyle));
+ }
+ if (textAttributes.fontVariant.has_value()) {
+ auto fontVariantMap = toMapBuffer(*textAttributes.fontVariant);
+ builder.putMapBuffer(TA_KEY_FONT_VARIANT, fontVariantMap);
+ }
+ if (textAttributes.allowFontScaling.has_value()) {
+ builder.putBool(
+ TA_KEY_ALLOW_FONT_SCALING, *textAttributes.allowFontScaling);
+ }
+ if (!std::isnan(textAttributes.letterSpacing)) {
+ builder.putDouble(TA_KEY_LETTER_SPACING, textAttributes.letterSpacing);
+ }
+ if (!std::isnan(textAttributes.lineHeight)) {
+ builder.putDouble(TA_KEY_LINE_HEIGHT, textAttributes.lineHeight);
+ }
+ if (textAttributes.alignment.has_value()) {
+ builder.putString(TA_KEY_ALIGNMENT, toString(*textAttributes.alignment));
+ }
+ if (textAttributes.baseWritingDirection.has_value()) {
+ builder.putString(
+ TA_KEY_BEST_WRITING_DIRECTION,
+ toString(*textAttributes.baseWritingDirection));
+ }
+ // Decoration
+ if (textAttributes.textDecorationColor) {
+ builder.putInt(
+ TA_KEY_TEXT_DECORATION_COLOR,
+ toMapBuffer(textAttributes.textDecorationColor));
+ }
+ if (textAttributes.textDecorationLineType.has_value()) {
+ builder.putString(
+ TA_KEY_TEXT_DECORATION_LINE,
+ toString(*textAttributes.textDecorationLineType));
+ }
+ if (textAttributes.textDecorationLineStyle.has_value()) {
+ builder.putString(
+ TA_KEY_TEXT_DECORATION_LINE_STYLE,
+ toString(*textAttributes.textDecorationLineStyle));
+ }
+ if (textAttributes.textDecorationLinePattern.has_value()) {
+ builder.putString(
+ TA_KEY_TEXT_DECORATION_LINE_PATTERN,
+ toString(*textAttributes.textDecorationLinePattern));
+ }
+ // Shadow
+ if (!std::isnan(textAttributes.textShadowRadius)) {
+ builder.putDouble(
+ TA_KEY_TEXT_SHADOW_RAIDUS, textAttributes.textShadowRadius);
+ }
+ if (textAttributes.textShadowColor) {
+ builder.putInt(
+ TA_KEY_TEXT_SHADOW_COLOR, toMapBuffer(textAttributes.textShadowColor));
+ }
+ // Special
+ if (textAttributes.isHighlighted.has_value()) {
+ builder.putBool(TA_KEY_IS_HIGHLIGHTED, *textAttributes.isHighlighted);
+ }
+ if (textAttributes.layoutDirection.has_value()) {
+ builder.putString(
+ TA_KEY_LAYOUT_DIRECTION, toString(*textAttributes.layoutDirection));
+ }
+ if (textAttributes.accessibilityRole.has_value()) {
+ builder.putString(
+ TA_KEY_ACCESSIBILITY_ROLE, toString(*textAttributes.accessibilityRole));
+ }
+ return builder.build();
+}
+
+inline MapBuffer toMapBuffer(const AttributedString &attributedString) {
+ auto fragmentsBuilder = MapBufferBuilder();
+
+ int index = 0;
+ for (auto fragment : attributedString.getFragments()) {
+ auto dynamicFragmentBuilder = MapBufferBuilder();
+ dynamicFragmentBuilder.putString(FR_KEY_STRING, fragment.string);
+ if (fragment.parentShadowView.componentHandle) {
+ dynamicFragmentBuilder.putInt(
+ FR_KEY_REACT_TAG, fragment.parentShadowView.tag);
+ }
+ if (fragment.isAttachment()) {
+ dynamicFragmentBuilder.putBool(FR_KEY_IS_ATTACHMENT, true);
+ dynamicFragmentBuilder.putDouble(
+ FR_KEY_WIDTH,
+ fragment.parentShadowView.layoutMetrics.frame.size.width);
+ dynamicFragmentBuilder.putDouble(
+ FR_KEY_HEIGHT,
+ fragment.parentShadowView.layoutMetrics.frame.size.height);
+ }
+ auto textAttributesMap = toMapBuffer(fragment.textAttributes);
+ dynamicFragmentBuilder.putMapBuffer(
+ FR_KEY_TEXT_ATTRIBUTES, textAttributesMap);
+ auto dynamicFragmentMap = dynamicFragmentBuilder.build();
+ fragmentsBuilder.putMapBuffer(index++, dynamicFragmentMap);
+ }
+
+ auto builder = MapBufferBuilder();
+ builder.putInt(
+ AS_KEY_HASH,
+ std::hash{}(attributedString));
+ builder.putString(AS_KEY_STRING, attributedString.getString());
+ auto fragmentsMap = fragmentsBuilder.build();
+ builder.putMapBuffer(AS_KEY_FRAGMENTS, fragmentsMap);
+ return builder.build();
+}
+
#endif
} // namespace react
diff --git a/ReactCommon/react/renderer/components/image/Android.mk b/ReactCommon/react/renderer/components/image/Android.mk
index eeaa35d4e74ae6..7fa492f3438b8d 100644
--- a/ReactCommon/react/renderer/components/image/Android.mk
+++ b/ReactCommon/react/renderer/components/image/Android.mk
@@ -21,7 +21,7 @@ LOCAL_CFLAGS += -fexceptions -frtti -std=c++14 -Wall
LOCAL_STATIC_LIBRARIES :=
-LOCAL_SHARED_LIBRARIES := libyoga glog libfolly_json libglog_init libreact_render_core libreact_render_debug libreact_render_graphics librrc_view libreact_render_imagemanager libreact_debug
+LOCAL_SHARED_LIBRARIES := libyoga glog libfolly_json libglog_init libreact_render_core libreact_render_debug libreact_render_graphics librrc_view libreact_render_imagemanager libreact_debug libreact_render_mapbuffer
include $(BUILD_SHARED_LIBRARY)
@@ -35,3 +35,4 @@ $(call import-module,react/renderer/imagemanager)
$(call import-module,react/renderer/components/view)
$(call import-module,yogajni)
$(call import-module,react/debug)
+$(call import-module,react/renderer/mapbuffer)
diff --git a/ReactCommon/react/renderer/components/image/BUCK b/ReactCommon/react/renderer/components/image/BUCK
index 512859a6e39aaa..00017fddf53a86 100644
--- a/ReactCommon/react/renderer/components/image/BUCK
+++ b/ReactCommon/react/renderer/components/image/BUCK
@@ -35,6 +35,9 @@ rn_xplat_cxx_library(
"-std=c++14",
"-Wall",
],
+ fbandroid_deps = [
+ react_native_xplat_target("react/renderer/mapbuffer:mapbuffer"),
+ ],
fbobjc_compiler_flags = APPLE_COMPILER_FLAGS,
fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(),
labels = ["supermodule:xplat/default/public.react_native.infra"],
diff --git a/ReactCommon/react/renderer/components/image/ImageState.h b/ReactCommon/react/renderer/components/image/ImageState.h
index 8dfa5d6f902173..68cdcb681f4fd8 100644
--- a/ReactCommon/react/renderer/components/image/ImageState.h
+++ b/ReactCommon/react/renderer/components/image/ImageState.h
@@ -7,10 +7,14 @@
#pragma once
-#include
#include
#include
+#ifdef ANDROID
+#include
+#include
+#endif
+
namespace facebook {
namespace react {
@@ -50,6 +54,10 @@ class ImageState final {
folly::dynamic getDynamic() const {
return {};
};
+
+ MapBuffer getMapBuffer() const {
+ return MapBufferBuilder::EMPTY();
+ };
#endif
private:
diff --git a/ReactCommon/react/renderer/components/modal/Android.mk b/ReactCommon/react/renderer/components/modal/Android.mk
index 8594c9d8208ca0..ea2dddf664a6cc 100644
--- a/ReactCommon/react/renderer/components/modal/Android.mk
+++ b/ReactCommon/react/renderer/components/modal/Android.mk
@@ -21,7 +21,7 @@ LOCAL_CFLAGS += -fexceptions -frtti -std=c++14 -Wall
LOCAL_STATIC_LIBRARIES :=
-LOCAL_SHARED_LIBRARIES := libyoga glog libfolly_json libglog_init libreact_render_core libreact_render_debug libreact_render_graphics librrc_image libreact_render_uimanager libreact_render_imagemanager librrc_view libreact_render_componentregistry libreact_codegen_rncore
+LOCAL_SHARED_LIBRARIES := libyoga glog libfolly_json libglog_init libreact_render_core libreact_render_debug libreact_render_graphics librrc_image libreact_render_uimanager libreact_render_imagemanager librrc_view libreact_render_componentregistry libreact_codegen_rncore libreact_render_mapbuffer
include $(BUILD_SHARED_LIBRARY)
@@ -37,3 +37,4 @@ $(call import-module,react/renderer/uimanager)
$(call import-module,react/renderer/components/image)
$(call import-module,react/renderer/components/view)
$(call import-module,yogajni)
+$(call import-module,react/renderer/mapbuffer)
diff --git a/ReactCommon/react/renderer/components/modal/BUCK b/ReactCommon/react/renderer/components/modal/BUCK
index 3eb48ca4cb6cdd..8a1dbba208353d 100644
--- a/ReactCommon/react/renderer/components/modal/BUCK
+++ b/ReactCommon/react/renderer/components/modal/BUCK
@@ -37,6 +37,9 @@ rn_xplat_cxx_library(
"-std=c++14",
"-Wall",
],
+ fbandroid_deps = [
+ react_native_xplat_target("react/renderer/mapbuffer:mapbuffer"),
+ ],
fbandroid_exported_headers = subdir_glob(
[
("", "*.h"),
diff --git a/ReactCommon/react/renderer/components/modal/ModalHostViewState.h b/ReactCommon/react/renderer/components/modal/ModalHostViewState.h
index 92ab4c5afb7e40..b0921864ba7791 100644
--- a/ReactCommon/react/renderer/components/modal/ModalHostViewState.h
+++ b/ReactCommon/react/renderer/components/modal/ModalHostViewState.h
@@ -13,6 +13,8 @@
#ifdef ANDROID
#include
+#include
+#include
#endif
namespace facebook {
@@ -41,6 +43,10 @@ class ModalHostViewState final {
#ifdef ANDROID
folly::dynamic getDynamic() const;
+ MapBuffer getMapBuffer() const {
+ return MapBufferBuilder::EMPTY();
+ };
+
#endif
#pragma mark - Getters
diff --git a/ReactCommon/react/renderer/components/scrollview/Android.mk b/ReactCommon/react/renderer/components/scrollview/Android.mk
index 8726fbea871b8c..5e0748c0e4917f 100644
--- a/ReactCommon/react/renderer/components/scrollview/Android.mk
+++ b/ReactCommon/react/renderer/components/scrollview/Android.mk
@@ -21,7 +21,7 @@ LOCAL_CFLAGS += -fexceptions -frtti -std=c++14 -Wall
LOCAL_STATIC_LIBRARIES :=
-LOCAL_SHARED_LIBRARIES := libyoga libfolly_futures glog libfolly_json libglog_init libreact_render_core libreact_render_debug libreact_render_graphics librrc_view libreact_debug
+LOCAL_SHARED_LIBRARIES := libyoga libfolly_futures glog libfolly_json libglog_init libreact_render_core libreact_render_debug libreact_render_graphics librrc_view libreact_debug libreact_render_mapbuffer
include $(BUILD_SHARED_LIBRARY)
@@ -34,3 +34,4 @@ $(call import-module,react/renderer/debug)
$(call import-module,react/renderer/graphics)
$(call import-module,react/debug)
$(call import-module,yogajni)
+$(call import-module,react/renderer/mapbuffer)
diff --git a/ReactCommon/react/renderer/components/scrollview/BUCK b/ReactCommon/react/renderer/components/scrollview/BUCK
index ab356b049788cc..f9fb72f3594146 100644
--- a/ReactCommon/react/renderer/components/scrollview/BUCK
+++ b/ReactCommon/react/renderer/components/scrollview/BUCK
@@ -38,6 +38,9 @@ rn_xplat_cxx_library(
"-std=c++14",
"-Wall",
],
+ fbandroid_deps = [
+ react_native_xplat_target("react/renderer/mapbuffer:mapbuffer"),
+ ],
fbobjc_compiler_flags = APPLE_COMPILER_FLAGS,
fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(),
force_static = True,
diff --git a/ReactCommon/react/renderer/components/scrollview/ScrollViewState.h b/ReactCommon/react/renderer/components/scrollview/ScrollViewState.h
index a2ea7b945f81c0..3f979ab3f597b9 100644
--- a/ReactCommon/react/renderer/components/scrollview/ScrollViewState.h
+++ b/ReactCommon/react/renderer/components/scrollview/ScrollViewState.h
@@ -9,7 +9,11 @@
#include
+#ifdef ANDROID
#include
+#include
+#include
+#endif
namespace facebook {
namespace react {
@@ -39,6 +43,9 @@ class ScrollViewState final {
return folly::dynamic::object("contentOffsetLeft", contentOffset.x)(
"contentOffsetTop", contentOffset.y);
};
+ MapBuffer getMapBuffer() const {
+ return MapBufferBuilder::EMPTY();
+ };
#endif
};
diff --git a/ReactCommon/react/renderer/components/slider/Android.mk b/ReactCommon/react/renderer/components/slider/Android.mk
index d64d8a9cb6dab4..34c4368b56f27e 100644
--- a/ReactCommon/react/renderer/components/slider/Android.mk
+++ b/ReactCommon/react/renderer/components/slider/Android.mk
@@ -21,7 +21,7 @@ LOCAL_CFLAGS += -fexceptions -frtti -std=c++14 -Wall
LOCAL_STATIC_LIBRARIES :=
-LOCAL_SHARED_LIBRARIES := libfbjni libreact_codegen_rncore libreact_render_imagemanager libreactnativeutilsjni libreact_render_componentregistry libreact_render_uimanager librrc_image libyoga libfolly_futures glog libfolly_json libglog_init libreact_render_core libreact_render_debug libreact_render_graphics librrc_view libreact_debug
+LOCAL_SHARED_LIBRARIES := libfbjni libreact_codegen_rncore libreact_render_imagemanager libreactnativeutilsjni libreact_render_componentregistry libreact_render_uimanager librrc_image libyoga libfolly_futures glog libfolly_json libglog_init libreact_render_core libreact_render_debug libreact_render_graphics librrc_view libreact_debug libreact_render_mapbuffer
include $(BUILD_SHARED_LIBRARY)
@@ -38,3 +38,4 @@ $(call import-module,react/renderer/components/image)
$(call import-module,react/renderer/components/view)
$(call import-module,react/renderer/uimanager)
$(call import-module,yogajni)
+$(call import-module,react/renderer/mapbuffer)
diff --git a/ReactCommon/react/renderer/components/slider/BUCK b/ReactCommon/react/renderer/components/slider/BUCK
index 412f0443b3e7bc..59f5a5cc27d695 100644
--- a/ReactCommon/react/renderer/components/slider/BUCK
+++ b/ReactCommon/react/renderer/components/slider/BUCK
@@ -42,6 +42,7 @@ rn_xplat_cxx_library(
cxx_tests = [":tests"],
fbandroid_deps = [
react_native_target("jni/react/jni:jni"),
+ react_native_xplat_target("react/renderer/mapbuffer:mapbuffer"),
],
fbandroid_exported_headers = subdir_glob(
[
diff --git a/ReactCommon/react/renderer/components/slider/SliderState.h b/ReactCommon/react/renderer/components/slider/SliderState.h
index 4aa4927faf64c1..82a338c08f24ee 100644
--- a/ReactCommon/react/renderer/components/slider/SliderState.h
+++ b/ReactCommon/react/renderer/components/slider/SliderState.h
@@ -7,7 +7,12 @@
#pragma once
+#ifdef ANDROID
#include
+#include
+#include
+#endif
+
#include
#include
@@ -64,6 +69,9 @@ class SliderState final {
folly::dynamic getDynamic() const {
return {};
};
+ MapBuffer getMapBuffer() const {
+ return MapBufferBuilder::EMPTY();
+ };
#endif
private:
diff --git a/ReactCommon/react/renderer/components/text/Android.mk b/ReactCommon/react/renderer/components/text/Android.mk
index 160867b6ac9067..7acbcced7ae74b 100644
--- a/ReactCommon/react/renderer/components/text/Android.mk
+++ b/ReactCommon/react/renderer/components/text/Android.mk
@@ -21,7 +21,7 @@ LOCAL_CFLAGS += -fexceptions -frtti -std=c++14 -Wall
LOCAL_STATIC_LIBRARIES :=
-LOCAL_SHARED_LIBRARIES := libyoga glog libfolly_json libglog_init libreact_render_core libreact_render_debug libreact_render_graphics libreact_render_uimanager libreact_render_textlayoutmanager libreact_render_attributedstring libreact_render_mounting librrc_view libreact_utils libreact_debug
+LOCAL_SHARED_LIBRARIES := libyoga glog libfolly_json libglog_init libreact_render_core libreact_render_debug libreact_render_graphics libreact_render_uimanager libreact_render_textlayoutmanager libreact_render_attributedstring libreact_render_mounting librrc_view libreact_utils libreact_debug libreact_render_mapbuffer
include $(BUILD_SHARED_LIBRARY)
@@ -38,4 +38,5 @@ $(call import-module,react/renderer/uimanager)
$(call import-module,react/renderer/components/view)
$(call import-module,react/utils)
$(call import-module,react/debug)
+$(call import-module,react/renderer/mapbuffer)
$(call import-module,yogajni)
diff --git a/ReactCommon/react/renderer/components/text/BUCK b/ReactCommon/react/renderer/components/text/BUCK
index 1a38a17d874945..9b467be70585be 100644
--- a/ReactCommon/react/renderer/components/text/BUCK
+++ b/ReactCommon/react/renderer/components/text/BUCK
@@ -43,6 +43,9 @@ rn_xplat_cxx_library(
"-Wall",
],
cxx_tests = [":tests"],
+ fbandroid_deps = [
+ react_native_xplat_target("react/renderer/mapbuffer:mapbuffer"),
+ ],
fbobjc_compiler_flags = APPLE_COMPILER_FLAGS,
fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(),
force_static = True,
diff --git a/ReactCommon/react/renderer/components/text/ParagraphState.cpp b/ReactCommon/react/renderer/components/text/ParagraphState.cpp
index c91bb6e480f41e..5c15d2a31e6492 100644
--- a/ReactCommon/react/renderer/components/text/ParagraphState.cpp
+++ b/ReactCommon/react/renderer/components/text/ParagraphState.cpp
@@ -17,6 +17,10 @@ namespace react {
folly::dynamic ParagraphState::getDynamic() const {
return toDynamic(*this);
}
+
+MapBuffer ParagraphState::getMapBuffer() const {
+ return toMapBuffer(*this);
+}
#endif
} // namespace react
diff --git a/ReactCommon/react/renderer/components/text/ParagraphState.h b/ReactCommon/react/renderer/components/text/ParagraphState.h
index b31b8798d847ed..d4e80fda9cd7c7 100644
--- a/ReactCommon/react/renderer/components/text/ParagraphState.h
+++ b/ReactCommon/react/renderer/components/text/ParagraphState.h
@@ -14,6 +14,7 @@
#ifdef ANDROID
#include
+#include
#endif
namespace facebook {
@@ -60,6 +61,7 @@ class ParagraphState final {
react_native_assert(false && "Not supported");
};
folly::dynamic getDynamic() const;
+ MapBuffer getMapBuffer() const;
#endif
};
diff --git a/ReactCommon/react/renderer/components/text/conversions.h b/ReactCommon/react/renderer/components/text/conversions.h
index 0d44cd762143c6..8ea5baeddb7978 100644
--- a/ReactCommon/react/renderer/components/text/conversions.h
+++ b/ReactCommon/react/renderer/components/text/conversions.h
@@ -8,6 +8,10 @@
#include
#include
#include
+#ifdef ANDROID
+#include
+#include
+#endif
namespace facebook {
namespace react {
@@ -21,6 +25,24 @@ inline folly::dynamic toDynamic(ParagraphState const ¶graphState) {
newState["hash"] = newState["attributedString"]["hash"];
return newState;
}
+
+// constants for Text State serialization
+constexpr static Key TX_STATE_KEY_ATTRIBUTED_STRING = 0;
+constexpr static Key TX_STATE_KEY_PARAGRAPH_ATTRIBUTES = 1;
+// Used for TextInput
+constexpr static Key TX_STATE_KEY_HASH = 2;
+constexpr static Key TX_STATE_KEY_MOST_RECENT_EVENT_COUNT = 3;
+
+inline MapBuffer toMapBuffer(ParagraphState const ¶graphState) {
+ auto builder = MapBufferBuilder();
+ auto attStringMapBuffer = toMapBuffer(paragraphState.attributedString);
+ builder.putMapBuffer(TX_STATE_KEY_ATTRIBUTED_STRING, attStringMapBuffer);
+ auto paMapBuffer = toMapBuffer(paragraphState.paragraphAttributes);
+ builder.putMapBuffer(TX_STATE_KEY_PARAGRAPH_ATTRIBUTES, paMapBuffer);
+ // TODO: Used for TextInput
+ builder.putInt(TX_STATE_KEY_HASH, 1234);
+ return builder.build();
+}
#endif
} // namespace react
diff --git a/ReactCommon/react/renderer/components/textinput/Android.mk b/ReactCommon/react/renderer/components/textinput/Android.mk
index 729aa7de908756..694e9042ea372b 100644
--- a/ReactCommon/react/renderer/components/textinput/Android.mk
+++ b/ReactCommon/react/renderer/components/textinput/Android.mk
@@ -21,7 +21,7 @@ LOCAL_CFLAGS += -fexceptions -frtti -std=c++14 -Wall
LOCAL_STATIC_LIBRARIES :=
-LOCAL_SHARED_LIBRARIES := libyoga glog libfolly_json libglog_init libreact_render_core libreact_render_mounting libreact_render_componentregistry libreact_render_debug libreact_render_graphics libreact_render_uimanager libreact_render_imagemanager libreact_render_textlayoutmanager libreact_render_attributedstring librrc_text librrc_image librrc_view libreact_utils libreact_debug
+LOCAL_SHARED_LIBRARIES := libyoga glog libfolly_json libglog_init libreact_render_core libreact_render_mounting libreact_render_componentregistry libreact_render_debug libreact_render_graphics libreact_render_uimanager libreact_render_imagemanager libreact_render_textlayoutmanager libreact_render_attributedstring librrc_text librrc_image librrc_view libreact_utils libreact_debug libreact_render_mapbuffer
include $(BUILD_SHARED_LIBRARY)
@@ -43,3 +43,4 @@ $(call import-module,react/renderer/components/text)
$(call import-module,react/utils)
$(call import-module,react/debug)
$(call import-module,yogajni)
+$(call import-module,react/renderer/mapbuffer)
diff --git a/ReactCommon/react/renderer/components/textinput/BUCK b/ReactCommon/react/renderer/components/textinput/BUCK
index 76373d0d8e0c4d..ed0615e0bda0b9 100644
--- a/ReactCommon/react/renderer/components/textinput/BUCK
+++ b/ReactCommon/react/renderer/components/textinput/BUCK
@@ -40,6 +40,9 @@ rn_xplat_cxx_library(
"-Wall",
],
cxx_tests = [":tests"],
+ fbandroid_deps = [
+ react_native_xplat_target("react/renderer/mapbuffer:mapbuffer"),
+ ],
fbobjc_compiler_flags = APPLE_COMPILER_FLAGS,
fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(),
force_static = True,
diff --git a/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputState.h b/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputState.h
index a52188f9ead565..4d3d41ac7a87c6 100644
--- a/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputState.h
+++ b/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputState.h
@@ -13,6 +13,8 @@
#ifdef ANDROID
#include
+#include
+#include
#endif
namespace facebook {
@@ -92,6 +94,9 @@ class AndroidTextInputState final {
AndroidTextInputState const &previousState,
folly::dynamic const &data);
folly::dynamic getDynamic() const;
+ MapBuffer getMapBuffer() const {
+ return MapBufferBuilder::EMPTY();
+ };
};
} // namespace react
diff --git a/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputState.h b/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputState.h
index 31ea9c77a2aa03..fb249f4a840ab3 100644
--- a/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputState.h
+++ b/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputState.h
@@ -13,6 +13,8 @@
#ifdef ANDROID
#include
+#include
+#include
#endif
namespace facebook {
diff --git a/ReactCommon/react/renderer/core/Android.mk b/ReactCommon/react/renderer/core/Android.mk
index b6787cd9cd8d10..2ffdde0f44d50f 100644
--- a/ReactCommon/react/renderer/core/Android.mk
+++ b/ReactCommon/react/renderer/core/Android.mk
@@ -30,3 +30,4 @@ $(call import-module,react/utils)
$(call import-module,react/debug)
$(call import-module,react/renderer/debug)
$(call import-module,react/renderer/graphics)
+$(call import-module,react/renderer/mapbuffer)
diff --git a/ReactCommon/react/renderer/core/ConcreteState.h b/ReactCommon/react/renderer/core/ConcreteState.h
index f4ebd03b35ee17..c4f8bef37e1d2b 100644
--- a/ReactCommon/react/renderer/core/ConcreteState.h
+++ b/ReactCommon/react/renderer/core/ConcreteState.h
@@ -105,6 +105,9 @@ class ConcreteState : public State {
void updateState(folly::dynamic data) const override {
updateState(std::move(Data(getData(), data)));
}
+ MapBuffer getMapBuffer() const override {
+ return getData().getMapBuffer();
+ }
#endif
};
diff --git a/ReactCommon/react/renderer/core/State.h b/ReactCommon/react/renderer/core/State.h
index c0472fec057bf5..ce5b51fe065e03 100644
--- a/ReactCommon/react/renderer/core/State.h
+++ b/ReactCommon/react/renderer/core/State.h
@@ -9,6 +9,8 @@
#ifdef ANDROID
#include
+#include
+#include
#endif
#include
@@ -65,6 +67,7 @@ class State {
#ifdef ANDROID
virtual folly::dynamic getDynamic() const = 0;
+ virtual MapBuffer getMapBuffer() const = 0;
virtual void updateState(folly::dynamic data) const = 0;
#endif
diff --git a/ReactCommon/react/renderer/core/StateData.h b/ReactCommon/react/renderer/core/StateData.h
index b13b5950490b87..36e098d1cd647d 100644
--- a/ReactCommon/react/renderer/core/StateData.h
+++ b/ReactCommon/react/renderer/core/StateData.h
@@ -11,6 +11,8 @@
#ifdef ANDROID
#include
+#include
+#include
#endif
namespace facebook {
@@ -27,6 +29,7 @@ struct StateData final {
StateData() = default;
StateData(StateData const &previousState, folly::dynamic data){};
folly::dynamic getDynamic() const;
+ MapBuffer getMapBuffer() const;
#endif
};
diff --git a/ReactCommon/react/renderer/graphics/conversions.h b/ReactCommon/react/renderer/graphics/conversions.h
index 1f910dd07096ca..f3420295b2ed57 100644
--- a/ReactCommon/react/renderer/graphics/conversions.h
+++ b/ReactCommon/react/renderer/graphics/conversions.h
@@ -58,6 +58,16 @@ inline folly::dynamic toDynamic(const SharedColor &color) {
((int)round(components.blue * ratio) & 0xff));
}
+inline int toMapBuffer(const SharedColor &color) {
+ ColorComponents components = colorComponentsFromColor(color);
+ auto ratio = 255.f;
+ return (
+ ((int)round(components.alpha * ratio) & 0xff) << 24 |
+ ((int)round(components.red * ratio) & 0xff) << 16 |
+ ((int)round(components.green * ratio) & 0xff) << 8 |
+ ((int)round(components.blue * ratio) & 0xff));
+}
+
#endif
inline std::string toString(const SharedColor &value) {
diff --git a/ReactCommon/react/renderer/mapbuffer/Android.mk b/ReactCommon/react/renderer/mapbuffer/Android.mk
index f6c9584ba33510..65bf3e9e2cf08a 100644
--- a/ReactCommon/react/renderer/mapbuffer/Android.mk
+++ b/ReactCommon/react/renderer/mapbuffer/Android.mk
@@ -9,21 +9,21 @@ include $(CLEAR_VARS)
LOCAL_MODULE := react_render_mapbuffer
-LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../../
-
LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp)
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../../
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../../../
-LOCAL_SHARED_LIBRARIES := libreact_utils glog libglog_init
-
LOCAL_CFLAGS := \
-DLOG_TAG=\"Fabric\"
LOCAL_CFLAGS += -fexceptions -frtti -std=c++14 -Wall
+LOCAL_STATIC_LIBRARIES :=
+
+LOCAL_SHARED_LIBRARIES := glog libglog_init
+
include $(BUILD_SHARED_LIBRARY)
-$(call import-module,react/utils)
$(call import-module,glog)
$(call import-module,fbgloginit)
diff --git a/ReactCommon/react/renderer/mapbuffer/BUCK b/ReactCommon/react/renderer/mapbuffer/BUCK
index dc246215845732..39bdca768f8a7f 100644
--- a/ReactCommon/react/renderer/mapbuffer/BUCK
+++ b/ReactCommon/react/renderer/mapbuffer/BUCK
@@ -44,7 +44,6 @@ rn_xplat_cxx_library(
"//xplat/fbsystrace:fbsystrace",
"//xplat/folly:headers_only",
"//xplat/folly:memory",
- react_native_xplat_target("react/utils:utils"),
],
)
diff --git a/ReactCommon/react/renderer/mapbuffer/MapBuffer.cpp b/ReactCommon/react/renderer/mapbuffer/MapBuffer.cpp
index 1de973515531f3..dfed3a200a60af 100644
--- a/ReactCommon/react/renderer/mapbuffer/MapBuffer.cpp
+++ b/ReactCommon/react/renderer/mapbuffer/MapBuffer.cpp
@@ -12,78 +12,31 @@ using namespace facebook::react;
namespace facebook {
namespace react {
-MapBuffer::MapBuffer(uint16_t initialSize) {
- _dataSize = initialSize;
- _data = new Byte[_dataSize];
- // TODO: Should we clean up memory here?
-}
+MapBuffer::MapBuffer(uint8_t *const data, uint16_t dataSize) {
+ // Should we move the memory here or document it?
+ _data = data;
-void MapBuffer::makeSpace() {
- int oldDataSize = _dataSize;
- if (_dataSize >= std::numeric_limits::max() / 2) {
- LOG(ERROR)
- << "Error: trying to assign a value beyond the capacity of uint16_t"
- << static_cast(_dataSize) * 2;
- throw "Error: trying to assign a value beyond the capacity of uint16_t" +
- std::to_string(static_cast(_dataSize) * 2);
- }
- _dataSize *= 2;
- uint8_t *_newdata = new Byte[_dataSize];
- uint8_t *_oldData = _data;
- memcpy(_newdata, _data, oldDataSize);
- _data = _newdata;
- delete[] _oldData;
-}
+ _count = 0;
+ memcpy(
+ reinterpret_cast(&_count),
+ reinterpret_cast(_data + HEADER_COUNT_OFFSET),
+ UINT16_SIZE);
-void MapBuffer::putBytes(Key key, uint8_t *value, int valueSize) {
- if (key != _header.count) {
- LOG(ERROR)
- << "Error: key out of order (for now keys should we stored contiguous) "
- << key;
- throw "Error: key out of order (for now keys should we stored contiguous) - key: " +
- std::to_string(key);
- }
+ // TODO: extract memcpy calls into an inline function to simplify the code
+ _dataSize = 0;
+ memcpy(
+ reinterpret_cast(&_dataSize),
+ reinterpret_cast(_data + HEADER_BUFFER_SIZE_OFFSET),
+ UINT16_SIZE);
- int valueOffset = getValueOffset(key);
- if (valueOffset + valueSize > _dataSize) {
- makeSpace();
+ if (dataSize != _dataSize) {
+ LOG(ERROR) << "Error: Data size does not match, expected " << dataSize
+ << " found: " << _dataSize;
+ throw "Error: Data size does not match";
}
-
- memcpy(_data + getKeyOffset(key), &key, KEY_SIZE);
- memcpy(_data + valueOffset, value, valueSize);
- _header.count++;
}
-void MapBuffer::putBool(Key key, bool value) {
- putInt(key, (int)value);
-}
-
-void MapBuffer::putDouble(Key key, double value) {
- uint8_t *bytePointer = reinterpret_cast(&value);
- putBytes(key, bytePointer, DOUBLE_SIZE);
-}
-
-void MapBuffer::putNull(Key key) {
- putInt(key, NULL_VALUE);
-}
-
-void MapBuffer::putInt(Key key, int value) {
- uint8_t *bytePointer = reinterpret_cast(&(value));
- putBytes(key, bytePointer, INT_SIZE);
-}
-
-void MapBuffer::finish() {
- // Copy header at the beginning of "_data"
- memcpy(_data, &_header, HEADER_SIZE);
- // TODO: create a MapBufferBuilder instead of calling the finish method.
-}
-
-// TODO: All the "getXXX" methods are currently operating on a "finished" map.
-// Next step: create a MapBufferBuilder, move "putXXX" methods into the
-// MapBufferBuilder, make MapBuffer class immutable.
-int MapBuffer::getInt(Key key) {
- checkKeyConsistency(_header, _data, key);
-
+int MapBuffer::getInt(Key key) const {
int value = 0;
memcpy(
reinterpret_cast(&value),
@@ -92,13 +45,11 @@ int MapBuffer::getInt(Key key) {
return value;
}
-bool MapBuffer::getBool(Key key) {
+bool MapBuffer::getBool(Key key) const {
return getInt(key) != 0;
}
-double MapBuffer::getDouble(Key key) {
- checkKeyConsistency(_header, _data, key);
-
+double MapBuffer::getDouble(Key key) const {
// TODO: extract this code into a "template method" and reuse it for other
// types
double value = 0;
@@ -109,26 +60,81 @@ double MapBuffer::getDouble(Key key) {
return value;
}
-bool MapBuffer::isNull(Key key) {
+int MapBuffer::getDynamicDataOffset() const {
+ // The begininig of dynamic data can be calculated as the offset of the next
+ // key in the map
+ return getKeyOffset(_count);
+}
+
+std::string MapBuffer::getString(Key key) const {
+ // TODO Add checks to verify that offsets are under the boundaries of the map
+ // buffer
+ int dynamicDataOffset = getDynamicDataOffset();
+ int stringLength = 0;
+ memcpy(
+ reinterpret_cast(&stringLength),
+ reinterpret_cast(_data + dynamicDataOffset),
+ INT_SIZE);
+
+ int valueOffset = getInt(key) + sizeof(stringLength);
+
+ char *value = new char[stringLength];
+
+ memcpy(
+ reinterpret_cast(value),
+ reinterpret_cast(_data + dynamicDataOffset + valueOffset),
+ stringLength);
+
+ return std::string(value);
+}
+
+MapBuffer MapBuffer::getMapBuffer(Key key) const {
+ // TODO Add checks to verify that offsets are under the boundaries of the map
+ // buffer
+ int dynamicDataOffset = getDynamicDataOffset();
+
+ uint16_t mapBufferLength = 0;
+
+ memcpy(
+ reinterpret_cast(&mapBufferLength),
+ reinterpret_cast(_data + dynamicDataOffset),
+ UINT16_SIZE);
+
+ int valueOffset = getInt(key) + UINT16_SIZE;
+
+ uint8_t *value = new Byte[mapBufferLength];
+
+ memcpy(
+ reinterpret_cast(value),
+ reinterpret_cast(
+ _data + dynamicDataOffset + valueOffset),
+ mapBufferLength);
+
+ return MapBuffer(value, mapBufferLength);
+}
+
+bool MapBuffer::isNull(Key key) const {
return getInt(key) == NULL_VALUE;
}
-uint16_t MapBuffer::getBufferSize() {
+uint16_t MapBuffer::getBufferSize() const {
return _dataSize;
}
-void MapBuffer::copy(uint8_t *output) {
+void MapBuffer::copy(uint8_t *output) const {
memcpy(output, _data, _dataSize);
}
-uint16_t MapBuffer::getSize() {
+uint16_t MapBuffer::getCount() const {
uint16_t size = 0;
+
memcpy(
reinterpret_cast(&size),
reinterpret_cast(
_data + UINT16_SIZE), // TODO refactor this: + UINT16_SIZE describes
// the position in the header
UINT16_SIZE);
+
return size;
}
diff --git a/ReactCommon/react/renderer/mapbuffer/MapBuffer.h b/ReactCommon/react/renderer/mapbuffer/MapBuffer.h
index 0a775e4c5b25b2..6b79db2943c736 100644
--- a/ReactCommon/react/renderer/mapbuffer/MapBuffer.h
+++ b/ReactCommon/react/renderer/mapbuffer/MapBuffer.h
@@ -9,12 +9,11 @@
#include
+#include
+
namespace facebook {
namespace react {
-// 506 = 5 entries = 50*10 + 6 sizeof(header)
-constexpr uint16_t INITIAL_SIZE = 506;
-
/**
* MapBuffer is an optimized map format for transferring data like props between
* C++ and other platforms The implementation of this map is optimized to:
@@ -32,50 +31,42 @@ constexpr uint16_t INITIAL_SIZE = 506;
*/
class MapBuffer {
private:
- Header _header = {ALIGNMENT, 0, 0};
-
- void makeSpace();
-
- void putBytes(Key key, uint8_t *value, int valueSize);
-
// Buffer and its size
- uint8_t *_data;
+ const uint8_t *_data;
+ // amount of bytes in the MapBuffer
uint16_t _dataSize;
- public:
- MapBuffer() : MapBuffer(INITIAL_SIZE) {}
+ // amount of items in the MapBuffer
+ uint16_t _count;
- MapBuffer(uint16_t initialSize);
+ // returns the relative offset of the first byte of dynamic data
+ int getDynamicDataOffset() const;
- ~MapBuffer();
-
- void putInt(Key key, int value);
-
- void putBool(Key key, bool value);
+ public:
+ MapBuffer(uint8_t *const data, uint16_t dataSize);
- void putDouble(Key key, double value);
+ ~MapBuffer();
- void putNull(Key key);
+ int getInt(Key key) const;
- // TODO: create a MapBufferBuilder instead or add checks to verify
- // if it's ok to read and write the Map
- void finish();
+ bool getBool(Key key) const;
- int getInt(Key key);
+ double getDouble(Key key) const;
- bool getBool(Key key);
+ std::string getString(Key key) const;
- double getDouble(Key key);
+ // TODO: review this declaration
+ MapBuffer getMapBuffer(Key key) const;
- uint16_t getBufferSize();
+ uint16_t getBufferSize() const;
// TODO: review parameters of copy method
- void copy(uint8_t *output);
+ void copy(uint8_t *output) const;
- bool isNull(Key key);
+ bool isNull(Key key) const;
- uint16_t getSize();
+ uint16_t getCount() const;
};
} // namespace react
diff --git a/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp b/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp
new file mode 100644
index 00000000000000..a4d272cccab63c
--- /dev/null
+++ b/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+#include "MapBufferBuilder.h"
+
+using namespace facebook::react;
+
+namespace facebook {
+namespace react {
+
+MapBufferBuilder::MapBufferBuilder()
+ : MapBufferBuilder::MapBufferBuilder(INITIAL_KEY_VALUE_SIZE) {}
+
+MapBuffer MapBufferBuilder::EMPTY() {
+ static auto emptyMap = MapBufferBuilder().build();
+ return emptyMap;
+}
+
+MapBufferBuilder::MapBufferBuilder(uint16_t initialSize) {
+ _keyValuesSize = initialSize;
+ _keyValues = new Byte[_keyValuesSize];
+ // First Key should be written right after the header.
+ _keyValuesOffset = HEADER_SIZE;
+
+ _dynamicDataSize = 0;
+ _dynamicDataValues = nullptr;
+ _dynamicDataOffset = 0;
+}
+
+void MapBufferBuilder::ensureKeyValueSpace() {
+ int oldKeyValuesSize = _keyValuesSize;
+ if (_keyValuesSize >= std::numeric_limits::max() / 2) {
+ LOG(ERROR)
+ << "Error: trying to assign a value beyond the capacity of uint16_t"
+ << static_cast(_keyValuesSize) * 2;
+ throw "Error: trying to assign a value beyond the capacity of uint16_t";
+ }
+ _keyValuesSize *= 2;
+ uint8_t *newKeyValues = new Byte[_keyValuesSize];
+ uint8_t *oldKeyValues = _keyValues;
+ memcpy(newKeyValues, _keyValues, oldKeyValuesSize);
+ _keyValues = newKeyValues;
+ delete[] oldKeyValues;
+}
+
+void MapBufferBuilder::storeKeyValue(Key key, uint8_t *value, int valueSize) {
+ if (key < _minKeyToStore) {
+ LOG(ERROR) << "Error: key out of order - key: " << key;
+ throw "Error: key out of order";
+ }
+ if (valueSize > MAX_VALUE_SIZE) {
+ throw "Error: size of value must be <= MAX_VALUE_SIZE";
+ }
+ // TODO: header.count points to the next index
+ // TODO: add test to verify storage of sparse keys
+ int keyOffset = getKeyOffset(_header.count);
+ int valueOffset = keyOffset + KEY_SIZE;
+
+ int nextKeyValueOffset = keyOffset + BUCKET_SIZE;
+ if (nextKeyValueOffset >= _keyValuesSize) {
+ ensureKeyValueSpace();
+ }
+
+ memcpy(_keyValues + keyOffset, &key, KEY_SIZE);
+ memcpy(_keyValues + valueOffset, value, valueSize);
+
+ _header.count++;
+
+ _minKeyToStore = key + 1;
+ // Move _keyValuesOffset to the next available [key, value] position
+ _keyValuesOffset = std::max(nextKeyValueOffset, _keyValuesOffset);
+}
+
+void MapBufferBuilder::putBool(Key key, bool value) {
+ putInt(key, (int)value);
+}
+
+void MapBufferBuilder::putDouble(Key key, double value) {
+ uint8_t *bytePointer = reinterpret_cast(&value);
+ storeKeyValue(key, bytePointer, DOUBLE_SIZE);
+}
+
+void MapBufferBuilder::putNull(Key key) {
+ putInt(key, NULL_VALUE);
+}
+
+void MapBufferBuilder::putInt(Key key, int value) {
+ uint8_t *bytePointer = reinterpret_cast(&(value));
+ storeKeyValue(key, bytePointer, INT_SIZE);
+}
+
+void MapBufferBuilder::ensureDynamicDataSpace(int size) {
+ if (_dynamicDataValues == nullptr) {
+ _dynamicDataSize = std::max(INITIAL_DYNAMIC_DATA_SIZE, size);
+ _dynamicDataValues = new Byte[_dynamicDataSize];
+ _dynamicDataOffset = 0;
+ return;
+ }
+
+ if (_dynamicDataOffset + size >= _dynamicDataSize) {
+ int oldDynamicDataSize = _dynamicDataSize;
+ if (_dynamicDataSize >= std::numeric_limits::max() / 2) {
+ LOG(ERROR)
+ << "Error: trying to assign a value beyond the capacity of uint16_t"
+ << static_cast(_dynamicDataSize) * 2;
+ throw "Error: trying to assign a value beyond the capacity of uint16_t";
+ }
+ _dynamicDataSize *= 2;
+ uint8_t *newDynamicDataValues = new Byte[_dynamicDataSize];
+ uint8_t *oldDynamicDataValues = _dynamicDataValues;
+ memcpy(newDynamicDataValues, _dynamicDataValues, oldDynamicDataSize);
+ _dynamicDataValues = newDynamicDataValues;
+ delete[] oldDynamicDataValues;
+ }
+}
+
+void MapBufferBuilder::putString(Key key, std::string value) {
+ int strLength = value.length();
+ const char *cstring = getCstring(&value);
+
+ // format [lenght of string (int)] + [Array of Characters in the string]
+ int sizeOfLength = INT_SIZE;
+ // TODO : review if map.getBufferSize() should be an int or long instead of an
+ // int16 (because strings can be longer than int16);
+
+ int sizeOfDynamicData = sizeOfLength + strLength;
+ ensureDynamicDataSpace(sizeOfDynamicData);
+ memcpy(_dynamicDataValues + _dynamicDataOffset, &strLength, sizeOfLength);
+ memcpy(
+ _dynamicDataValues + _dynamicDataOffset + sizeOfLength,
+ cstring,
+ strLength);
+
+ // Store Key and pointer to the string
+ putInt(key, _dynamicDataOffset);
+
+ _dynamicDataOffset += sizeOfDynamicData;
+}
+
+void MapBufferBuilder::putMapBuffer(Key key, MapBuffer &map) {
+ uint16_t mapBufferSize = map.getBufferSize();
+
+ // format [lenght of buffer (short)] + [Array of Characters in the string]
+ int sizeOfDynamicData = mapBufferSize + UINT16_SIZE;
+
+ // format [Array of bytes of the mapBuffer]
+ ensureDynamicDataSpace(sizeOfDynamicData);
+
+ memcpy(_dynamicDataValues + _dynamicDataOffset, &mapBufferSize, UINT16_SIZE);
+ // Copy the content of the map into _dynamicDataValues
+ map.copy(_dynamicDataValues + _dynamicDataOffset + UINT16_SIZE);
+
+ // Store Key and pointer to the string
+ putInt(key, _dynamicDataOffset);
+
+ _dynamicDataOffset += sizeOfDynamicData;
+}
+
+MapBuffer MapBufferBuilder::build() {
+ // Create buffer: [header] + [key, values] + [dynamic data]
+ int bufferSize = _keyValuesOffset + _dynamicDataOffset;
+
+ _header.bufferSize = bufferSize;
+
+ // Copy header at the beginning of "_keyValues"
+ memcpy(_keyValues, &_header, HEADER_SIZE);
+
+ uint8_t *buffer = new Byte[bufferSize];
+
+ memcpy(buffer, _keyValues, _keyValuesOffset);
+
+ if (_dynamicDataValues != nullptr) {
+ memcpy(buffer + _keyValuesOffset, _dynamicDataValues, _dynamicDataOffset);
+ }
+
+ // TODO: should we use std::move here?
+ auto map = MapBuffer(buffer, bufferSize);
+
+ // TODO: we should invalidate the class once the build() method is
+ // called.
+
+ // Reset internal data
+ delete[] _keyValues;
+ _keyValues = nullptr;
+ _keyValuesSize = 0;
+ _keyValuesOffset = 0;
+
+ if (_dynamicDataValues != nullptr) {
+ delete[] _dynamicDataValues;
+ _dynamicDataValues = nullptr;
+ }
+ _dynamicDataSize = 0;
+ _dynamicDataOffset = 0;
+
+ return map;
+}
+
+MapBufferBuilder::~MapBufferBuilder() {
+ if (_keyValues != nullptr) {
+ delete[] _keyValues;
+ }
+ if (_dynamicDataValues != nullptr) {
+ delete[] _dynamicDataValues;
+ }
+}
+
+} // namespace react
+} // namespace facebook
diff --git a/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.h b/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.h
new file mode 100644
index 00000000000000..7222ba2981e71c
--- /dev/null
+++ b/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) Facebook, Inc. and its 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
+#include
+
+namespace facebook {
+namespace react {
+
+// Default initial size for _keyValues array
+// 106 = 10 entries = 10*10 + 6 sizeof(header)
+constexpr uint16_t INITIAL_KEY_VALUE_SIZE = 106;
+
+// Default initial size for _dynamicDataValues array
+constexpr int INITIAL_DYNAMIC_DATA_SIZE = 200;
+
+/**
+ * MapBufferBuilder is a builder class for MapBuffer
+ */
+class MapBufferBuilder {
+ private:
+ Header _header = {ALIGNMENT, 0, 0};
+
+ void ensureKeyValueSpace();
+
+ void ensureDynamicDataSpace(int size);
+
+ void storeKeyValue(Key key, uint8_t *value, int valueSize);
+
+ // Array of [key,value] map entries:
+ // - Key is represented in 2 bytes
+ // - Value is stored into 8 bytes. The 8 bytes of the value will contain the
+ // actual value for the key or a pointer to the actual value (based on the
+ // type)
+ uint8_t *_keyValues;
+
+ // Amount of bytes allocated on _keyValues
+ uint16_t _keyValuesSize;
+
+ // Relative offset on the _keyValues array.
+ // This represents the first byte that can be written in _keyValues array
+ int _keyValuesOffset;
+
+ // This array contains data for dynamic values in the MapBuffer.
+ // A dynamic value is a String or another MapBuffer.
+ uint8_t *_dynamicDataValues;
+
+ // Amount of bytes allocated on _dynamicDataValues
+ uint16_t _dynamicDataSize;
+
+ // Relative offset on the _dynamicDataValues array.
+ // This represents the first byte that can be written in _dynamicDataValues
+ // array
+ int _dynamicDataOffset;
+
+ // Minimmum key to store in the MapBuffer (this is used to guarantee
+ // consistency)
+ uint16_t _minKeyToStore = 0;
+
+ public:
+ MapBufferBuilder();
+
+ MapBufferBuilder(uint16_t initialSize);
+
+ ~MapBufferBuilder();
+
+ static MapBuffer EMPTY();
+
+ void putInt(Key key, int value);
+
+ void putBool(Key key, bool value);
+
+ void putDouble(Key key, double value);
+
+ void putNull(Key key);
+
+ void putString(Key key, std::string value);
+
+ void putMapBuffer(Key key, MapBuffer &map);
+
+ // TODO This should return MapBuffer!
+ MapBuffer build();
+};
+
+} // namespace react
+} // namespace facebook
diff --git a/ReactCommon/react/renderer/mapbuffer/primitives.h b/ReactCommon/react/renderer/mapbuffer/primitives.h
index 7800ac70acc66f..ad153fd6f52647 100644
--- a/ReactCommon/react/renderer/mapbuffer/primitives.h
+++ b/ReactCommon/react/renderer/mapbuffer/primitives.h
@@ -11,12 +11,12 @@
// TODO: Enable CHECK_CONSISTENCY only in debug mode or test environments (or
// just in demand)
-#define CHECK_CONSISTENCY 1
+// #define CHECK_CONSISTENCY 1
constexpr static int NULL_VALUE = 0;
// Value used to verify if the data is serialized with LittleEndian order
-constexpr static int ALIGNMENT = 0xFE;
+constexpr static uint16_t ALIGNMENT = 0xFE;
using Key = uint16_t;
@@ -27,8 +27,8 @@ namespace react {
struct Header {
uint16_t alignment; // alignment of serialization
- uint16_t count; // amount of items
- uint16_t bufferSize; // Size of buffer that contains Strings and Objects
+ uint16_t count; // amount of items in the map
+ uint16_t bufferSize; // Amount of bytes used to store the map in memory
};
constexpr static int KEY_SIZE = sizeof(Key);
@@ -38,6 +38,11 @@ constexpr static int DOUBLE_SIZE = sizeof(double);
constexpr static int UINT8_SIZE = sizeof(uint8_t);
constexpr static int UINT16_SIZE = sizeof(uint16_t);
constexpr static int UINT64_SIZE = sizeof(uint64_t);
+constexpr static int HEADER_ALIGNMENT_OFFSET = 0;
+constexpr static int HEADER_COUNT_OFFSET = UINT16_SIZE;
+constexpr static int HEADER_BUFFER_SIZE_OFFSET = UINT16_SIZE * 2;
+
+constexpr static int MAX_VALUE_SIZE = UINT64_SIZE;
// 10 bytes : 2 key + 8 value
constexpr static int BUCKET_SIZE = KEY_SIZE + UINT64_SIZE;
@@ -56,13 +61,17 @@ inline int getValueOffset(Key key) {
return getKeyOffset(key) + KEY_SIZE;
}
+static inline const char *getCstring(const std::string *str) {
+ return str ? str->c_str() : "";
+}
+
inline void
checkKeyConsistency(const Header &header, const uint8_t *data, Key key) {
#ifdef CHECK_CONSISTENCY
if (key >= header.count) {
LOG(ERROR) << "Error: Key is higher than size of Map - key '" << key
<< "' - size: '" << header.count << "'";
- exit(1);
+ assert(false && "Error while reading key");
}
Key storedKey = 0;
@@ -74,7 +83,7 @@ checkKeyConsistency(const Header &header, const uint8_t *data, Key key) {
if (storedKey != key) {
LOG(ERROR) << "Error while reading key, expecting '" << key << "' found: '"
<< storedKey << "'";
- exit(1);
+ assert(false && "Error while reading key");
}
#endif
}
diff --git a/ReactCommon/react/renderer/mapbuffer/tests/MapBufferTest.cpp b/ReactCommon/react/renderer/mapbuffer/tests/MapBufferTest.cpp
index 5d718d50493b8c..cb18889cf76f7b 100644
--- a/ReactCommon/react/renderer/mapbuffer/tests/MapBufferTest.cpp
+++ b/ReactCommon/react/renderer/mapbuffer/tests/MapBufferTest.cpp
@@ -9,81 +9,136 @@
#include
#include
+#include
using namespace facebook::react;
-TEST(MapBufferTest, testMapGrowth) {
- auto buffer = MapBuffer();
+TEST(MapBufferTest, testSimpleIntMap) {
+ auto builder = MapBufferBuilder();
- buffer.putInt(0, 1234);
- buffer.putInt(1, 4321);
+ builder.putInt(0, 1234);
+ builder.putInt(1, 4321);
- buffer.finish();
+ auto map = builder.build();
- EXPECT_EQ(buffer.getSize(), 2);
- EXPECT_EQ(buffer.getInt(0), 1234);
- EXPECT_EQ(buffer.getInt(1), 4321);
+ EXPECT_EQ(map.getCount(), 2);
+ EXPECT_EQ(map.getInt(0), 1234);
+ EXPECT_EQ(map.getInt(1), 4321);
}
TEST(MapBufferTest, testMapBufferExtension) {
// 26 = 2 buckets: 2*10 + 6 sizeof(header)
int initialSize = 26;
- auto buffer = MapBuffer(initialSize);
+ auto buffer = MapBufferBuilder(initialSize);
buffer.putInt(0, 1234);
buffer.putInt(1, 4321);
buffer.putInt(2, 2121);
buffer.putInt(3, 1212);
- buffer.finish();
+ auto map = buffer.build();
- EXPECT_EQ(buffer.getSize(), 4);
+ EXPECT_EQ(map.getCount(), 4);
- EXPECT_EQ(buffer.getInt(0), 1234);
- EXPECT_EQ(buffer.getInt(1), 4321);
- EXPECT_EQ(buffer.getInt(2), 2121);
- EXPECT_EQ(buffer.getInt(3), 1212);
+ EXPECT_EQ(map.getInt(0), 1234);
+ EXPECT_EQ(map.getInt(1), 4321);
+ EXPECT_EQ(map.getInt(2), 2121);
+ EXPECT_EQ(map.getInt(3), 1212);
}
TEST(MapBufferTest, testBoolEntries) {
- auto buffer = MapBuffer();
+ auto buffer = MapBufferBuilder();
buffer.putBool(0, true);
buffer.putBool(1, false);
- buffer.finish();
+ auto map = buffer.build();
- EXPECT_EQ(buffer.getSize(), 2);
- EXPECT_EQ(buffer.getBool(0), true);
- EXPECT_EQ(buffer.getBool(1), false);
+ EXPECT_EQ(map.getCount(), 2);
+ EXPECT_EQ(map.getBool(0), true);
+ EXPECT_EQ(map.getBool(1), false);
}
TEST(MapBufferTest, testNullEntries) {
- auto buffer = MapBuffer();
+ auto buffer = MapBufferBuilder();
buffer.putNull(0);
buffer.putInt(1, 1234);
- buffer.finish();
+ auto map = buffer.build();
- EXPECT_EQ(buffer.getSize(), 2);
- EXPECT_EQ(buffer.isNull(0), true);
- EXPECT_EQ(buffer.isNull(1), false);
+ EXPECT_EQ(map.getCount(), 2);
+ EXPECT_EQ(map.isNull(0), true);
+ EXPECT_EQ(map.isNull(1), false);
// TODO: serialize null values to be distinguishable from '0' values
- // EXPECT_EQ(buffer.isNull(1), false);
- // EXPECT_EQ(buffer.getBool(1), false);
+ // EXPECT_EQ(map.isNull(1), false);
+ // EXPECT_EQ(map.getBool(1), false);
}
TEST(MapBufferTest, testDoubleEntries) {
- auto buffer = MapBuffer();
+ auto buffer = MapBufferBuilder();
buffer.putDouble(0, 123.4);
buffer.putDouble(1, 432.1);
- buffer.finish();
+ auto map = buffer.build();
+
+ EXPECT_EQ(map.getCount(), 2);
+
+ EXPECT_EQ(map.getDouble(0), 123.4);
+ EXPECT_EQ(map.getDouble(1), 432.1);
+}
+
+TEST(MapBufferTest, testStringEntries) {
+ auto builder = MapBufferBuilder();
+
+ builder.putString(0, "This is a test");
+ auto map = builder.build();
+
+ EXPECT_EQ(map.getString(0), "This is a test");
+}
+
+TEST(MapBufferTest, testUTFStringEntries) {
+ auto builder = MapBufferBuilder();
+
+ builder.putString(0, "Let's count: 的, 一, 是");
+ auto map = builder.build();
+
+ EXPECT_EQ(map.getString(0), "Let's count: 的, 一, 是");
+}
+
+TEST(MapBufferTest, testEmptyMap) {
+ auto builder = MapBufferBuilder();
+ auto map = builder.build();
+ EXPECT_EQ(map.getCount(), 0);
+}
+
+TEST(MapBufferTest, testEmptyMapConstant) {
+ auto map = MapBufferBuilder::EMPTY();
+ EXPECT_EQ(map.getCount(), 0);
+}
+
+TEST(MapBufferTest, testMapEntries) {
+ auto builder = MapBufferBuilder();
+ builder.putString(0, "This is a test");
+ builder.putInt(1, 1234);
+ auto map = builder.build();
+
+ EXPECT_EQ(map.getCount(), 2);
+ EXPECT_EQ(map.getString(0), "This is a test");
+ EXPECT_EQ(map.getInt(1), 1234);
+
+ auto builder2 = MapBufferBuilder();
+ builder2.putInt(0, 4321);
+ builder2.putMapBuffer(1, map);
+ auto map2 = builder2.build();
+
+ EXPECT_EQ(map2.getCount(), 2);
+ EXPECT_EQ(map2.getInt(0), 4321);
- EXPECT_EQ(buffer.getSize(), 2);
+ MapBuffer readMap2 = map2.getMapBuffer(1);
- EXPECT_EQ(buffer.getDouble(0), 123.4);
- EXPECT_EQ(buffer.getDouble(1), 432.1);
+ EXPECT_EQ(readMap2.getCount(), 2);
+ EXPECT_EQ(readMap2.getString(0), "This is a test");
+ EXPECT_EQ(readMap2.getInt(1), 1234);
}
diff --git a/ReactCommon/react/renderer/textlayoutmanager/Android.mk b/ReactCommon/react/renderer/textlayoutmanager/Android.mk
index 5af214a5ef70f2..500366152a9953 100644
--- a/ReactCommon/react/renderer/textlayoutmanager/Android.mk
+++ b/ReactCommon/react/renderer/textlayoutmanager/Android.mk
@@ -11,7 +11,7 @@ LOCAL_MODULE := react_render_textlayoutmanager
LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp $(LOCAL_PATH)/platform/android/react/renderer/textlayoutmanager/*.cpp)
-LOCAL_SHARED_LIBRARIES := libfolly_futures libreactnativeutilsjni libreact_utils libfb libfbjni libreact_render_uimanager libreact_render_componentregistry libreact_render_attributedstring libreact_render_mounting libfolly_json libyoga libfolly_json libreact_render_core libreact_render_debug libreact_render_graphics libreact_debug
+LOCAL_SHARED_LIBRARIES := libfolly_futures libreactnativeutilsjni libreact_utils libfb libfbjni libreact_render_uimanager libreact_render_componentregistry libreact_render_attributedstring libreact_render_mounting libfolly_json libyoga libfolly_json libreact_render_core libreact_render_debug libreact_render_graphics libreact_debug libreact_render_mapbuffer libmapbufferjni
LOCAL_STATIC_LIBRARIES :=
@@ -39,3 +39,4 @@ $(call import-module,react/renderer/graphics)
$(call import-module,react/renderer/uimanager)
$(call import-module,react/utils)
$(call import-module,yogajni)
+$(call import-module,react/renderer/mapbuffer)
diff --git a/ReactCommon/react/renderer/textlayoutmanager/BUCK b/ReactCommon/react/renderer/textlayoutmanager/BUCK
index c79371156a8e12..de04dc7f8eb93a 100644
--- a/ReactCommon/react/renderer/textlayoutmanager/BUCK
+++ b/ReactCommon/react/renderer/textlayoutmanager/BUCK
@@ -62,6 +62,7 @@ rn_xplat_cxx_library(
cxx_tests = [":tests"],
fbandroid_deps = [
react_native_target("jni/react/jni:jni"),
+ react_native_xplat_target("react/renderer/mapbuffer:mapbuffer"),
],
fbandroid_exported_headers = subdir_glob(
[
diff --git a/ReactCommon/react/renderer/textlayoutmanager/platform/android/react/renderer/textlayoutmanager/TextLayoutManager.cpp b/ReactCommon/react/renderer/textlayoutmanager/platform/android/react/renderer/textlayoutmanager/TextLayoutManager.cpp
index 04a21772bcf1ef..f0ae38f5820f4f 100644
--- a/ReactCommon/react/renderer/textlayoutmanager/platform/android/react/renderer/textlayoutmanager/TextLayoutManager.cpp
+++ b/ReactCommon/react/renderer/textlayoutmanager/platform/android/react/renderer/textlayoutmanager/TextLayoutManager.cpp
@@ -40,8 +40,11 @@ TextMeasurement TextLayoutManager::measure(
telemetry->willMeasureText();
}
- auto measurement =
- doMeasure(attributedString, paragraphAttributes, layoutConstraints);
+ auto measurement = mapBufferSerializationEnabled_
+ ? doMeasureMapBuffer(
+ attributedString, paragraphAttributes, layoutConstraints)
+ : doMeasure(
+ attributedString, paragraphAttributes, layoutConstraints);
if (telemetry) {
telemetry->didMeasureText();
@@ -93,6 +96,10 @@ LinesMeasurements TextLayoutManager::measureLines(
AttributedString attributedString,
ParagraphAttributes paragraphAttributes,
Size size) const {
+ if (mapBufferSerializationEnabled_) {
+ return measureLinesMapBuffer(attributedString, paragraphAttributes, size);
+ }
+
const jni::global_ref &fabricUIManager =
contextContainer_->at>("FabricUIManager");
static auto measureLines =
@@ -139,6 +146,47 @@ LinesMeasurements TextLayoutManager::measureLines(
return lineMeasurements;
}
+LinesMeasurements TextLayoutManager::measureLinesMapBuffer(
+ AttributedString attributedString,
+ ParagraphAttributes paragraphAttributes,
+ Size size) const {
+ const jni::global_ref &fabricUIManager =
+ contextContainer_->at>("FabricUIManager");
+ static auto measureLines =
+ jni::findClassStatic("com/facebook/react/fabric/FabricUIManager")
+ ->getMethod("measureLinesMapBuffer");
+
+ auto attributedStringMB =
+ ReadableMapBuffer::createWithContents(toMapBuffer(attributedString));
+ auto paragraphAttributesMB =
+ ReadableMapBuffer::createWithContents(toMapBuffer(paragraphAttributes));
+
+ auto array = measureLines(
+ fabricUIManager,
+ attributedStringMB.get(),
+ paragraphAttributesMB.get(),
+ size.width,
+ size.height);
+
+ auto dynamicArray = cthis(array)->consume();
+ LinesMeasurements lineMeasurements;
+ lineMeasurements.reserve(dynamicArray.size());
+
+ for (auto const &data : dynamicArray) {
+ lineMeasurements.push_back(LineMeasurement(data));
+ }
+
+ // Explicitly release smart pointers to free up space faster in JNI tables
+ attributedStringMB.reset();
+ paragraphAttributesMB.reset();
+
+ return lineMeasurements;
+}
+
TextMeasurement TextLayoutManager::doMeasure(
AttributedString attributedString,
ParagraphAttributes paragraphAttributes,
@@ -201,5 +249,67 @@ TextMeasurement TextLayoutManager::doMeasure(
return TextMeasurement{size, attachments};
}
+TextMeasurement TextLayoutManager::doMeasureMapBuffer(
+ AttributedString attributedString,
+ ParagraphAttributes paragraphAttributes,
+ LayoutConstraints layoutConstraints) const {
+ layoutConstraints.maximumSize.height = std::numeric_limits::infinity();
+
+ int attachmentsCount = 0;
+ for (auto fragment : attributedString.getFragments()) {
+ if (fragment.isAttachment()) {
+ attachmentsCount++;
+ }
+ }
+ auto env = Environment::current();
+ auto attachmentPositions = env->NewFloatArray(attachmentsCount * 2);
+
+ auto minimumSize = layoutConstraints.minimumSize;
+ auto maximumSize = layoutConstraints.maximumSize;
+
+ auto attributedStringMap = toMapBuffer(attributedString);
+ auto paragraphAttributesMap = toMapBuffer(paragraphAttributes);
+
+ auto size = measureAndroidComponentMapBuffer(
+ contextContainer_,
+ -1, // TODO: we should pass rootTag in
+ "RCTText",
+ attributedStringMap,
+ paragraphAttributesMap,
+ minimumSize.width,
+ maximumSize.width,
+ minimumSize.height,
+ maximumSize.height,
+ attachmentPositions);
+
+ jfloat *attachmentData = env->GetFloatArrayElements(attachmentPositions, 0);
+
+ auto attachments = TextMeasurement::Attachments{};
+ if (attachmentsCount > 0) {
+ int attachmentIndex = 0;
+ for (auto fragment : attributedString.getFragments()) {
+ if (fragment.isAttachment()) {
+ float top = attachmentData[attachmentIndex * 2];
+ float left = attachmentData[attachmentIndex * 2 + 1];
+ float width = fragment.parentShadowView.layoutMetrics.frame.size.width;
+ float height =
+ fragment.parentShadowView.layoutMetrics.frame.size.height;
+
+ auto rect = facebook::react::Rect{
+ {left, top}, facebook::react::Size{width, height}};
+ attachments.push_back(TextMeasurement::Attachment{rect, false});
+ attachmentIndex++;
+ }
+ }
+ }
+
+ // Clean up allocated ref
+ env->ReleaseFloatArrayElements(
+ attachmentPositions, attachmentData, JNI_ABORT);
+ env->DeleteLocalRef(attachmentPositions);
+
+ return TextMeasurement{size, attachments};
+}
+
} // namespace react
} // namespace facebook
diff --git a/ReactCommon/react/renderer/textlayoutmanager/platform/android/react/renderer/textlayoutmanager/TextLayoutManager.h b/ReactCommon/react/renderer/textlayoutmanager/platform/android/react/renderer/textlayoutmanager/TextLayoutManager.h
index 681c54e22fb793..19ae11b2a7d62c 100644
--- a/ReactCommon/react/renderer/textlayoutmanager/platform/android/react/renderer/textlayoutmanager/TextLayoutManager.h
+++ b/ReactCommon/react/renderer/textlayoutmanager/platform/android/react/renderer/textlayoutmanager/TextLayoutManager.h
@@ -7,6 +7,7 @@
#pragma once
+#include
#include
#include
#include
@@ -26,7 +27,11 @@ using SharedTextLayoutManager = std::shared_ptr;
class TextLayoutManager {
public:
TextLayoutManager(const ContextContainer::Shared &contextContainer)
- : contextContainer_(contextContainer){};
+ : contextContainer_(contextContainer) {
+ static auto value =
+ contextContainer->at("MapBufferSerializationEnabled");
+ mapBufferSerializationEnabled_ = value;
+ }
~TextLayoutManager();
/*
@@ -67,8 +72,19 @@ class TextLayoutManager {
ParagraphAttributes paragraphAttributes,
LayoutConstraints layoutConstraints) const;
+ TextMeasurement doMeasureMapBuffer(
+ AttributedString attributedString,
+ ParagraphAttributes paragraphAttributes,
+ LayoutConstraints layoutConstraints) const;
+
+ LinesMeasurements measureLinesMapBuffer(
+ AttributedString attributedString,
+ ParagraphAttributes paragraphAttributes,
+ Size size) const;
+
void *self_;
ContextContainer::Shared contextContainer_;
+ bool mapBufferSerializationEnabled_;
TextMeasureCache measureCache_{};
};
diff --git a/ReactCommon/react/utils/Android.mk b/ReactCommon/react/utils/Android.mk
index 4e15562a50dcd0..7e23e6eb26a14e 100644
--- a/ReactCommon/react/utils/Android.mk
+++ b/ReactCommon/react/utils/Android.mk
@@ -20,8 +20,9 @@ LOCAL_CFLAGS := \
LOCAL_CFLAGS += -fexceptions -frtti -std=c++14 -Wall
LOCAL_STATIC_LIBRARIES :=
-LOCAL_SHARED_LIBRARIES := libreact_debug
+LOCAL_SHARED_LIBRARIES := libreact_debug libreact_render_mapbuffer
include $(BUILD_SHARED_LIBRARY)
$(call import-module,react/debug)
+$(call import-module,react/renderer/mapbuffer)
diff --git a/ReactCommon/react/utils/BUCK b/ReactCommon/react/utils/BUCK
index 6e2f7ab2fe9021..6865e9d443afe2 100644
--- a/ReactCommon/react/utils/BUCK
+++ b/ReactCommon/react/utils/BUCK
@@ -6,6 +6,7 @@ load(
"get_apple_compiler_flags",
"get_apple_inspector_flags",
"get_preprocessor_flags_for_build_mode",
+ "react_native_target",
"react_native_xplat_target",
"rn_xplat_cxx_library",
"subdir_glob",
@@ -39,6 +40,10 @@ rn_xplat_cxx_library(
"-std=c++14",
"-Wall",
],
+ fbandroid_deps = [
+ react_native_target("java/com/facebook/react/common/mapbuffer/jni:jni"),
+ react_native_xplat_target("react/renderer/mapbuffer:mapbuffer"),
+ ],
fbobjc_compiler_flags = APPLE_COMPILER_FLAGS,
fbobjc_frameworks = ["Foundation"],
fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(),
diff --git a/ReactCommon/react/utils/LayoutManager.h b/ReactCommon/react/utils/LayoutManager.h
index 9789fc87d997ce..bc901971a3cbb2 100644
--- a/ReactCommon/react/utils/LayoutManager.h
+++ b/ReactCommon/react/utils/LayoutManager.h
@@ -10,6 +10,11 @@
#include
#include
#include
+#ifdef ANDROID
+#include
+#include
+#include
+#endif
#include
namespace facebook {
@@ -88,6 +93,57 @@ Size measureAndroidComponent(
return size;
}
+Size measureAndroidComponentMapBuffer(
+ const ContextContainer::Shared &contextContainer,
+ Tag rootTag,
+ std::string componentName,
+ MapBuffer &localData,
+ MapBuffer &props,
+ float minWidth,
+ float maxWidth,
+ float minHeight,
+ float maxHeight,
+ jfloatArray attachmentPositions) {
+ const jni::global_ref &fabricUIManager =
+ contextContainer->at>("FabricUIManager");
+ auto componentNameRef = make_jstring(componentName);
+
+ static auto measure =
+ jni::findClassStatic("com/facebook/react/fabric/FabricUIManager")
+ ->getMethod("measureMapBuffer");
+
+ auto localDataMap =
+ ReadableMapBuffer::createWithContents(std::move(localData));
+ auto propsMap = ReadableMapBuffer::createWithContents(std::move(props));
+
+ auto size = yogaMeassureToSize(measure(
+ fabricUIManager,
+ rootTag,
+ componentNameRef.get(),
+ localDataMap.get(),
+ propsMap.get(),
+ minWidth,
+ maxWidth,
+ minHeight,
+ maxHeight,
+ attachmentPositions));
+
+ // Explicitly release smart pointers to free up space faster in JNI tables
+ componentNameRef.reset();
+ localDataMap.reset();
+ propsMap.reset();
+ return size;
+}
+
#endif
} // namespace react