first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,160 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "BaseParagraphProps.h"
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/attributedstring/conversions.h>
#include <react/renderer/attributedstring/primitives.h>
#include <react/renderer/core/propsConversions.h>
#include <react/renderer/debug/debugStringConvertibleUtils.h>
#include <glog/logging.h>
namespace facebook::react {
BaseParagraphProps::BaseParagraphProps(
const PropsParserContext& context,
const BaseParagraphProps& sourceProps,
const RawProps& rawProps)
: ViewProps(context, sourceProps, rawProps),
BaseTextProps(context, sourceProps, rawProps),
paragraphAttributes(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.paragraphAttributes
: convertRawProp(
context,
rawProps,
sourceProps.paragraphAttributes,
{})),
isSelectable(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.isSelectable
: convertRawProp(
context,
rawProps,
"selectable",
sourceProps.isSelectable,
false)),
onTextLayout(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.onTextLayout
: convertRawProp(
context,
rawProps,
"onTextLayout",
sourceProps.onTextLayout,
{})) {
/*
* These props are applied to `View`, therefore they must not be a part of
* base text attributes.
*/
textAttributes.opacity = std::numeric_limits<Float>::quiet_NaN();
textAttributes.backgroundColor = {};
};
void BaseParagraphProps::setProp(
const PropsParserContext& context,
RawPropsPropNameHash hash,
const char* propName,
const RawValue& value) {
// All Props structs setProp methods must always, unconditionally,
// call all super::setProp methods, since multiple structs may
// reuse the same values.
ViewProps::setProp(context, hash, propName, value);
BaseTextProps::setProp(context, hash, propName, value);
static auto defaults = BaseParagraphProps{};
// ParagraphAttributes has its own switch statement - to keep all
// of these fields together, and because there are some collisions between
// propnames parsed here and outside of ParagraphAttributes.
// This code is also duplicated in AndroidTextInput.
static auto paDefaults = ParagraphAttributes{};
switch (hash) {
REBUILD_FIELD_SWITCH_CASE(
paDefaults,
value,
paragraphAttributes,
maximumNumberOfLines,
"numberOfLines");
REBUILD_FIELD_SWITCH_CASE(
paDefaults, value, paragraphAttributes, ellipsizeMode, "ellipsizeMode");
REBUILD_FIELD_SWITCH_CASE(
paDefaults,
value,
paragraphAttributes,
textBreakStrategy,
"textBreakStrategy");
REBUILD_FIELD_SWITCH_CASE(
paDefaults,
value,
paragraphAttributes,
adjustsFontSizeToFit,
"adjustsFontSizeToFit");
REBUILD_FIELD_SWITCH_CASE(
paDefaults,
value,
paragraphAttributes,
minimumFontScale,
"minimumFontScale");
REBUILD_FIELD_SWITCH_CASE(
paDefaults,
value,
paragraphAttributes,
minimumFontSize,
"minimumFontSize");
REBUILD_FIELD_SWITCH_CASE(
paDefaults,
value,
paragraphAttributes,
maximumFontSize,
"maximumFontSize");
REBUILD_FIELD_SWITCH_CASE(
paDefaults,
value,
paragraphAttributes,
includeFontPadding,
"includeFontPadding");
REBUILD_FIELD_SWITCH_CASE(
paDefaults,
value,
paragraphAttributes,
android_hyphenationFrequency,
"android_hyphenationFrequency");
REBUILD_FIELD_SWITCH_CASE(
paDefaults,
value,
paragraphAttributes,
textAlignVertical,
"textAlignVertical");
}
switch (hash) {
RAW_SET_PROP_SWITCH_CASE(isSelectable, "selectable");
RAW_SET_PROP_SWITCH_CASE_BASIC(onTextLayout);
}
/*
* These props are applied to `View`, therefore they must not be a part of
* base text attributes.
*/
textAttributes.opacity = std::numeric_limits<Float>::quiet_NaN();
textAttributes.backgroundColor = {};
}
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList BaseParagraphProps::getDebugProps() const {
return ViewProps::getDebugProps() + BaseTextProps::getDebugProps() +
paragraphAttributes.getDebugProps() +
SharedDebugStringConvertibleList{
debugStringConvertibleItem("selectable", isSelectable)};
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <limits>
#include <memory>
#include <react/renderer/attributedstring/ParagraphAttributes.h>
#include <react/renderer/components/text/BaseTextProps.h>
#include <react/renderer/components/view/ViewProps.h>
#include <react/renderer/core/Props.h>
#include <react/renderer/core/PropsParserContext.h>
namespace facebook::react {
/*
* Props of <Paragraph> component.
* Most of the props are directly stored in composed `ParagraphAttributes`
* object.
*/
class BaseParagraphProps : public ViewProps, public BaseTextProps {
public:
BaseParagraphProps() = default;
BaseParagraphProps(
const PropsParserContext &context,
const BaseParagraphProps &sourceProps,
const RawProps &rawProps);
void
setProp(const PropsParserContext &context, RawPropsPropNameHash hash, const char *propName, const RawValue &value);
#pragma mark - Props
/*
* Contains all prop values that affect visual representation of the
* paragraph.
*/
ParagraphAttributes paragraphAttributes{};
/*
* Defines can the text be selected (and copied) or not.
*/
bool isSelectable{};
bool onTextLayout{};
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugProps() const override;
#endif
};
} // namespace facebook::react

View File

@@ -0,0 +1,538 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "BaseTextProps.h"
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/attributedstring/conversions.h>
#include <react/renderer/core/graphicsConversions.h>
#include <react/renderer/core/propsConversions.h>
#include <react/renderer/debug/DebugStringConvertibleItem.h>
#include <react/utils/FloatComparison.h>
namespace facebook::react {
static TextAttributes convertRawProp(
const PropsParserContext& context,
const RawProps& rawProps,
const TextAttributes& sourceTextAttributes,
const TextAttributes& defaultTextAttributes) {
auto textAttributes = TextAttributes{};
// Color (not accessed by ViewProps)
textAttributes.foregroundColor = convertRawProp(
context,
rawProps,
"color",
sourceTextAttributes.foregroundColor,
defaultTextAttributes.foregroundColor);
// Font
textAttributes.fontFamily = convertRawProp(
context,
rawProps,
"fontFamily",
sourceTextAttributes.fontFamily,
defaultTextAttributes.fontFamily);
textAttributes.fontSize = convertRawProp(
context,
rawProps,
"fontSize",
sourceTextAttributes.fontSize,
defaultTextAttributes.fontSize);
textAttributes.fontSizeMultiplier = convertRawProp(
context,
rawProps,
"fontSizeMultiplier",
sourceTextAttributes.fontSizeMultiplier,
defaultTextAttributes.fontSizeMultiplier);
textAttributes.fontWeight = convertRawProp(
context,
rawProps,
"fontWeight",
sourceTextAttributes.fontWeight,
defaultTextAttributes.fontWeight);
textAttributes.fontStyle = convertRawProp(
context,
rawProps,
"fontStyle",
sourceTextAttributes.fontStyle,
defaultTextAttributes.fontStyle);
textAttributes.fontVariant = convertRawProp(
context,
rawProps,
"fontVariant",
sourceTextAttributes.fontVariant,
defaultTextAttributes.fontVariant);
textAttributes.allowFontScaling = convertRawProp(
context,
rawProps,
"allowFontScaling",
sourceTextAttributes.allowFontScaling,
defaultTextAttributes.allowFontScaling);
textAttributes.maxFontSizeMultiplier = convertRawProp(
context,
rawProps,
"maxFontSizeMultiplier",
sourceTextAttributes.maxFontSizeMultiplier,
defaultTextAttributes.maxFontSizeMultiplier);
textAttributes.dynamicTypeRamp = convertRawProp(
context,
rawProps,
"dynamicTypeRamp",
sourceTextAttributes.dynamicTypeRamp,
defaultTextAttributes.dynamicTypeRamp);
textAttributes.letterSpacing = convertRawProp(
context,
rawProps,
"letterSpacing",
sourceTextAttributes.letterSpacing,
defaultTextAttributes.letterSpacing);
textAttributes.textTransform = convertRawProp(
context,
rawProps,
"textTransform",
sourceTextAttributes.textTransform,
defaultTextAttributes.textTransform);
// Paragraph
textAttributes.lineHeight = convertRawProp(
context,
rawProps,
"lineHeight",
sourceTextAttributes.lineHeight,
defaultTextAttributes.lineHeight);
textAttributes.alignment = convertRawProp(
context,
rawProps,
"textAlign",
sourceTextAttributes.alignment,
defaultTextAttributes.alignment);
textAttributes.baseWritingDirection = convertRawProp(
context,
rawProps,
"writingDirection",
sourceTextAttributes.baseWritingDirection,
defaultTextAttributes.baseWritingDirection);
textAttributes.lineBreakStrategy = convertRawProp(
context,
rawProps,
"lineBreakStrategyIOS",
sourceTextAttributes.lineBreakStrategy,
defaultTextAttributes.lineBreakStrategy);
textAttributes.lineBreakMode = convertRawProp(
context,
rawProps,
"lineBreakModeIOS",
sourceTextAttributes.lineBreakMode,
defaultTextAttributes.lineBreakMode);
// Decoration
textAttributes.textDecorationColor = convertRawProp(
context,
rawProps,
"textDecorationColor",
sourceTextAttributes.textDecorationColor,
defaultTextAttributes.textDecorationColor);
textAttributes.textDecorationLineType = convertRawProp(
context,
rawProps,
"textDecorationLine",
sourceTextAttributes.textDecorationLineType,
defaultTextAttributes.textDecorationLineType);
textAttributes.textDecorationStyle = convertRawProp(
context,
rawProps,
"textDecorationStyle",
sourceTextAttributes.textDecorationStyle,
defaultTextAttributes.textDecorationStyle);
// Shadow
textAttributes.textShadowOffset = convertRawProp(
context,
rawProps,
"textShadowOffset",
sourceTextAttributes.textShadowOffset,
defaultTextAttributes.textShadowOffset);
textAttributes.textShadowRadius = convertRawProp(
context,
rawProps,
"textShadowRadius",
sourceTextAttributes.textShadowRadius,
defaultTextAttributes.textShadowRadius);
textAttributes.textShadowColor = convertRawProp(
context,
rawProps,
"textShadowColor",
sourceTextAttributes.textShadowColor,
defaultTextAttributes.textShadowColor);
// Special
textAttributes.isHighlighted = convertRawProp(
context,
rawProps,
"isHighlighted",
sourceTextAttributes.isHighlighted,
defaultTextAttributes.isHighlighted);
textAttributes.isPressable = convertRawProp(
context,
rawProps,
"isPressable",
sourceTextAttributes.isPressable,
defaultTextAttributes.isPressable);
// In general, we want this class to access props in the same order
// that ViewProps accesses them in, so that RawPropParser can optimize
// accesses. This is both theoretical, and ParagraphProps takes advantage
// of this.
// In particular: accessibilityRole, opacity, and backgroundColor also
// are parsed first by ViewProps (and indirectly AccessibilityProps).
// However, since RawPropsParser will always store these props /before/
// the unique BaseTextProps props, it is most efficient to parse these, in
// order, /after/ all of the other BaseTextProps, so that the RawPropsParser
// index rolls over only once instead of twice.
textAttributes.accessibilityRole = convertRawProp(
context,
rawProps,
"accessibilityRole",
sourceTextAttributes.accessibilityRole,
defaultTextAttributes.accessibilityRole);
textAttributes.role = convertRawProp(
context,
rawProps,
"role",
sourceTextAttributes.role,
defaultTextAttributes.role);
// Color (accessed in this order by ViewProps)
textAttributes.opacity = convertRawProp(
context,
rawProps,
"opacity",
sourceTextAttributes.opacity,
defaultTextAttributes.opacity);
textAttributes.backgroundColor = convertRawProp(
context,
rawProps,
"backgroundColor",
sourceTextAttributes.backgroundColor,
defaultTextAttributes.backgroundColor);
return textAttributes;
}
BaseTextProps::BaseTextProps(
const PropsParserContext& context,
const BaseTextProps& sourceProps,
const RawProps& rawProps)
: textAttributes(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.textAttributes
: convertRawProp(
context,
rawProps,
sourceProps.textAttributes,
TextAttributes{})) {};
void BaseTextProps::setProp(
const PropsParserContext& context,
RawPropsPropNameHash hash,
const char* /*propName*/,
const RawValue& value) {
static auto defaults = TextAttributes{};
switch (hash) {
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, foregroundColor, "color");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, fontFamily, "fontFamily");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, fontSize, "fontSize");
REBUILD_FIELD_SWITCH_CASE(
defaults,
value,
textAttributes,
fontSizeMultiplier,
"fontSizeMultiplier");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, fontWeight, "fontWeight");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, fontStyle, "fontStyle");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, fontVariant, "fontVariant");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, allowFontScaling, "allowFontScaling");
REBUILD_FIELD_SWITCH_CASE(
defaults,
value,
textAttributes,
maxFontSizeMultiplier,
"maxFontSizeMultiplier");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, letterSpacing, "letterSpacing");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, textTransform, "textTransform");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, lineHeight, "lineHeight");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, alignment, "textAlign");
REBUILD_FIELD_SWITCH_CASE(
defaults,
value,
textAttributes,
baseWritingDirection,
"baseWritingDirection");
REBUILD_FIELD_SWITCH_CASE(
defaults,
value,
textAttributes,
lineBreakStrategy,
"lineBreakStrategyIOS");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, lineBreakMode, "lineBreakModeIOS");
REBUILD_FIELD_SWITCH_CASE(
defaults,
value,
textAttributes,
textDecorationColor,
"textDecorationColor");
REBUILD_FIELD_SWITCH_CASE(
defaults,
value,
textAttributes,
textDecorationLineType,
"textDecorationLine");
REBUILD_FIELD_SWITCH_CASE(
defaults,
value,
textAttributes,
textDecorationStyle,
"textDecorationStyle");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, textShadowOffset, "textShadowOffset");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, textShadowRadius, "textShadowRadius");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, textShadowColor, "textShadowColor");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, isHighlighted, "isHighlighted");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, isPressable, "isPressable");
REBUILD_FIELD_SWITCH_CASE(
defaults,
value,
textAttributes,
accessibilityRole,
"accessibilityRole");
REBUILD_FIELD_SWITCH_CASE(defaults, value, textAttributes, role, "role");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, opacity, "opacity");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, backgroundColor, "backgroundColor");
}
}
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList BaseTextProps::getDebugProps() const {
return textAttributes.getDebugProps();
}
#endif
#ifdef RN_SERIALIZABLE_STATE
static folly::dynamic toDynamic(const Size& size) {
folly::dynamic sizeResult = folly::dynamic::object();
sizeResult["width"] = size.width;
sizeResult["height"] = size.height;
return sizeResult;
}
void BaseTextProps::appendTextAttributesProps(
folly::dynamic& result,
const BaseTextProps* oldProps) const {
if (textAttributes.foregroundColor !=
oldProps->textAttributes.foregroundColor) {
result["color"] = *textAttributes.foregroundColor;
}
if (textAttributes.fontFamily != oldProps->textAttributes.fontFamily) {
result["fontFamily"] = textAttributes.fontFamily;
}
if (!floatEquality(
textAttributes.fontSize, oldProps->textAttributes.fontSize)) {
result["fontSize"] = textAttributes.fontSize;
}
if (!floatEquality(
textAttributes.fontSizeMultiplier,
oldProps->textAttributes.fontSizeMultiplier)) {
result["fontSizeMultiplier"] = textAttributes.fontSizeMultiplier;
}
if (textAttributes.fontWeight != oldProps->textAttributes.fontWeight) {
result["fontWeight"] = textAttributes.fontWeight.has_value()
? toString(textAttributes.fontWeight.value())
: nullptr;
}
if (textAttributes.fontStyle != oldProps->textAttributes.fontStyle) {
result["fontStyle"] = textAttributes.fontStyle.has_value()
? toString(textAttributes.fontStyle.value())
: nullptr;
}
if (textAttributes.fontVariant != oldProps->textAttributes.fontVariant) {
result["fontVariant"] = textAttributes.fontVariant.has_value()
? toString(textAttributes.fontVariant.value())
: nullptr;
}
if (textAttributes.allowFontScaling !=
oldProps->textAttributes.allowFontScaling) {
result["allowFontScaling"] = textAttributes.allowFontScaling.has_value()
? textAttributes.allowFontScaling.value()
: folly::dynamic(nullptr);
}
if (!floatEquality(
textAttributes.maxFontSizeMultiplier,
oldProps->textAttributes.maxFontSizeMultiplier)) {
result["maxFontSizeMultiplier"] = textAttributes.maxFontSizeMultiplier;
}
if (textAttributes.dynamicTypeRamp !=
oldProps->textAttributes.dynamicTypeRamp) {
result["dynamicTypeRamp"] = textAttributes.dynamicTypeRamp.has_value()
? toString(textAttributes.dynamicTypeRamp.value())
: nullptr;
}
if (!floatEquality(
textAttributes.letterSpacing,
oldProps->textAttributes.letterSpacing)) {
result["letterSpacing"] = textAttributes.letterSpacing;
}
if (textAttributes.textTransform != oldProps->textAttributes.textTransform) {
result["textTransform"] = textAttributes.textTransform.has_value()
? toString(textAttributes.textTransform.value())
: nullptr;
}
if (!floatEquality(
textAttributes.lineHeight, oldProps->textAttributes.lineHeight)) {
result["lineHeight"] = textAttributes.lineHeight;
}
if (textAttributes.alignment != oldProps->textAttributes.alignment) {
result["textAlign"] = textAttributes.alignment.has_value()
? toString(textAttributes.alignment.value())
: nullptr;
}
if (textAttributes.baseWritingDirection !=
oldProps->textAttributes.baseWritingDirection) {
result["baseWritingDirection"] =
textAttributes.baseWritingDirection.has_value()
? toString(textAttributes.baseWritingDirection.value())
: nullptr;
}
if (textAttributes.lineBreakStrategy !=
oldProps->textAttributes.lineBreakStrategy) {
result["lineBreakStrategyIOS"] =
textAttributes.lineBreakStrategy.has_value()
? toString(textAttributes.lineBreakStrategy.value())
: nullptr;
}
if (textAttributes.lineBreakMode != oldProps->textAttributes.lineBreakMode) {
result["lineBreakModeIOS"] = textAttributes.lineBreakMode.has_value()
? toString(textAttributes.lineBreakMode.value())
: nullptr;
}
if (textAttributes.textDecorationColor !=
oldProps->textAttributes.textDecorationColor) {
result["textDecorationColor"] = *textAttributes.textDecorationColor;
}
if (textAttributes.textDecorationLineType !=
oldProps->textAttributes.textDecorationLineType) {
result["textDecorationLine"] =
textAttributes.textDecorationLineType.has_value()
? toString(textAttributes.textDecorationLineType.value())
: nullptr;
}
if (textAttributes.textDecorationStyle !=
oldProps->textAttributes.textDecorationStyle) {
result["textDecorationStyle"] =
textAttributes.textDecorationStyle.has_value()
? toString(textAttributes.textDecorationStyle.value())
: nullptr;
}
if (textAttributes.textShadowOffset !=
oldProps->textAttributes.textShadowOffset) {
result["textShadowOffset"] = textAttributes.textShadowOffset.has_value()
? toDynamic(textAttributes.textShadowOffset.value())
: nullptr;
}
if (!floatEquality(
textAttributes.textShadowRadius,
oldProps->textAttributes.textShadowRadius)) {
result["textShadowRadius"] = textAttributes.textShadowRadius;
}
if (textAttributes.textShadowColor !=
oldProps->textAttributes.textShadowColor) {
result["textShadowColor"] = *textAttributes.textShadowColor;
}
if (textAttributes.isHighlighted != oldProps->textAttributes.isHighlighted) {
result["isHighlighted"] = textAttributes.isHighlighted.has_value()
? textAttributes.isHighlighted.value()
: folly::dynamic(nullptr);
}
if (textAttributes.isPressable != oldProps->textAttributes.isPressable) {
result["isPressable"] = textAttributes.isPressable.has_value()
? textAttributes.isPressable.value()
: folly::dynamic(nullptr);
}
if (textAttributes.accessibilityRole !=
oldProps->textAttributes.accessibilityRole) {
result["accessibilityRole"] = textAttributes.accessibilityRole.has_value()
? toString(textAttributes.accessibilityRole.value())
: nullptr;
}
if (textAttributes.role != oldProps->textAttributes.role) {
result["role"] = textAttributes.role.has_value()
? toString(textAttributes.role.value())
: nullptr;
}
if (textAttributes.opacity != oldProps->textAttributes.opacity) {
result["opacity"] = textAttributes.opacity;
}
if (textAttributes.backgroundColor !=
oldProps->textAttributes.backgroundColor) {
result["backgroundColor"] = *textAttributes.backgroundColor;
}
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/attributedstring/TextAttributes.h>
#include <react/renderer/core/Props.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/graphics/Color.h>
namespace facebook::react {
/*
* `Props`-like class which is used as a base class for all Props classes
* that can have text attributes (such as Text and Paragraph).
*/
class BaseTextProps {
public:
BaseTextProps() = default;
BaseTextProps(const PropsParserContext &context, const BaseTextProps &sourceProps, const RawProps &rawProps);
void
setProp(const PropsParserContext &context, RawPropsPropNameHash hash, const char *propName, const RawValue &value);
#pragma mark - Props
TextAttributes textAttributes{};
#pragma mark - DebugStringConvertible (partially)
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugProps() const;
#endif
#ifdef RN_SERIALIZABLE_STATE
void appendTextAttributesProps(folly::dynamic &result, const BaseTextProps *prevProps) const;
#endif
};
} // namespace facebook::react

View File

@@ -0,0 +1,85 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "BaseTextShadowNode.h"
#include <react/renderer/components/text/RawTextProps.h>
#include <react/renderer/components/text/RawTextShadowNode.h>
#include <react/renderer/components/text/TextProps.h>
#include <react/renderer/components/text/TextShadowNode.h>
#include <react/renderer/mounting/ShadowView.h>
namespace facebook::react {
inline ShadowView shadowViewFromShadowNode(const ShadowNode& shadowNode) {
auto shadowView = ShadowView{shadowNode};
// Clearing `props` and `state` (which we don't use) allows avoiding retain
// cycles.
shadowView.props = nullptr;
shadowView.state = nullptr;
return shadowView;
}
void BaseTextShadowNode::buildAttributedString(
const TextAttributes& baseTextAttributes,
const ShadowNode& parentNode,
AttributedString& outAttributedString,
Attachments& outAttachments) {
bool lastFragmentWasRawText = false;
for (const auto& childNode : parentNode.getChildren()) {
// RawShadowNode
auto rawTextShadowNode =
dynamic_cast<const RawTextShadowNode*>(childNode.get());
if (rawTextShadowNode != nullptr) {
const auto& rawText = rawTextShadowNode->getConcreteProps().text;
if (lastFragmentWasRawText) {
outAttributedString.getFragments().back().string += rawText;
} else {
auto fragment = AttributedString::Fragment{};
fragment.string = rawText;
fragment.textAttributes = baseTextAttributes;
// Storing a retaining pointer to `ParagraphShadowNode` inside
// `attributedString` causes a retain cycle (besides that fact that we
// don't need it at all). Storing a `ShadowView` instance instead of
// `ShadowNode` should properly fix this problem.
fragment.parentShadowView = shadowViewFromShadowNode(parentNode);
outAttributedString.appendFragment(std::move(fragment));
lastFragmentWasRawText = true;
}
continue;
}
lastFragmentWasRawText = false;
// TextShadowNode
auto textShadowNode = dynamic_cast<const TextShadowNode*>(childNode.get());
if (textShadowNode != nullptr) {
auto localTextAttributes = baseTextAttributes;
localTextAttributes.apply(
textShadowNode->getConcreteProps().textAttributes);
buildAttributedString(
localTextAttributes,
*textShadowNode,
outAttributedString,
outAttachments);
continue;
}
// Any *other* kind of ShadowNode
auto fragment = AttributedString::Fragment{};
fragment.string = AttributedString::Fragment::AttachmentCharacter();
fragment.parentShadowView = shadowViewFromShadowNode(*childNode);
fragment.textAttributes = baseTextAttributes;
outAttributedString.appendFragment(std::move(fragment));
outAttachments.push_back(
Attachment{
childNode.get(), outAttributedString.getFragments().size() - 1});
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,61 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/attributedstring/AttributedString.h>
#include <react/renderer/attributedstring/TextAttributes.h>
namespace facebook::react {
/*
* Base class (one of) for shadow nodes that represents attributed text,
* such as Text and Paragraph (but not RawText).
*/
class BaseTextShadowNode {
public:
/*
* Represents additional information associated with some fragments which
* represent embedded into text component (such as an image or inline view).
*/
class Attachment final {
public:
/*
* Unowning pointer to a `ShadowNode` that represents the attachment.
* Cannot be `null`.
*/
const ShadowNode *shadowNode;
/*
* Index of the fragment in `AttributedString` that represents the
* the attachment.
*/
size_t fragmentIndex;
};
/*
* A list of `Attachment`s.
* Performance-wise, the prevailing case is when there are no attachments
* at all, therefore we don't need an inline buffer (`small_vector`).
*/
using Attachments = std::vector<Attachment>;
/*
* Builds an `AttributedString` which represents text content of the node.
* This is static so that both Paragraph (which subclasses BaseText) and
* TextInput (which does not) can use this.
* TODO T53299884: decide if this should be moved out and made a static
* function, or if TextInput should inherit from BaseTextShadowNode.
*/
static void buildAttributedString(
const TextAttributes &baseTextAttributes,
const ShadowNode &parentNode,
AttributedString &outAttributedString,
Attachments &outAttachments);
};
} // namespace facebook::react

View File

@@ -0,0 +1,51 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/internal/react-native-platform-selector.cmake)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
react_native_android_selector(platform_SRC
platform/android/react/renderer/components/text/*.cpp
platform/cxx/react/renderer/components/text/*.cpp
)
file(GLOB rrc_text_SRC CONFIGURE_DEPENDS *.cpp ${platform_SRC})
add_library(rrc_text OBJECT ${rrc_text_SRC})
react_native_android_selector(platform_DIR
${CMAKE_CURRENT_SOURCE_DIR}/platform/android/
${CMAKE_CURRENT_SOURCE_DIR}/platform/cxx/)
target_include_directories(rrc_text PUBLIC
${REACT_COMMON_DIR}
${platform_DIR})
react_native_android_selector(platform_DIR_PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/platform/android/react/renderer/components/text/
${CMAKE_CURRENT_SOURCE_DIR}/platform/cxx/react/renderer/components/text/)
target_include_directories(rrc_text PRIVATE
${platform_DIR_PRIVATE})
target_link_libraries(rrc_text
glog
folly_runtime
glog_init
jsi
react_debug
react_renderer_attributedstring
react_renderer_core
react_renderer_debug
react_renderer_graphics
react_renderer_mapbuffer
react_renderer_mounting
react_renderer_textlayoutmanager
react_utils
rrc_view
yoga
)
target_compile_reactnative_options(rrc_text PRIVATE)
target_compile_options(rrc_text PRIVATE -Wpedantic)

View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ParagraphComponentDescriptor.h"
namespace facebook::react {
extern const char TextLayoutManagerKey[] = "TextLayoutManager";
} // namespace facebook::react

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/components/text/ParagraphShadowNode.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/textlayoutmanager/TextLayoutManager.h>
#include <react/utils/ContextContainer.h>
namespace facebook::react {
extern const char TextLayoutManagerKey[];
/*
* Descriptor for <Paragraph> component.
*/
class ParagraphComponentDescriptor final : public ConcreteComponentDescriptor<ParagraphShadowNode> {
public:
explicit ParagraphComponentDescriptor(const ComponentDescriptorParameters &parameters)
: ConcreteComponentDescriptor<ParagraphShadowNode>(parameters),
textLayoutManager_(getManagerByName<TextLayoutManager>(contextContainer_, TextLayoutManagerKey))
{
}
protected:
void adopt(ShadowNode &shadowNode) const override
{
ConcreteComponentDescriptor::adopt(shadowNode);
auto &paragraphShadowNode = static_cast<ParagraphShadowNode &>(shadowNode);
// `ParagraphShadowNode` uses `TextLayoutManager` to measure text content
// and communicate text rendering metrics to mounting layer.
paragraphShadowNode.setTextLayoutManager(textLayoutManager_);
}
private:
// Every `ParagraphShadowNode` has a reference to a shared `TextLayoutManager`
const std::shared_ptr<const TextLayoutManager> textLayoutManager_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ParagraphEventEmitter.h"
namespace facebook::react {
static jsi::Value linesMeasurementsPayload(
jsi::Runtime& runtime,
const LinesMeasurements& linesMeasurements) {
auto payload = jsi::Object(runtime);
auto lines = jsi::Array(runtime, linesMeasurements.size());
for (size_t i = 0; i < linesMeasurements.size(); ++i) {
const auto& lineMeasurement = linesMeasurements[i];
auto jsiLine = jsi::Object(runtime);
jsiLine.setProperty(runtime, "text", lineMeasurement.text);
jsiLine.setProperty(runtime, "x", lineMeasurement.frame.origin.x);
jsiLine.setProperty(runtime, "y", lineMeasurement.frame.origin.y);
jsiLine.setProperty(runtime, "width", lineMeasurement.frame.size.width);
jsiLine.setProperty(runtime, "height", lineMeasurement.frame.size.height);
jsiLine.setProperty(runtime, "descender", lineMeasurement.descender);
jsiLine.setProperty(runtime, "capHeight", lineMeasurement.capHeight);
jsiLine.setProperty(runtime, "ascender", lineMeasurement.ascender);
jsiLine.setProperty(runtime, "xHeight", lineMeasurement.xHeight);
lines.setValueAtIndex(runtime, i, jsiLine);
}
payload.setProperty(runtime, "lines", lines);
return payload;
}
void ParagraphEventEmitter::onTextLayout(
const LinesMeasurements& linesMeasurements) const {
{
std::scoped_lock guard(linesMeasurementsMutex_);
if (linesMeasurementsMetrics_ == linesMeasurements) {
return;
}
linesMeasurementsMetrics_ = linesMeasurements;
}
dispatchEvent("textLayout", [linesMeasurements](jsi::Runtime& runtime) {
return linesMeasurementsPayload(runtime, linesMeasurements);
});
}
} // namespace facebook::react

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/components/view/ViewEventEmitter.h>
#include <react/renderer/textlayoutmanager/TextMeasureCache.h>
namespace facebook::react {
class ParagraphEventEmitter : public ViewEventEmitter {
public:
using ViewEventEmitter::ViewEventEmitter;
void onTextLayout(const LinesMeasurements &linesMeasurements) const;
private:
mutable std::mutex linesMeasurementsMutex_;
mutable LinesMeasurements linesMeasurementsMetrics_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/components/text/HostPlatformParagraphProps.h>
namespace facebook::react {
/*
* Props of <Paragraph> component.
* Most of the props are directly stored in composed `ParagraphAttributes`
* object.
*/
using ParagraphProps = HostPlatformParagraphProps;
} // namespace facebook::react

View File

@@ -0,0 +1,448 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ParagraphShadowNode.h"
#include <cmath>
#include <react/debug/react_native_assert.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/attributedstring/AttributedStringBox.h>
#include <react/renderer/components/view/ViewShadowNode.h>
#include <react/renderer/components/view/conversions.h>
#include <react/renderer/graphics/rounding.h>
#include <react/renderer/telemetry/TransactionTelemetry.h>
#include <react/renderer/textlayoutmanager/TextLayoutContext.h>
#include <react/utils/FloatComparison.h>
#include <react/renderer/components/text/ParagraphState.h>
#define assert_valid_size(size, layoutConstraints) \
react_native_assert( \
(size).width + kDefaultEpsilon >= \
(layoutConstraints).minimumSize.width && \
(size).width - kDefaultEpsilon <= \
(layoutConstraints).maximumSize.width && \
(size).height + kDefaultEpsilon >= \
(layoutConstraints).minimumSize.height && \
(size).height - kDefaultEpsilon <= \
(layoutConstraints).maximumSize.height)
namespace facebook::react {
using Content = ParagraphShadowNode::Content;
// NOLINTNEXTLINE(facebook-hte-CArray, modernize-avoid-c-arrays)
const char ParagraphComponentName[] = "Paragraph";
void ParagraphShadowNode::initialize() noexcept {
#ifdef ANDROID
if (getConcreteProps().isSelectable) {
traits_.set(ShadowNodeTraits::Trait::KeyboardFocusable);
}
#endif
}
ParagraphShadowNode::ParagraphShadowNode(
const ShadowNodeFragment& fragment,
const ShadowNodeFamily::Shared& family,
ShadowNodeTraits traits)
: ConcreteViewShadowNode(fragment, family, traits) {
initialize();
}
ParagraphShadowNode::ParagraphShadowNode(
const ShadowNode& sourceShadowNode,
const ShadowNodeFragment& fragment)
: ConcreteViewShadowNode(sourceShadowNode, fragment) {
initialize();
}
bool ParagraphShadowNode::shouldNewRevisionDirtyMeasurement(
const ShadowNode& /*sourceShadowNode*/,
const ShadowNodeFragment& fragment) const {
return fragment.props != nullptr;
}
const Content& ParagraphShadowNode::getContent(
const LayoutContext& layoutContext) const {
if (content_.has_value()) {
return content_.value();
}
ensureUnsealed();
auto textAttributes = TextAttributes::defaultTextAttributes();
textAttributes.fontSizeMultiplier = layoutContext.fontSizeMultiplier;
textAttributes.apply(getConcreteProps().textAttributes);
textAttributes.layoutDirection =
YGNodeLayoutGetDirection(&yogaNode_) == YGDirectionRTL
? LayoutDirection::RightToLeft
: LayoutDirection::LeftToRight;
auto attributedString = AttributedString{};
auto attachments = Attachments{};
buildAttributedString(textAttributes, *this, attributedString, attachments);
attributedString.setBaseTextAttributes(textAttributes);
content_ = Content{
attributedString, getConcreteProps().paragraphAttributes, attachments};
return content_.value();
}
Content ParagraphShadowNode::getContentWithMeasuredAttachments(
const LayoutContext& layoutContext,
const LayoutConstraints& layoutConstraints) const {
auto content = getContent(layoutContext);
if (content.attachments.empty()) {
// Base case: No attachments, nothing to do.
return content;
}
auto localLayoutConstraints = layoutConstraints;
// Having enforced minimum size for text fragments doesn't make much sense.
localLayoutConstraints.minimumSize = Size{0, 0};
auto& fragments = content.attributedString.getFragments();
for (const auto& attachment : content.attachments) {
auto laytableShadowNode =
dynamic_cast<const LayoutableShadowNode*>(attachment.shadowNode);
if (laytableShadowNode == nullptr) {
continue;
}
auto size =
laytableShadowNode->measure(layoutContext, localLayoutConstraints);
// Rounding to *next* value on the pixel grid.
size.width += 0.01f;
size.height += 0.01f;
size = roundToPixel<&ceil>(size, layoutContext.pointScaleFactor);
auto fragmentLayoutMetrics = LayoutMetrics{};
fragmentLayoutMetrics.pointScaleFactor = layoutContext.pointScaleFactor;
fragmentLayoutMetrics.frame.size = size;
fragments[attachment.fragmentIndex].parentShadowView.layoutMetrics =
fragmentLayoutMetrics;
}
return content;
}
void ParagraphShadowNode::setTextLayoutManager(
std::shared_ptr<const TextLayoutManager> textLayoutManager) {
ensureUnsealed();
textLayoutManager_ = std::move(textLayoutManager);
}
template <typename ParagraphStateT>
void ParagraphShadowNode::updateStateIfNeeded(
const Content& content,
const MeasuredPreparedLayout& layout) {
ensureUnsealed();
auto& state = static_cast<const ParagraphStateT&>(getStateData());
react_native_assert(textLayoutManager_);
if (state.measuredLayout.measurement.size == layout.measurement.size &&
state.attributedString == content.attributedString &&
state.paragraphAttributes == content.paragraphAttributes) {
return;
}
setStateData(
ParagraphStateT{
content.attributedString,
content.paragraphAttributes,
textLayoutManager_,
layout});
}
void ParagraphShadowNode::updateStateIfNeeded(const Content& content) {
ensureUnsealed();
auto& state = getStateData();
react_native_assert(textLayoutManager_);
if (state.attributedString == content.attributedString) {
return;
}
setStateData(
ParagraphState{
content.attributedString,
content.paragraphAttributes,
textLayoutManager_});
}
MeasuredPreparedLayout* ParagraphShadowNode::findUsableLayout() {
MeasuredPreparedLayout* ret = nullptr;
if constexpr (TextLayoutManagerExtended::supportsPreparedLayout()) {
// We consider the layout to be reusable, if our content measurement,
// combined with padding/border (not snapped) exactly corresponds to the
// measurement of the node, before layout rounding. We may not find a
// compatible layout, such as if Yoga already knew the dimensions (an
// exact width/height was given), or if our content measurement was
// adjusted by a constraint (like min-size).
auto expectedSize = rawContentSize();
for (auto& prevLayout : measuredLayouts_) {
if (floatEquality(
prevLayout.measurement.size.width, expectedSize.width) &&
floatEquality(
prevLayout.measurement.size.height, expectedSize.height)) {
ret = &prevLayout;
break;
}
}
}
return ret;
}
Size ParagraphShadowNode::rawContentSize() {
return Size{
.width = YGNodeLayoutGetRawWidth(&yogaNode_) -
layoutMetrics_.contentInsets.left -
layoutMetrics_.contentInsets.right,
.height = YGNodeLayoutGetRawHeight(&yogaNode_) -
layoutMetrics_.contentInsets.top -
layoutMetrics_.contentInsets.bottom};
}
#pragma mark - LayoutableShadowNode
Size ParagraphShadowNode::measureContent(
const LayoutContext& layoutContext,
const LayoutConstraints& layoutConstraints) const {
if constexpr (TextLayoutManagerExtended::supportsPreparedLayout()) {
for (const auto& layout : measuredLayouts_) {
if (layout.layoutConstraints == layoutConstraints) {
return layout.measurement.size;
}
}
}
auto content =
getContentWithMeasuredAttachments(layoutContext, layoutConstraints);
TextLayoutContext textLayoutContext{
.pointScaleFactor = layoutContext.pointScaleFactor,
.surfaceId = getSurfaceId(),
};
if constexpr (TextLayoutManagerExtended::supportsPreparedLayout()) {
if (ReactNativeFeatureFlags::enablePreparedTextLayout()) {
TextLayoutManagerExtended tme(*textLayoutManager_);
auto preparedLayout = tme.prepareLayout(
content.attributedString,
content.paragraphAttributes,
textLayoutContext,
layoutConstraints);
auto measurement = tme.measurePreparedLayout(
preparedLayout, textLayoutContext, layoutConstraints);
measuredLayouts_.push_back(
MeasuredPreparedLayout{
.layoutConstraints = layoutConstraints,
.measurement = measurement,
// PreparedLayout is not trivially copyable on all platforms
// NOLINTNEXTLINE(performance-move-const-arg)
.preparedLayout = std::move(preparedLayout)});
assert_valid_size(measurement.size, layoutConstraints);
return measurement.size;
}
}
auto size = textLayoutManager_
->measure(
AttributedStringBox{content.attributedString},
content.paragraphAttributes,
textLayoutContext,
layoutConstraints)
.size;
assert_valid_size(size, layoutConstraints);
return size;
}
Float ParagraphShadowNode::baseline(
const LayoutContext& layoutContext,
Size size) const {
auto layoutMetrics = getLayoutMetrics();
auto layoutConstraints =
LayoutConstraints{size, size, layoutMetrics.layoutDirection};
auto content =
getContentWithMeasuredAttachments(layoutContext, layoutConstraints);
AttributedStringBox attributedStringBox{content.attributedString};
if constexpr (TextLayoutManagerExtended::supportsLineMeasurement()) {
auto lines =
TextLayoutManagerExtended(*textLayoutManager_)
.measureLines(
attributedStringBox, content.paragraphAttributes, size);
return LineMeasurement::baseline(lines);
} else {
LOG(WARNING)
<< "Baseline alignment is not supported by the current platform";
return 0;
}
}
void ParagraphShadowNode::layout(LayoutContext layoutContext) {
ensureUnsealed();
auto layoutMetrics = getLayoutMetrics();
auto size = ReactNativeFeatureFlags::enablePreparedTextLayout()
? rawContentSize()
: layoutMetrics.getContentFrame().size;
LayoutConstraints layoutConstraints{
.minimumSize = size,
.maximumSize = size,
.layoutDirection = layoutMetrics.layoutDirection};
auto content =
getContentWithMeasuredAttachments(layoutContext, layoutConstraints);
auto measuredLayout = findUsableLayout();
if constexpr (
TextLayoutManagerExtended::supportsPreparedLayout() &&
std::is_constructible_v<
ParagraphState,
decltype(content.attributedString),
decltype(content.paragraphAttributes),
decltype(textLayoutManager_),
decltype(*measuredLayout)>) {
if (ReactNativeFeatureFlags::enablePreparedTextLayout()) {
// We may not have a reusable layout, like if Yoga knew exact dimensions
// for the paragraph. Measure now, if this is the case.
// T223634461: This would ideally happen lazily, in case the view may be
// culled
if (measuredLayout == nullptr) {
measureContent(layoutContext, layoutConstraints);
measuredLayout = findUsableLayout();
}
react_native_assert(measuredLayout);
updateStateIfNeeded<ParagraphState>(content, *measuredLayout);
} else {
updateStateIfNeeded(content);
}
} else {
updateStateIfNeeded(content);
}
TextLayoutContext textLayoutContext{
.pointScaleFactor = layoutContext.pointScaleFactor,
.surfaceId = getSurfaceId(),
};
AttributedStringBox attributedStringBox{content.attributedString};
if (getConcreteProps().onTextLayout) {
if constexpr (TextLayoutManagerExtended::supportsLineMeasurement()) {
auto linesMeasurements =
TextLayoutManagerExtended(*textLayoutManager_)
.measureLines(
attributedStringBox, content.paragraphAttributes, size);
getConcreteEventEmitter().onTextLayout(linesMeasurements);
} else {
LOG(WARNING) << "onTextLayout is not supported by the current platform";
}
}
if (content.attachments.empty()) {
// No attachments to layout.
return;
}
// Only measure if attachments are not empty.
auto measurement = (measuredLayout != nullptr)
? measuredLayout->measurement
: textLayoutManager_->measure(
attributedStringBox,
content.paragraphAttributes,
textLayoutContext,
layoutConstraints);
// Iterating on attachments, we clone shadow nodes and moving
// `paragraphShadowNode` that represents clones of `this` object.
auto paragraphShadowNode = static_cast<ParagraphShadowNode*>(this);
// `paragraphOwningShadowNode` is owning pointer to`paragraphShadowNode`
// (besides the initial case when `paragraphShadowNode == this`), we need
// this only to keep it in memory for a while.
auto paragraphOwningShadowNode = std::shared_ptr<ShadowNode>{};
react_native_assert(
content.attachments.size() == measurement.attachments.size());
for (size_t i = 0; i < content.attachments.size(); i++) {
auto& attachment = content.attachments.at(i);
if (dynamic_cast<const LayoutableShadowNode*>(attachment.shadowNode) ==
nullptr) {
// Not a layoutable `ShadowNode`, no need to lay it out.
continue;
}
auto clonedShadowNode = std::shared_ptr<ShadowNode>{};
paragraphOwningShadowNode = paragraphShadowNode->cloneTree(
attachment.shadowNode->getFamily(),
[&](const ShadowNode& oldShadowNode) {
clonedShadowNode = oldShadowNode.clone({});
return clonedShadowNode;
});
paragraphShadowNode =
static_cast<ParagraphShadowNode*>(paragraphOwningShadowNode.get());
auto& layoutableShadowNode =
dynamic_cast<LayoutableShadowNode&>(*clonedShadowNode);
const auto& attachmentMeasurement = measurement.attachments[i];
if (attachmentMeasurement.isClipped) {
layoutableShadowNode.setLayoutMetrics(
LayoutMetrics{.frame = {}, .displayType = DisplayType::None});
continue;
}
auto attachmentFrame = attachmentMeasurement.frame;
attachmentFrame.origin.x += layoutMetrics.contentInsets.left;
attachmentFrame.origin.y += layoutMetrics.contentInsets.top;
auto attachmentSize = roundToPixel<&ceil>(
attachmentFrame.size, layoutMetrics.pointScaleFactor);
auto attachmentOrigin = roundToPixel<&round>(
attachmentFrame.origin, layoutMetrics.pointScaleFactor);
auto attachmentLayoutContext = layoutContext;
auto attachmentLayoutConstrains = LayoutConstraints{
attachmentSize, attachmentSize, layoutConstraints.layoutDirection};
// Laying out the `ShadowNode` and the subtree starting from it.
layoutableShadowNode.layoutTree(
attachmentLayoutContext, attachmentLayoutConstrains);
// Altering the origin of the `ShadowNode` (which is defined by text
// layout, not by internal styles and state).
auto attachmentLayoutMetrics = layoutableShadowNode.getLayoutMetrics();
attachmentLayoutMetrics.frame.origin = attachmentOrigin;
layoutableShadowNode.setLayoutMetrics(attachmentLayoutMetrics);
}
// If we ended up cloning something, we need to update the list of children
// to reflect the changes that we made.
if (paragraphShadowNode != this) {
this->children_ =
static_cast<const ParagraphShadowNode*>(paragraphShadowNode)->children_;
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,142 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/components/text/BaseTextShadowNode.h>
#include <react/renderer/components/text/ParagraphEventEmitter.h>
#include <react/renderer/components/text/ParagraphProps.h>
#include <react/renderer/components/text/ParagraphState.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/core/LayoutContext.h>
#include <react/renderer/core/ShadowNode.h>
#include <react/renderer/textlayoutmanager/TextLayoutManager.h>
#include <react/renderer/textlayoutmanager/TextLayoutManagerExtended.h>
namespace facebook::react {
extern const char ParagraphComponentName[];
/*
* `ShadowNode` for <Paragraph> component, represents <View>-like component
* containing and displaying text. Text content is represented as nested <Text>
* and <RawText> components.
*/
class ParagraphShadowNode final
: public ConcreteViewShadowNode<ParagraphComponentName, ParagraphProps, ParagraphEventEmitter, ParagraphState>,
public BaseTextShadowNode {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
ParagraphShadowNode(
const ShadowNodeFragment &fragment,
const ShadowNodeFamily::Shared &family,
ShadowNodeTraits traits);
ParagraphShadowNode(const ShadowNode &sourceShadowNode, const ShadowNodeFragment &fragment);
static ShadowNodeTraits BaseTraits()
{
auto traits = ConcreteViewShadowNode::BaseTraits();
traits.set(ShadowNodeTraits::Trait::LeafYogaNode);
traits.set(ShadowNodeTraits::Trait::MeasurableYogaNode);
traits.set(ShadowNodeTraits::Trait::BaselineYogaNode);
#ifdef ANDROID
// Unsetting `FormsStackingContext` trait is essential on Android where we
// can't mount views inside `TextView`.
// T221699219: This should be removed when PreparedLayoutTextView is rolled
// out.
traits.unset(ShadowNodeTraits::Trait::FormsStackingContext);
#endif
return traits;
}
/*
* Associates a shared TextLayoutManager with the node.
* `ParagraphShadowNode` uses the manager to measure text content
* and construct `ParagraphState` objects.
*/
void setTextLayoutManager(std::shared_ptr<const TextLayoutManager> textLayoutManager);
#pragma mark - LayoutableShadowNode
void layout(LayoutContext layoutContext) override;
Size measureContent(const LayoutContext &layoutContext, const LayoutConstraints &layoutConstraints) const override;
Float baseline(const LayoutContext &layoutContext, Size size) const override;
/*
* Internal representation of the nested content of the node in a format
* suitable for future processing.
*/
class Content final {
public:
AttributedString attributedString;
ParagraphAttributes paragraphAttributes;
Attachments attachments;
};
protected:
bool shouldNewRevisionDirtyMeasurement(const ShadowNode &sourceShadowNode, const ShadowNodeFragment &fragment)
const override;
private:
void initialize() noexcept;
/*
* Builds (if needed) and returns a reference to a `Content` object.
*/
const Content &getContent(const LayoutContext &layoutContext) const;
/*
* Builds and returns a `Content` object with given `layoutConstraints`.
*/
Content getContentWithMeasuredAttachments(
const LayoutContext &layoutContext,
const LayoutConstraints &layoutConstraints) const;
/*
* Creates a `State` object (with `AttributedText` and
* `TextLayoutManager`) if needed.
*/
template <typename ParagraphStateT>
void updateStateIfNeeded(const Content &content, const MeasuredPreparedLayout &layout);
/*
* Creates a `State` object (with `AttributedText` and
* `TextLayoutManager`) if needed.
*/
void updateStateIfNeeded(const Content &content);
/**
* Returns any previously prepared layout, generated for measurement, which
* may be used, given the currently assigned layout to the ShadowNode
*/
MeasuredPreparedLayout *findUsableLayout();
/**
* Returns the final measure of the content frame, before layout rounding
*/
Size rawContentSize();
std::shared_ptr<const TextLayoutManager> textLayoutManager_;
/*
* Cached content of the subtree started from the node.
*/
mutable std::optional<Content> content_{};
/*
* Intermediate layout results generated during measurement, that may be
* reused by the platform.
*/
mutable std::vector<MeasuredPreparedLayout> measuredLayouts_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,17 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/components/text/RawTextShadowNode.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
namespace facebook::react {
using RawTextComponentDescriptor = ConcreteComponentDescriptor<RawTextShadowNode>;
} // namespace facebook::react

View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "RawTextProps.h"
#include <react/renderer/core/propsConversions.h>
#include <react/renderer/debug/debugStringConvertibleUtils.h>
namespace facebook::react {
RawTextProps::RawTextProps(
const PropsParserContext& context,
const RawTextProps& sourceProps,
const RawProps& rawProps)
: Props(context, sourceProps, rawProps),
text(convertRawProp(context, rawProps, "text", sourceProps.text, {})) {};
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList RawTextProps::getDebugProps() const {
return SharedDebugStringConvertibleList{
debugStringConvertibleItem("text", text)};
}
#endif
#ifdef RN_SERIALIZABLE_STATE
ComponentName RawTextProps::getDiffPropsImplementationTarget() const {
return "RawText";
}
folly::dynamic RawTextProps::getDiffProps(const Props* prevProps) const {
folly::dynamic result = folly::dynamic::object();
static const auto defaultProps = RawTextProps();
const RawTextProps* oldProps = prevProps == nullptr
? &defaultProps
: static_cast<const RawTextProps*>(prevProps);
if (this == oldProps) {
return result;
}
if (text != oldProps->text) {
result["text"] = text;
}
return result;
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <memory>
#include <react/renderer/core/Props.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/debug/DebugStringConvertible.h>
namespace facebook::react {
class RawTextProps;
using SharedRawTextProps = std::shared_ptr<const RawTextProps>;
class RawTextProps : public Props {
public:
RawTextProps() = default;
RawTextProps(const PropsParserContext &context, const RawTextProps &sourceProps, const RawProps &rawProps);
#pragma mark - Props
std::string text{};
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugProps() const override;
#endif
#ifdef RN_SERIALIZABLE_STATE
ComponentName getDiffPropsImplementationTarget() const override;
folly::dynamic getDiffProps(const Props *prevProps) const override;
#endif
};
} // namespace facebook::react

View File

@@ -0,0 +1,15 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "RawTextShadowNode.h"
namespace facebook::react {
// NOLINTNEXTLINE(modernize-avoid-c-arrays)
extern const char RawTextComponentName[] = "RawText";
} // namespace facebook::react

View File

@@ -0,0 +1,28 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/components/text/RawTextProps.h>
#include <react/renderer/core/ConcreteShadowNode.h>
namespace facebook::react {
extern const char RawTextComponentName[];
/*
* `ShadowNode` for <RawText> component, represents a purely regular string
* object in React. In a code fragment `<Text>Hello!</Text>`, "Hello!" part
* is represented as `<RawText text="Hello!"/>`.
* <RawText> component must not have any children.
*/
class RawTextShadowNode : public ConcreteShadowNode<RawTextComponentName, ShadowNode, RawTextProps> {
public:
using ConcreteShadowNode::ConcreteShadowNode;
};
} // namespace facebook::react

View File

@@ -0,0 +1,17 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/components/text/TextShadowNode.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
namespace facebook::react {
using TextComponentDescriptor = ConcreteComponentDescriptor<TextShadowNode>;
} // namespace facebook::react

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "TextProps.h"
namespace facebook::react {
TextProps::TextProps(
const PropsParserContext& context,
const TextProps& sourceProps,
const RawProps& rawProps)
: Props(context, sourceProps, rawProps),
BaseTextProps::BaseTextProps(context, sourceProps, rawProps) {};
void TextProps::setProp(
const PropsParserContext& context,
RawPropsPropNameHash hash,
const char* propName,
const RawValue& value) {
BaseTextProps::setProp(context, hash, propName, value);
Props::setProp(context, hash, propName, value);
}
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList TextProps::getDebugProps() const {
return BaseTextProps::getDebugProps();
}
#endif
#ifdef RN_SERIALIZABLE_STATE
ComponentName TextProps::getDiffPropsImplementationTarget() const {
return "Text";
}
folly::dynamic TextProps::getDiffProps(const Props* prevProps) const {
folly::dynamic result = folly::dynamic::object();
static const auto defaultProps = TextProps();
const TextProps* oldProps = prevProps == nullptr
? &defaultProps
: static_cast<const TextProps*>(prevProps);
BaseTextProps::appendTextAttributesProps(result, oldProps);
return result;
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/attributedstring/TextAttributes.h>
#include <react/renderer/components/text/BaseTextProps.h>
#include <react/renderer/core/Props.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/graphics/Color.h>
namespace facebook::react {
class TextProps : public Props, public BaseTextProps {
public:
TextProps() = default;
TextProps(const PropsParserContext &context, const TextProps &sourceProps, const RawProps &rawProps);
void
setProp(const PropsParserContext &context, RawPropsPropNameHash hash, const char *propName, const RawValue &value);
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugProps() const override;
#endif
#ifdef RN_SERIALIZABLE_STATE
ComponentName getDiffPropsImplementationTarget() const override;
folly::dynamic getDiffProps(const Props *prevProps) const override;
#endif
};
} // namespace facebook::react

View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "TextShadowNode.h"
namespace facebook::react {
extern const char TextComponentName[] = "Text";
} // namespace facebook::react

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <limits>
#include <react/renderer/components/text/BaseTextShadowNode.h>
#include <react/renderer/components/text/TextProps.h>
#include <react/renderer/components/view/ViewEventEmitter.h>
#include <react/renderer/core/ConcreteShadowNode.h>
namespace facebook::react {
extern const char TextComponentName[];
using TextEventEmitter = TouchEventEmitter;
class TextShadowNode : public ConcreteShadowNode<TextComponentName, ShadowNode, TextProps, TextEventEmitter>,
public BaseTextShadowNode {
public:
static ShadowNodeTraits BaseTraits()
{
auto traits = ConcreteShadowNode::BaseTraits();
#ifdef ANDROID
traits.set(ShadowNodeTraits::Trait::FormsView);
#endif
return traits;
}
using ConcreteShadowNode::ConcreteShadowNode;
#ifdef ANDROID
using BaseShadowNode = ConcreteShadowNode<TextComponentName, ShadowNode, TextProps, TextEventEmitter>;
TextShadowNode(const ShadowNodeFragment &fragment, const ShadowNodeFamily::Shared &family, ShadowNodeTraits traits)
: BaseShadowNode(fragment, family, traits), BaseTextShadowNode()
{
orderIndex_ = std::numeric_limits<decltype(orderIndex_)>::max();
}
#endif
};
} // namespace facebook::react

View File

@@ -0,0 +1,198 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "HostPlatformParagraphProps.h"
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/attributedstring/conversions.h>
#include <react/renderer/attributedstring/primitives.h>
#include <react/renderer/components/text/conversions.h>
#include <react/renderer/core/graphicsConversions.h>
#include <react/renderer/core/propsConversions.h>
#include <react/renderer/debug/debugStringConvertibleUtils.h>
#include <glog/logging.h>
namespace facebook::react {
HostPlatformParagraphProps::HostPlatformParagraphProps(
const PropsParserContext& context,
const HostPlatformParagraphProps& sourceProps,
const RawProps& rawProps)
: BaseParagraphProps(context, sourceProps, rawProps),
disabled(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.disabled
: convertRawProp(
context,
rawProps,
"disabled",
sourceProps.disabled,
false)),
selectionColor(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.selectionColor
: convertRawProp(
context,
rawProps,
"selectionColor",
sourceProps.selectionColor,
{})),
dataDetectorType(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.dataDetectorType
: convertRawProp(
context,
rawProps,
"dataDetectorType",
sourceProps.dataDetectorType,
{}))
{};
void HostPlatformParagraphProps::setProp(
const PropsParserContext& context,
RawPropsPropNameHash hash,
const char* propName,
const RawValue& value) {
// All Props structs setProp methods must always, unconditionally,
// call all super::setProp methods, since multiple structs may
// reuse the same values.
BaseParagraphProps::setProp(context, hash, propName, value);
static auto defaults = HostPlatformParagraphProps{};
switch (hash) {
RAW_SET_PROP_SWITCH_CASE_BASIC(disabled);
RAW_SET_PROP_SWITCH_CASE_BASIC(selectionColor);
RAW_SET_PROP_SWITCH_CASE_BASIC(dataDetectorType);
}
}
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList HostPlatformParagraphProps::getDebugProps()
const {
return BaseParagraphProps::getDebugProps() +
SharedDebugStringConvertibleList{
debugStringConvertibleItem("disabled", disabled),
debugStringConvertibleItem("selectionColor", selectionColor),
debugStringConvertibleItem("dataDetectorType", dataDetectorType)};
}
#endif
#ifdef RN_SERIALIZABLE_STATE
ComponentName HostPlatformParagraphProps::getDiffPropsImplementationTarget()
const {
return "Paragraph";
}
folly::dynamic HostPlatformParagraphProps::getDiffProps(
const Props* prevProps) const {
static const auto defaultProps = HostPlatformParagraphProps();
const HostPlatformParagraphProps* oldProps = prevProps == nullptr
? &defaultProps
: static_cast<const HostPlatformParagraphProps*>(prevProps);
folly::dynamic result = ViewProps::getDiffProps(oldProps);
BaseTextProps::appendTextAttributesProps(result, oldProps);
if (paragraphAttributes.maximumNumberOfLines !=
oldProps->paragraphAttributes.maximumNumberOfLines) {
result["numberOfLines"] = paragraphAttributes.maximumNumberOfLines;
}
if (paragraphAttributes.ellipsizeMode !=
oldProps->paragraphAttributes.ellipsizeMode) {
result["ellipsizeMode"] = toString(paragraphAttributes.ellipsizeMode);
}
if (paragraphAttributes.textBreakStrategy !=
oldProps->paragraphAttributes.textBreakStrategy) {
result["textBreakStrategy"] =
toString(paragraphAttributes.textBreakStrategy);
}
if (paragraphAttributes.adjustsFontSizeToFit !=
oldProps->paragraphAttributes.adjustsFontSizeToFit) {
result["adjustsFontSizeToFit"] = paragraphAttributes.adjustsFontSizeToFit;
}
if (!floatEquality(
paragraphAttributes.minimumFontScale,
oldProps->paragraphAttributes.minimumFontScale)) {
result["minimumFontScale"] = paragraphAttributes.minimumFontScale;
}
if (!floatEquality(
paragraphAttributes.minimumFontSize,
oldProps->paragraphAttributes.minimumFontSize)) {
result["minimumFontSize"] = paragraphAttributes.minimumFontSize;
}
if (!floatEquality(
paragraphAttributes.maximumFontSize,
oldProps->paragraphAttributes.maximumFontSize)) {
result["maximumFontSize"] = paragraphAttributes.maximumFontSize;
}
if (paragraphAttributes.includeFontPadding !=
oldProps->paragraphAttributes.includeFontPadding) {
result["includeFontPadding"] = paragraphAttributes.includeFontPadding;
}
if (paragraphAttributes.android_hyphenationFrequency !=
oldProps->paragraphAttributes.android_hyphenationFrequency) {
result["android_hyphenationFrequency"] =
toString(paragraphAttributes.android_hyphenationFrequency);
}
if (paragraphAttributes.textAlignVertical !=
oldProps->paragraphAttributes.textAlignVertical) {
result["textAlignVertical"] =
paragraphAttributes.textAlignVertical.has_value()
? toString(paragraphAttributes.textAlignVertical.value())
: nullptr;
}
if (isSelectable != oldProps->isSelectable) {
result["selectable"] = isSelectable;
}
if (onTextLayout != oldProps->onTextLayout) {
result["onTextLayout"] = onTextLayout;
}
if (disabled != oldProps->disabled) {
result["disabled"] = disabled;
}
if (selectionColor != oldProps->selectionColor) {
if (selectionColor.has_value()) {
result["selectionColor"] = *selectionColor.value();
} else {
result["selectionColor"] = folly::dynamic(nullptr);
}
}
if (dataDetectorType != oldProps->dataDetectorType) {
if (dataDetectorType.has_value()) {
result["dataDetectorType"] = toString(dataDetectorType.value());
} else {
result["dataDetectorType"] = folly::dynamic(nullptr);
}
}
return result;
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <limits>
#include <memory>
#include <optional>
#include <react/renderer/components/text/BaseParagraphProps.h>
#include <react/renderer/components/text/primitives.h>
#include <react/renderer/core/Props.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/graphics/Color.h>
namespace facebook::react {
/*
* Props of <Paragraph> component.
* Most of the props are directly stored in composed `ParagraphAttributes`
* object.
*/
class HostPlatformParagraphProps : public BaseParagraphProps {
public:
HostPlatformParagraphProps() = default;
HostPlatformParagraphProps(
const PropsParserContext &context,
const HostPlatformParagraphProps &sourceProps,
const RawProps &rawProps);
void
setProp(const PropsParserContext &context, RawPropsPropNameHash hash, const char *propName, const RawValue &value);
#pragma mark - Props
bool disabled{false};
std::optional<SharedColor> selectionColor{};
std::optional<DataDetectorType> dataDetectorType{};
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugProps() const override;
#endif
#ifdef RN_SERIALIZABLE_STATE
ComponentName getDiffPropsImplementationTarget() const override;
folly::dynamic getDiffProps(const Props *prevProps) const override;
#endif
};
} // namespace facebook::react

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ParagraphState.h"
#include <react/renderer/components/text/stateConversions.h>
#include <react/renderer/core/ConcreteState.h>
#include <react/renderer/debug/debugStringConvertibleUtils.h>
namespace facebook::react {
static_assert(StateDataWithMapBuffer<ParagraphState>);
static_assert(StateDataWithJNIReference<ParagraphState>);
folly::dynamic ParagraphState::getDynamic() const {
LOG(FATAL) << "ParagraphState may only be serialized to MapBuffer";
}
MapBuffer ParagraphState::getMapBuffer() const {
return toMapBuffer(*this);
}
jni::local_ref<jobject> ParagraphState::getJNIReference() const {
return jni::make_local(measuredLayout.preparedLayout.get());
}
} // namespace facebook::react

View File

@@ -0,0 +1,91 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/debug/react_native_assert.h>
#include <react/renderer/attributedstring/AttributedString.h>
#include <react/renderer/attributedstring/ParagraphAttributes.h>
#include <react/renderer/textlayoutmanager/TextLayoutManager.h>
#include <react/renderer/textlayoutmanager/TextLayoutManagerExtended.h>
#include <fbjni/fbjni.h>
#include <folly/dynamic.h>
#include <react/renderer/mapbuffer/MapBuffer.h>
#include <utility>
namespace facebook::react {
// constants for Text State serialization
constexpr static MapBuffer::Key TX_STATE_KEY_ATTRIBUTED_STRING = 0;
constexpr static MapBuffer::Key TX_STATE_KEY_PARAGRAPH_ATTRIBUTES = 1;
// Used for TextInput only
constexpr static MapBuffer::Key TX_STATE_KEY_HASH = 2;
constexpr static MapBuffer::Key TX_STATE_KEY_MOST_RECENT_EVENT_COUNT = 3;
/*
* State for <Paragraph> component.
* Represents what to render and how to render.
*/
class ParagraphState final {
public:
/*
* All content of <Paragraph> component represented as an `AttributedString`.
*/
AttributedString attributedString;
/*
* Represents all visual attributes of a paragraph of text represented as
* a ParagraphAttributes.
*/
ParagraphAttributes paragraphAttributes;
/*
* `TextLayoutManager` provides a connection to platform-specific
* text rendering infrastructure which is capable to render the
* `AttributedString`.
*/
std::weak_ptr<const TextLayoutManager> layoutManager;
/**
* A fully prepared representation of a text layout to mount
*/
MeasuredPreparedLayout measuredLayout;
ParagraphState(
AttributedString attributedString,
ParagraphAttributes paragraphAttributes,
std::weak_ptr<const TextLayoutManager> layoutManager,
MeasuredPreparedLayout measuredLayout)
: attributedString(std::move(attributedString)),
paragraphAttributes(std::move(paragraphAttributes)),
layoutManager(std::move(layoutManager)),
measuredLayout(std::move(measuredLayout))
{
}
ParagraphState(
AttributedString attributedString,
ParagraphAttributes paragraphAttributes,
std::weak_ptr<const TextLayoutManager> layoutManager)
: ParagraphState(std::move(attributedString), std::move(paragraphAttributes), std::move(layoutManager), {})
{
}
ParagraphState() = default;
ParagraphState(const ParagraphState & /*previousState*/, const folly::dynamic & /*data*/)
{
react_native_assert(false && "Not supported");
};
folly::dynamic getDynamic() const;
MapBuffer getMapBuffer() const;
jni::local_ref<jobject> getJNIReference() const;
};
} // namespace facebook::react

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/components/text/primitives.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/core/RawValue.h>
#include <string>
namespace facebook::react {
inline void fromRawValue(const PropsParserContext & /*context*/, const RawValue &value, DataDetectorType &result)
{
auto string = static_cast<std::string>(value);
if (string == "all") {
result = DataDetectorType::All;
} else if (string == "email") {
result = DataDetectorType::Email;
} else if (string == "link") {
result = DataDetectorType::Link;
} else if (string == "none") {
result = DataDetectorType::None;
} else if (string == "phoneNumber") {
result = DataDetectorType::PhoneNumber;
} else {
abort();
}
}
inline std::string toString(const DataDetectorType &value)
{
switch (value) {
case DataDetectorType::All:
return "all";
case DataDetectorType::Email:
return "email";
case DataDetectorType::Link:
return "link";
case DataDetectorType::None:
return "none";
case DataDetectorType::PhoneNumber:
return "phoneNumber";
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
namespace facebook::react {
enum class DataDetectorType {
All,
Email,
Link,
None,
PhoneNumber,
};
} // namespace facebook::react

View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/components/text/BaseParagraphProps.h>
namespace facebook::react {
using HostPlatformParagraphProps = BaseParagraphProps;
} // namespace facebook::react

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/debug/react_native_assert.h>
#include <react/renderer/attributedstring/AttributedString.h>
#include <react/renderer/attributedstring/ParagraphAttributes.h>
#include <react/renderer/textlayoutmanager/TextLayoutManager.h>
namespace facebook::react {
/*
* State for <Paragraph> component.
* Represents what to render and how to render.
*/
class ParagraphState final {
public:
/*
* All content of <Paragraph> component represented as an `AttributedString`.
*/
AttributedString attributedString;
/*
* Represents all visual attributes of a paragraph of text represented as
* a ParagraphAttributes.
*/
ParagraphAttributes paragraphAttributes;
/*
* `TextLayoutManager` provides a connection to platform-specific
* text rendering infrastructure which is capable to render the
* `AttributedString`.
*/
std::weak_ptr<const TextLayoutManager> layoutManager;
};
} // namespace facebook::react

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <folly/dynamic.h>
#include <react/renderer/attributedstring/conversions.h>
#include <react/renderer/components/text/ParagraphState.h>
#ifdef RN_SERIALIZABLE_STATE
#include <react/renderer/mapbuffer/MapBuffer.h>
#include <react/renderer/mapbuffer/MapBufferBuilder.h>
#endif
namespace facebook::react {
#ifdef RN_SERIALIZABLE_STATE
inline MapBuffer toMapBuffer(const ParagraphState &paragraphState)
{
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);
builder.putInt(TX_STATE_KEY_HASH, attStringMapBuffer.getInt(AS_KEY_HASH));
return builder.build();
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,95 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/element/ComponentBuilder.h>
#include <gtest/gtest.h>
#include <react/renderer/element/Element.h>
#include <react/renderer/element/testUtils.h>
namespace facebook::react {
namespace {
Element<RawTextShadowNode> rawTextElement(const char* text) {
auto rawTextProps = std::make_shared<RawTextProps>();
rawTextProps->text = text;
return Element<RawTextShadowNode>().props(rawTextProps);
}
} // namespace
TEST(BaseTextShadowNodeTest, fragmentsWithDifferentAttributes) {
ContextContainer contextContainer{};
PropsParserContext parserContext{-1, contextContainer};
auto builder = simpleComponentBuilder();
auto shadowNode = builder.build(
Element<ParagraphShadowNode>().children({
Element<TextShadowNode>()
.props([]() {
auto props = std::make_shared<TextProps>();
props->textAttributes.fontSize = 12;
return props;
})
.children({
rawTextElement("First fragment. "),
}),
Element<TextShadowNode>()
.props([]() {
auto props = std::make_shared<TextProps>();
props->textAttributes.fontSize = 24;
return props;
})
.children({
rawTextElement("Second fragment"),
}),
}));
auto baseTextAttributes = TextAttributes::defaultTextAttributes();
AttributedString output;
BaseTextShadowNode::Attachments attachments;
BaseTextShadowNode::buildAttributedString(
baseTextAttributes, *shadowNode, output, attachments);
EXPECT_EQ(output.getString(), "First fragment. Second fragment");
const auto& fragments = output.getFragments();
EXPECT_EQ(fragments.size(), 2);
EXPECT_EQ(fragments[0].textAttributes.fontSize, 12);
EXPECT_EQ(
fragments[0].parentShadowView.tag,
shadowNode->getChildren()[0]->getTag());
EXPECT_EQ(fragments[1].textAttributes.fontSize, 24);
EXPECT_EQ(
fragments[1].parentShadowView.tag,
shadowNode->getChildren()[1]->getTag());
}
TEST(BaseTextShadowNodeTest, rawTextIsMerged) {
ContextContainer contextContainer{};
PropsParserContext parserContext{-1, contextContainer};
auto builder = simpleComponentBuilder();
auto shadowNode = builder.build(
Element<TextShadowNode>().children({
rawTextElement("Hello "),
rawTextElement("World"),
}));
auto baseTextAttributes = TextAttributes::defaultTextAttributes();
AttributedString output;
BaseTextShadowNode::Attachments attachments;
BaseTextShadowNode::buildAttributedString(
baseTextAttributes, *shadowNode, output, attachments);
EXPECT_EQ(output.getString(), "Hello World");
EXPECT_EQ(output.getFragments().size(), 1);
}
} // namespace facebook::react