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,152 @@
/*
* 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 "AttributedString.h"
#include <react/renderer/debug/DebugStringConvertibleItem.h>
namespace facebook::react {
using Fragment = AttributedString::Fragment;
using Fragments = AttributedString::Fragments;
#pragma mark - Fragment
std::string Fragment::AttachmentCharacter() {
// C++20 makes char8_t a distinct type from char, and u8 string literals
// consist of char8_t instead of char, which in turn requires std::u8string,
// etc. Here we were assuming char was UTF-8 anyway, so just cast to that
// (which is valid because char* is allowed to alias anything).
return reinterpret_cast<const char*>(
u8"\uFFFC"); // Unicode `OBJECT REPLACEMENT CHARACTER`
}
bool Fragment::isAttachment() const {
return string == AttachmentCharacter();
}
bool Fragment::operator==(const Fragment& rhs) const {
return std::tie(
string,
textAttributes,
parentShadowView.tag,
parentShadowView.layoutMetrics) ==
std::tie(
rhs.string,
rhs.textAttributes,
rhs.parentShadowView.tag,
rhs.parentShadowView.layoutMetrics);
}
bool Fragment::isContentEqual(const Fragment& rhs) const {
return std::tie(string, textAttributes) ==
std::tie(rhs.string, rhs.textAttributes);
}
#pragma mark - AttributedString
void AttributedString::appendFragment(Fragment&& fragment) {
ensureUnsealed();
if (!fragment.string.empty()) {
fragments_.push_back(std::move(fragment));
}
}
void AttributedString::prependFragment(Fragment&& fragment) {
ensureUnsealed();
if (!fragment.string.empty()) {
fragments_.insert(fragments_.begin(), std::move(fragment));
}
}
void AttributedString::setBaseTextAttributes(
const TextAttributes& defaultAttributes) {
baseAttributes_ = defaultAttributes;
}
const Fragments& AttributedString::getFragments() const {
return fragments_;
}
Fragments& AttributedString::getFragments() {
return fragments_;
}
std::string AttributedString::getString() const {
auto string = std::string{};
for (const auto& fragment : fragments_) {
string += fragment.string;
}
return string;
}
const TextAttributes& AttributedString::getBaseTextAttributes() const {
return baseAttributes_;
}
bool AttributedString::isEmpty() const {
return fragments_.empty();
}
bool AttributedString::compareTextAttributesWithoutFrame(
const AttributedString& rhs) const {
if (fragments_.size() != rhs.fragments_.size()) {
return false;
}
for (size_t i = 0; i < fragments_.size(); i++) {
if (fragments_[i].textAttributes != rhs.fragments_[i].textAttributes ||
fragments_[i].string != rhs.fragments_[i].string) {
return false;
}
}
return true;
}
bool AttributedString::operator==(const AttributedString& rhs) const {
return std::tie(fragments_, baseAttributes_) ==
std::tie(rhs.fragments_, rhs.baseAttributes_);
}
bool AttributedString::isContentEqual(const AttributedString& rhs) const {
if (fragments_.size() != rhs.fragments_.size()) {
return false;
}
for (size_t i = 0; i < fragments_.size(); i++) {
if (!fragments_[i].isContentEqual(rhs.fragments_[i])) {
return false;
}
}
return true;
}
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList AttributedString::getDebugChildren() const {
auto list = SharedDebugStringConvertibleList{};
for (auto&& fragment : fragments_) {
auto propsList =
fragment.textAttributes.DebugStringConvertible::getDebugProps();
list.push_back(
std::make_shared<DebugStringConvertibleItem>(
"Fragment",
fragment.string,
SharedDebugStringConvertibleList(),
propsList));
}
return list;
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,141 @@
/*
* 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/Sealable.h>
#include <react/renderer/core/ShadowNode.h>
#include <react/renderer/debug/DebugStringConvertible.h>
#include <react/renderer/mounting/ShadowView.h>
#include <react/utils/hash_combine.h>
namespace facebook::react {
/*
* Simple, cross-platform, React-specific implementation of attributed string
* (aka spanned string).
* `AttributedString` is basically a list of `Fragments` which have `string` and
* `textAttributes` + `shadowNode` associated with the `string`.
*/
class AttributedString : public Sealable, public DebugStringConvertible {
public:
class Fragment {
public:
static std::string AttachmentCharacter();
std::string string;
TextAttributes textAttributes;
ShadowView parentShadowView;
/*
* Returns true is the Fragment represents an attachment.
* Equivalent to `string == AttachmentCharacter()`.
*/
bool isAttachment() const;
/*
* Returns whether the underlying text and attributes are equal,
* disregarding layout or other information.
*/
bool isContentEqual(const Fragment &rhs) const;
bool operator==(const Fragment &rhs) const;
};
class Range {
public:
int location{0};
int length{0};
};
using Fragments = std::vector<Fragment>;
/*
* Appends and prepends a `fragment` to the string.
*/
void appendFragment(Fragment &&fragment);
void prependFragment(Fragment &&fragment);
/*
* Sets attributes which would apply to hypothetical text not included in the
* AttributedString.
*/
void setBaseTextAttributes(const TextAttributes &defaultAttributes);
/*
* Returns a read-only reference to a list of fragments.
*/
const Fragments &getFragments() const;
/*
* Returns a reference to a list of fragments.
*/
Fragments &getFragments();
/*
* Returns a string constructed from all strings in all fragments.
*/
std::string getString() const;
const TextAttributes &getBaseTextAttributes() const;
/*
* Returns `true` if the string is empty (has no any fragments).
*/
bool isEmpty() const;
/**
* Compares equality of TextAttributes of all Fragments on both sides.
*/
bool compareTextAttributesWithoutFrame(const AttributedString &rhs) const;
bool isContentEqual(const AttributedString &rhs) const;
bool operator==(const AttributedString &rhs) const;
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugChildren() const override;
#endif
private:
Fragments fragments_;
TextAttributes baseAttributes_;
};
} // namespace facebook::react
namespace std {
template <>
struct hash<facebook::react::AttributedString::Fragment> {
size_t operator()(const facebook::react::AttributedString::Fragment &fragment) const
{
return facebook::react::hash_combine(
fragment.string,
fragment.textAttributes,
fragment.parentShadowView.tag,
fragment.parentShadowView.layoutMetrics);
}
};
template <>
struct hash<facebook::react::AttributedString> {
size_t operator()(const facebook::react::AttributedString &attributedString) const
{
auto seed = size_t{0};
facebook::react::hash_combine(seed, attributedString.getBaseTextAttributes());
for (const auto &fragment : attributedString.getFragments()) {
facebook::react::hash_combine(seed, fragment);
}
return seed;
}
};
} // namespace std

View File

@@ -0,0 +1,88 @@
/*
* 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 "AttributedStringBox.h"
#include <react/debug/react_native_assert.h>
#include <utility>
namespace facebook::react {
AttributedStringBox::AttributedStringBox()
: mode_(Mode::Value),
value_(std::make_shared<const AttributedString>(AttributedString{})),
opaquePointer_({}) {};
AttributedStringBox::AttributedStringBox(const AttributedString& value)
: mode_(Mode::Value),
value_(std::make_shared<const AttributedString>(value)),
opaquePointer_({}) {};
AttributedStringBox::AttributedStringBox(std::shared_ptr<void> opaquePointer)
: mode_(Mode::OpaquePointer),
value_({}),
opaquePointer_(std::move(opaquePointer)) {}
AttributedStringBox::AttributedStringBox(AttributedStringBox&& other) noexcept
: mode_(other.mode_),
value_(std::move(other.value_)),
opaquePointer_(std::move(other.opaquePointer_)) {
other.mode_ = AttributedStringBox::Mode::Value;
other.value_ = std::make_shared<const AttributedString>(AttributedString{});
}
AttributedStringBox::Mode AttributedStringBox::getMode() const {
return mode_;
}
const AttributedString& AttributedStringBox::getValue() const {
react_native_assert(mode_ == AttributedStringBox::Mode::Value);
react_native_assert(value_);
return *value_;
}
std::shared_ptr<void> AttributedStringBox::getOpaquePointer() const {
react_native_assert(mode_ == AttributedStringBox::Mode::OpaquePointer);
react_native_assert(opaquePointer_);
return opaquePointer_;
}
AttributedStringBox& AttributedStringBox::operator=(
AttributedStringBox&& other) noexcept {
if (this != &other) {
mode_ = other.mode_;
value_ = std::move(other.value_);
opaquePointer_ = std::move(other.opaquePointer_);
other.mode_ = AttributedStringBox::Mode::Value;
other.value_ = std::make_shared<const AttributedString>(AttributedString{});
}
return *this;
}
bool operator==(
const AttributedStringBox& lhs,
const AttributedStringBox& rhs) {
if (lhs.getMode() != rhs.getMode()) {
return false;
}
switch (lhs.getMode()) {
case AttributedStringBox::Mode::Value:
return lhs.getValue() == rhs.getValue();
case AttributedStringBox::Mode::OpaquePointer:
return lhs.getOpaquePointer() == rhs.getOpaquePointer();
}
}
bool operator!=(
const AttributedStringBox& lhs,
const AttributedStringBox& rhs) {
return !(lhs == rhs);
}
} // namespace facebook::react

View File

@@ -0,0 +1,76 @@
/*
* 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/attributedstring/AttributedString.h>
namespace facebook::react {
/*
* Represents an object storing a shared `AttributedString` or a shared pointer
* to some opaque platform-specific object that can be used as an attributed
* string. The class serves two main purposes:
* - Represent type-erased attributed string entity (which can be
* platform-specific or platform-independent);
* - Represent a container that can be copied with constant complexity.
*/
class AttributedStringBox final {
public:
enum class Mode { Value, OpaquePointer };
/*
* Default constructor constructs an empty string.
*/
AttributedStringBox();
/*
* Custom explicit constructors.
*/
explicit AttributedStringBox(const AttributedString &value);
explicit AttributedStringBox(std::shared_ptr<void> opaquePointer);
/*
* Movable, Copyable, Assignable.
*/
AttributedStringBox(const AttributedStringBox &other) = default;
AttributedStringBox(AttributedStringBox &&other) noexcept;
AttributedStringBox &operator=(const AttributedStringBox &other) = default;
AttributedStringBox &operator=(AttributedStringBox &&other) noexcept;
/*
* Getters.
*/
Mode getMode() const;
const AttributedString &getValue() const;
std::shared_ptr<void> getOpaquePointer() const;
private:
Mode mode_;
std::shared_ptr<const AttributedString> value_;
std::shared_ptr<void> opaquePointer_;
};
bool operator==(const AttributedStringBox &lhs, const AttributedStringBox &rhs);
bool operator!=(const AttributedStringBox &lhs, const AttributedStringBox &rhs);
} // namespace facebook::react
template <>
struct std::hash<facebook::react::AttributedStringBox> {
size_t operator()(const facebook::react::AttributedStringBox &attributedStringBox) const
{
switch (attributedStringBox.getMode()) {
case facebook::react::AttributedStringBox::Mode::Value:
return std::hash<facebook::react::AttributedString>()(attributedStringBox.getValue());
case facebook::react::AttributedStringBox::Mode::OpaquePointer:
return std::hash<std::shared_ptr<void>>()(attributedStringBox.getOpaquePointer());
}
}
};

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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB react_renderer_attributedstring_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_renderer_attributedstring OBJECT ${react_renderer_attributedstring_SRC})
target_include_directories(react_renderer_attributedstring PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_renderer_attributedstring
folly_runtime
glog
glog_init
react_debug
rrc_view
react_renderer_core
react_renderer_debug
react_renderer_graphics
react_renderer_mapbuffer
react_utils
rrc_view
yoga
)
target_compile_reactnative_options(react_renderer_attributedstring PRIVATE)
target_compile_options(react_renderer_attributedstring PRIVATE -Wpedantic)

View File

@@ -0,0 +1,86 @@
/*
* 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 "ParagraphAttributes.h"
#include <react/renderer/attributedstring/conversions.h>
#include <react/renderer/core/graphicsConversions.h>
#include <react/renderer/debug/debugStringConvertibleUtils.h>
#include <react/utils/FloatComparison.h>
namespace facebook::react {
bool ParagraphAttributes::operator==(const ParagraphAttributes& rhs) const {
return std::tie(
maximumNumberOfLines,
ellipsizeMode,
textBreakStrategy,
adjustsFontSizeToFit,
includeFontPadding,
android_hyphenationFrequency,
textAlignVertical) ==
std::tie(
rhs.maximumNumberOfLines,
rhs.ellipsizeMode,
rhs.textBreakStrategy,
rhs.adjustsFontSizeToFit,
rhs.includeFontPadding,
rhs.android_hyphenationFrequency,
rhs.textAlignVertical) &&
floatEquality(minimumFontSize, rhs.minimumFontSize) &&
floatEquality(maximumFontSize, rhs.maximumFontSize) &&
floatEquality(minimumFontScale, rhs.minimumFontScale);
}
bool ParagraphAttributes::operator!=(const ParagraphAttributes& rhs) const {
return !(*this == rhs);
}
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList ParagraphAttributes::getDebugProps() const {
ParagraphAttributes paragraphAttributes{};
return {
debugStringConvertibleItem(
"maximumNumberOfLines",
maximumNumberOfLines,
paragraphAttributes.maximumNumberOfLines),
debugStringConvertibleItem(
"ellipsizeMode", ellipsizeMode, paragraphAttributes.ellipsizeMode),
debugStringConvertibleItem(
"textBreakStrategy",
textBreakStrategy,
paragraphAttributes.textBreakStrategy),
debugStringConvertibleItem(
"adjustsFontSizeToFit",
adjustsFontSizeToFit,
paragraphAttributes.adjustsFontSizeToFit),
debugStringConvertibleItem(
"minimumFontSize",
minimumFontSize,
paragraphAttributes.minimumFontSize),
debugStringConvertibleItem(
"maximumFontSize",
maximumFontSize,
paragraphAttributes.maximumFontSize),
debugStringConvertibleItem(
"includeFontPadding",
includeFontPadding,
paragraphAttributes.includeFontPadding),
debugStringConvertibleItem(
"android_hyphenationFrequency",
android_hyphenationFrequency,
paragraphAttributes.android_hyphenationFrequency),
debugStringConvertibleItem(
"textAlignVertical",
textAlignVertical,
paragraphAttributes.textAlignVertical)};
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,116 @@
/*
* 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/attributedstring/primitives.h>
#include <react/renderer/debug/DebugStringConvertible.h>
#include <react/renderer/graphics/Float.h>
#include <react/utils/hash_combine.h>
namespace facebook::react {
class ParagraphAttributes;
using SharedParagraphAttributes = std::shared_ptr<const ParagraphAttributes>;
/*
* Represents all visual attributes of a paragraph of text.
* Two data structures, ParagraphAttributes and AttributedText, should be
* enough to define visual representation of a piece of text on the screen.
*/
class ParagraphAttributes : public DebugStringConvertible {
public:
#pragma mark - Fields
/*
* Maximum number of lines which paragraph can take.
* Zero value represents "no limit".
*/
int maximumNumberOfLines{};
/*
* In case if a text cannot fit given boundaries, defines a place where
* an ellipsize should be placed.
*/
EllipsizeMode ellipsizeMode{};
/*
* (Android only) Break strategy for breaking paragraphs into lines.
*/
TextBreakStrategy textBreakStrategy{TextBreakStrategy::HighQuality};
/*
* Enables font size adjustment to fit constrained boundaries.
*/
bool adjustsFontSizeToFit{};
/*
* (Android only) Leaves enough room for ascenders and descenders instead of
* using the font ascent and descent strictly.
*/
bool includeFontPadding{true};
/*
* (Android only) Frequency of automatic hyphenation to use when determining
* word breaks.
*/
HyphenationFrequency android_hyphenationFrequency{};
/*
* In case of font size adjustment enabled, defines minimum and maximum
* font sizes.
*/
Float minimumFontSize{std::numeric_limits<Float>::quiet_NaN()};
Float maximumFontSize{std::numeric_limits<Float>::quiet_NaN()};
/*
* Specifies the smallest possible scale a font can reach when
* adjustsFontSizeToFit is enabled. (values 0.01-1.0).
*/
Float minimumFontScale{std::numeric_limits<Float>::quiet_NaN()};
/*
* The vertical alignment of the text, causing the glyphs to be vertically
* aligned in its container.
*/
std::optional<TextAlignmentVertical> textAlignVertical{};
bool operator==(const ParagraphAttributes &rhs) const;
bool operator!=(const ParagraphAttributes &rhs) const;
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugProps() const override;
#endif
};
} // namespace facebook::react
namespace std {
template <>
struct hash<facebook::react::ParagraphAttributes> {
size_t operator()(const facebook::react::ParagraphAttributes &attributes) const
{
return facebook::react::hash_combine(
attributes.maximumNumberOfLines,
attributes.ellipsizeMode,
attributes.textBreakStrategy,
attributes.adjustsFontSizeToFit,
attributes.minimumFontSize,
attributes.maximumFontSize,
attributes.includeFontPadding,
attributes.android_hyphenationFrequency,
attributes.minimumFontScale,
attributes.textAlignVertical);
}
};
} // namespace std

View File

@@ -0,0 +1,32 @@
/*
* 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>
namespace facebook::react {
/**
* Prior to D63303709 AttributedString could not represent formatting on an
* empty string, and so some text content was forcefully added to empty strings
* during measurement. Usages of this function should be replaced with
* formatting based off of baseTextAttributes.
*/
inline AttributedString ensurePlaceholderIfEmpty_DO_NOT_USE(const AttributedString &attributedString)
{
if (attributedString.isEmpty()) {
AttributedString placeholder{attributedString};
placeholder.appendFragment(
{.string = "I", .textAttributes = attributedString.getBaseTextAttributes(), .parentShadowView = {}});
return placeholder;
}
return attributedString;
}
} // namespace facebook::react

View File

@@ -0,0 +1,288 @@
/*
* 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 "TextAttributes.h"
#include <react/renderer/attributedstring/conversions.h>
#include <react/renderer/core/conversions.h>
#include <react/renderer/core/graphicsConversions.h>
#include <react/utils/FloatComparison.h>
#include <cmath>
#include <react/renderer/debug/debugStringConvertibleUtils.h>
namespace facebook::react {
void TextAttributes::apply(TextAttributes textAttributes) {
// Color
foregroundColor = textAttributes.foregroundColor
? textAttributes.foregroundColor
: foregroundColor;
backgroundColor = textAttributes.backgroundColor
? textAttributes.backgroundColor
: backgroundColor;
opacity =
!std::isnan(textAttributes.opacity) ? textAttributes.opacity : opacity;
// Font
fontFamily = !textAttributes.fontFamily.empty() ? textAttributes.fontFamily
: fontFamily;
fontSize =
!std::isnan(textAttributes.fontSize) ? textAttributes.fontSize : fontSize;
fontSizeMultiplier = !std::isnan(textAttributes.fontSizeMultiplier)
? textAttributes.fontSizeMultiplier
: fontSizeMultiplier;
fontWeight = textAttributes.fontWeight.has_value() ? textAttributes.fontWeight
: fontWeight;
fontStyle = textAttributes.fontStyle.has_value() ? textAttributes.fontStyle
: fontStyle;
fontVariant = textAttributes.fontVariant.has_value()
? textAttributes.fontVariant
: fontVariant;
allowFontScaling = textAttributes.allowFontScaling.has_value()
? textAttributes.allowFontScaling
: allowFontScaling;
maxFontSizeMultiplier = !std::isnan(textAttributes.maxFontSizeMultiplier)
? textAttributes.maxFontSizeMultiplier
: maxFontSizeMultiplier;
dynamicTypeRamp = textAttributes.dynamicTypeRamp.has_value()
? textAttributes.dynamicTypeRamp
: dynamicTypeRamp;
letterSpacing = !std::isnan(textAttributes.letterSpacing)
? textAttributes.letterSpacing
: letterSpacing;
textTransform = textAttributes.textTransform.has_value()
? textAttributes.textTransform
: textTransform;
// Paragraph Styles
lineHeight = !std::isnan(textAttributes.lineHeight)
? textAttributes.lineHeight
: lineHeight;
alignment = textAttributes.alignment.has_value() ? textAttributes.alignment
: alignment;
baseWritingDirection = textAttributes.baseWritingDirection.has_value()
? textAttributes.baseWritingDirection
: baseWritingDirection;
lineBreakStrategy = textAttributes.lineBreakStrategy.has_value()
? textAttributes.lineBreakStrategy
: lineBreakStrategy;
lineBreakMode = textAttributes.lineBreakMode.has_value()
? textAttributes.lineBreakMode
: lineBreakMode;
// Decoration
textDecorationColor = textAttributes.textDecorationColor
? textAttributes.textDecorationColor
: textDecorationColor;
textDecorationLineType = textAttributes.textDecorationLineType.has_value()
? textAttributes.textDecorationLineType
: textDecorationLineType;
textDecorationStyle = textAttributes.textDecorationStyle.has_value()
? textAttributes.textDecorationStyle
: textDecorationStyle;
// Shadow
textShadowOffset = textAttributes.textShadowOffset.has_value()
? textAttributes.textShadowOffset.value()
: textShadowOffset;
textShadowRadius = !std::isnan(textAttributes.textShadowRadius)
? textAttributes.textShadowRadius
: textShadowRadius;
textShadowColor = textAttributes.textShadowColor
? textAttributes.textShadowColor
: textShadowColor;
// Special
isHighlighted = textAttributes.isHighlighted.has_value()
? textAttributes.isHighlighted
: isHighlighted;
// TextAttributes "inherits" the isPressable value from ancestors, so this
// only applies the current node's value for isPressable if it is truthy.
isPressable =
textAttributes.isPressable.has_value() && *textAttributes.isPressable
? textAttributes.isPressable
: isPressable;
layoutDirection = textAttributes.layoutDirection.has_value()
? textAttributes.layoutDirection
: layoutDirection;
accessibilityRole = textAttributes.accessibilityRole.has_value()
? textAttributes.accessibilityRole
: accessibilityRole;
role = textAttributes.role.has_value() ? textAttributes.role : role;
}
#pragma mark - Operators
bool TextAttributes::operator==(const TextAttributes& rhs) const {
return std::tie(
foregroundColor,
backgroundColor,
fontFamily,
fontWeight,
fontStyle,
fontVariant,
allowFontScaling,
dynamicTypeRamp,
alignment,
baseWritingDirection,
lineBreakStrategy,
textDecorationColor,
textDecorationLineType,
textDecorationStyle,
textShadowOffset,
textShadowColor,
isHighlighted,
isPressable,
layoutDirection,
accessibilityRole,
role,
textTransform) ==
std::tie(
rhs.foregroundColor,
rhs.backgroundColor,
rhs.fontFamily,
rhs.fontWeight,
rhs.fontStyle,
rhs.fontVariant,
rhs.allowFontScaling,
rhs.dynamicTypeRamp,
rhs.alignment,
rhs.baseWritingDirection,
rhs.lineBreakStrategy,
rhs.textDecorationColor,
rhs.textDecorationLineType,
rhs.textDecorationStyle,
rhs.textShadowOffset,
rhs.textShadowColor,
rhs.isHighlighted,
rhs.isPressable,
rhs.layoutDirection,
rhs.accessibilityRole,
rhs.role,
rhs.textTransform) &&
floatEquality(maxFontSizeMultiplier, rhs.maxFontSizeMultiplier) &&
floatEquality(opacity, rhs.opacity) &&
floatEquality(fontSize, rhs.fontSize) &&
floatEquality(fontSizeMultiplier, rhs.fontSizeMultiplier) &&
floatEquality(letterSpacing, rhs.letterSpacing) &&
floatEquality(lineHeight, rhs.lineHeight) &&
floatEquality(textShadowRadius, rhs.textShadowRadius);
}
TextAttributes TextAttributes::defaultTextAttributes() {
static auto textAttributes = [] {
auto defaultAttrs = TextAttributes{};
// Non-obvious (can be different among platforms) default text attributes.
defaultAttrs.foregroundColor = blackColor();
defaultAttrs.backgroundColor = clearColor();
defaultAttrs.fontSize = 14.0;
defaultAttrs.fontSizeMultiplier = 1.0;
return defaultAttrs;
}();
return textAttributes;
}
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList TextAttributes::getDebugProps() const {
const auto& textAttributes = TextAttributes::defaultTextAttributes();
return {
// Color
debugStringConvertibleItem(
"backgroundColor", backgroundColor, textAttributes.backgroundColor),
debugStringConvertibleItem(
"foregroundColor", foregroundColor, textAttributes.foregroundColor),
debugStringConvertibleItem("opacity", opacity, textAttributes.opacity),
// Font
debugStringConvertibleItem(
"fontFamily", fontFamily, textAttributes.fontFamily),
debugStringConvertibleItem("fontSize", fontSize, textAttributes.fontSize),
debugStringConvertibleItem(
"fontSizeMultiplier",
fontSizeMultiplier,
textAttributes.fontSizeMultiplier),
debugStringConvertibleItem(
"fontWeight", fontWeight, textAttributes.fontWeight),
debugStringConvertibleItem(
"fontStyle", fontStyle, textAttributes.fontStyle),
debugStringConvertibleItem(
"fontVariant", fontVariant, textAttributes.fontVariant),
debugStringConvertibleItem(
"allowFontScaling",
allowFontScaling,
textAttributes.allowFontScaling),
debugStringConvertibleItem(
"maxFontSizeMultiplier",
maxFontSizeMultiplier,
textAttributes.maxFontSizeMultiplier),
debugStringConvertibleItem(
"dynamicTypeRamp", dynamicTypeRamp, textAttributes.dynamicTypeRamp),
debugStringConvertibleItem(
"letterSpacing", letterSpacing, textAttributes.letterSpacing),
// Paragraph Styles
debugStringConvertibleItem(
"lineHeight", lineHeight, textAttributes.lineHeight),
debugStringConvertibleItem(
"alignment", alignment, textAttributes.alignment),
debugStringConvertibleItem(
"writingDirection",
baseWritingDirection,
textAttributes.baseWritingDirection),
debugStringConvertibleItem(
"lineBreakStrategyIOS",
lineBreakStrategy,
textAttributes.lineBreakStrategy),
debugStringConvertibleItem(
"lineBreakModeIOS", lineBreakMode, textAttributes.lineBreakMode),
// Decoration
debugStringConvertibleItem(
"textDecorationColor",
textDecorationColor,
textAttributes.textDecorationColor),
debugStringConvertibleItem(
"textDecorationLineType",
textDecorationLineType,
textAttributes.textDecorationLineType),
debugStringConvertibleItem(
"textDecorationStyle",
textDecorationStyle,
textAttributes.textDecorationStyle),
// Shadow
debugStringConvertibleItem(
"textShadowOffset",
textShadowOffset,
textAttributes.textShadowOffset),
debugStringConvertibleItem(
"textShadowRadius",
textShadowRadius,
textAttributes.textShadowRadius),
debugStringConvertibleItem(
"textShadowColor", textShadowColor, textAttributes.textShadowColor),
// Special
debugStringConvertibleItem(
"isHighlighted", isHighlighted, textAttributes.isHighlighted),
debugStringConvertibleItem(
"isPressable", isPressable, textAttributes.isPressable),
debugStringConvertibleItem(
"layoutDirection", layoutDirection, textAttributes.layoutDirection),
debugStringConvertibleItem(
"accessibilityRole",
accessibilityRole,
textAttributes.accessibilityRole),
debugStringConvertibleItem("role", role, textAttributes.role),
};
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,144 @@
/*
* 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 <functional>
#include <limits>
#include <optional>
#include <react/renderer/attributedstring/primitives.h>
#include <react/renderer/components/view/AccessibilityPrimitives.h>
#include <react/renderer/core/LayoutPrimitives.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <react/renderer/debug/DebugStringConvertible.h>
#include <react/renderer/graphics/Color.h>
#include <react/renderer/graphics/Float.h>
#include <react/renderer/graphics/Size.h>
#include <react/utils/hash_combine.h>
namespace facebook::react {
class TextAttributes;
using SharedTextAttributes = std::shared_ptr<const TextAttributes>;
class TextAttributes : public DebugStringConvertible {
public:
/*
* Returns TextAttribute object which has actual default attribute values
* (e.g. `foregroundColor = black`), in oppose to TextAttribute's default
* constructor which creates an object with nulled attributes.
*/
static TextAttributes defaultTextAttributes();
#pragma mark - Fields
// Color
SharedColor foregroundColor{};
SharedColor backgroundColor{};
Float opacity{std::numeric_limits<Float>::quiet_NaN()};
// Font
std::string fontFamily{""};
Float fontSize{std::numeric_limits<Float>::quiet_NaN()};
Float fontSizeMultiplier{std::numeric_limits<Float>::quiet_NaN()};
std::optional<FontWeight> fontWeight{};
std::optional<FontStyle> fontStyle{};
std::optional<FontVariant> fontVariant{};
std::optional<bool> allowFontScaling{};
Float maxFontSizeMultiplier{std::numeric_limits<Float>::quiet_NaN()};
std::optional<DynamicTypeRamp> dynamicTypeRamp{};
Float letterSpacing{std::numeric_limits<Float>::quiet_NaN()};
std::optional<TextTransform> textTransform{};
// Paragraph Styles
Float lineHeight{std::numeric_limits<Float>::quiet_NaN()};
std::optional<TextAlignment> alignment{};
std::optional<WritingDirection> baseWritingDirection{};
std::optional<LineBreakStrategy> lineBreakStrategy{};
std::optional<LineBreakMode> lineBreakMode{};
// Decoration
SharedColor textDecorationColor{};
std::optional<TextDecorationLineType> textDecorationLineType{};
std::optional<TextDecorationStyle> textDecorationStyle{};
// Shadow
// TODO: Use `Point` type instead of `Size` for `textShadowOffset` attribute.
std::optional<Size> textShadowOffset{};
Float textShadowRadius{std::numeric_limits<Float>::quiet_NaN()};
SharedColor textShadowColor{};
// Special
std::optional<bool> isHighlighted{};
std::optional<bool> isPressable{};
// TODO T59221129: document where this value comes from and how it is set.
// It's not clear if this is being used properly, or if it's being set at all.
// Currently, it is intentionally *not* being set as part of BaseTextProps
// construction.
std::optional<LayoutDirection> layoutDirection{};
std::optional<AccessibilityRole> accessibilityRole{};
std::optional<Role> role{};
#pragma mark - Operations
void apply(TextAttributes textAttributes);
#pragma mark - Operators
bool operator==(const TextAttributes &rhs) const;
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugProps() const override;
#endif
};
} // namespace facebook::react
namespace std {
template <>
struct hash<facebook::react::TextAttributes> {
size_t operator()(const facebook::react::TextAttributes &textAttributes) const
{
return facebook::react::hash_combine(
textAttributes.foregroundColor,
textAttributes.backgroundColor,
textAttributes.opacity,
textAttributes.fontFamily,
textAttributes.fontSize,
textAttributes.maxFontSizeMultiplier,
textAttributes.fontSizeMultiplier,
textAttributes.fontWeight,
textAttributes.fontStyle,
textAttributes.fontVariant,
textAttributes.allowFontScaling,
textAttributes.letterSpacing,
textAttributes.textTransform,
textAttributes.lineHeight,
textAttributes.alignment,
textAttributes.baseWritingDirection,
textAttributes.lineBreakStrategy,
textAttributes.lineBreakMode,
textAttributes.textDecorationColor,
textAttributes.textDecorationLineType,
textAttributes.textDecorationStyle,
textAttributes.textShadowOffset,
textAttributes.textShadowRadius,
textAttributes.textShadowColor,
textAttributes.isHighlighted,
textAttributes.isPressable,
textAttributes.layoutDirection,
textAttributes.accessibilityRole,
textAttributes.role);
}
};
} // namespace std

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,153 @@
/*
* 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 <functional>
#include <limits>
namespace facebook::react {
enum class FontStyle { Normal, Italic, Oblique };
enum class FontWeight : int {
Weight100 = 100,
UltraLight = 100,
Weight200 = 200,
Thin = 200,
Weight300 = 300,
Light = 300,
Weight400 = 400,
Regular = 400,
Weight500 = 500,
Medium = 500,
Weight600 = 600,
Semibold = 600,
Demibold = 600,
Weight700 = 700,
Bold = 700,
Weight800 = 800,
Heavy = 800,
Weight900 = 900,
Black = 900
};
enum class FontVariant : int {
Default = 0,
SmallCaps = 1 << 1,
OldstyleNums = 1 << 2,
LiningNums = 1 << 3,
TabularNums = 1 << 4,
ProportionalNums = 1 << 5,
StylisticOne = 1 << 6,
StylisticTwo = 1 << 7,
StylisticThree = 1 << 8,
StylisticFour = 1 << 9,
StylisticFive = 1 << 10,
StylisticSix = 1 << 11,
StylisticSeven = 1 << 12,
StylisticEight = 1 << 13,
StylisticNine = 1 << 14,
StylisticTen = 1 << 15,
StylisticEleven = 1 << 16,
StylisticTwelve = 1 << 17,
StylisticThirteen = 1 << 18,
StylisticFourteen = 1 << 19,
StylisticFifteen = 1 << 20,
StylisticSixteen = 1 << 21,
StylisticSeventeen = 1 << 22,
StylisticEighteen = 1 << 23,
StylisticNineteen = 1 << 24,
StylisticTwenty = 1 << 25
};
enum class DynamicTypeRamp {
Caption2,
Caption1,
Footnote,
Subheadline,
Callout,
Body,
Headline,
Title3,
Title2,
Title1,
LargeTitle
};
enum class EllipsizeMode {
Clip, // Do not add ellipsize, simply clip.
Head, // Truncate at head of line: "...wxyz".
Tail, // Truncate at tail of line: "abcd...".
Middle // Truncate middle of line: "ab...yz".
};
enum class TextBreakStrategy {
Simple, // Simple strategy.
HighQuality, // High-quality strategy, including hyphenation.
Balanced // Balances line lengths.
};
enum class TextAlignment {
Natural, // Indicates the default alignment for script.
Left, // Visually left aligned.
Center, // Visually centered.
Right, // Visually right aligned.
Justified // Fully-justified. The last line in a paragraph is natural-aligned.
};
enum class TextAlignmentVertical {
Auto,
Top,
Bottom,
Center,
};
enum class WritingDirection {
Natural, // Determines direction using the Unicode Bidi Algorithm rules P2 and
// P3.
LeftToRight, // Left to right writing direction.
RightToLeft // Right to left writing direction.
};
enum class LineBreakStrategy {
None, // Don't use any line break strategies
PushOut, // Use the push out line break strategy.
HangulWordPriority, // When specified, it prohibits breaking between Hangul
// characters.
Standard // Use the same configuration of line break strategies that the
// system uses for standard UI labels.
};
enum class LineBreakMode {
Word, // Wrap at word boundaries, default
Char, // Wrap at character boundaries
Clip, // Simply clip
Head, // Truncate at head of line: "...wxyz"
Middle, // Truncate middle of line: "ab...yz"
Tail // Truncate at tail of line: "abcd..."
};
enum class TextDecorationLineType { None, Underline, Strikethrough, UnderlineStrikethrough };
enum class TextDecorationStyle { Solid, Double, Dotted, Dashed };
enum class TextTransform {
None,
Uppercase,
Lowercase,
Capitalize,
Unset,
};
enum class HyphenationFrequency {
None, // No hyphenation.
Normal, // Less frequent hyphenation.
Full // Standard amount of hyphenation.
};
} // namespace facebook::react

View File

@@ -0,0 +1,103 @@
/*
* 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 <memory>
#include <gtest/gtest.h>
#include <react/renderer/attributedstring/AttributedStringBox.h>
namespace facebook::react {
TEST(AttributedStringBoxTest, testDefaultConstructor) {
auto attributedStringBox = AttributedStringBox{};
EXPECT_EQ(attributedStringBox.getMode(), AttributedStringBox::Mode::Value);
EXPECT_EQ(attributedStringBox.getValue(), AttributedString{});
}
TEST(AttributedStringBoxTest, testValueConstructor) {
auto attributedString = AttributedString{};
auto fragment = AttributedString::Fragment{};
fragment.string = "test string";
attributedString.appendFragment(std::move(fragment));
auto attributedStringBox = AttributedStringBox{attributedString};
EXPECT_EQ(attributedStringBox.getMode(), AttributedStringBox::Mode::Value);
EXPECT_EQ(attributedStringBox.getValue(), attributedString);
}
TEST(AttributedStringBoxTest, testOpaquePointerConstructor) {
auto string = std::make_shared<std::string>("test string");
auto attributedStringBox = AttributedStringBox{string};
EXPECT_EQ(
attributedStringBox.getMode(), AttributedStringBox::Mode::OpaquePointer);
EXPECT_EQ(attributedStringBox.getOpaquePointer(), string);
EXPECT_EQ(string.use_count(), 2);
}
TEST(AttributedStringBoxTest, testMoveConstructor) {
{
auto string = std::make_shared<std::string>("test string");
auto movedFromAttributedStringBox = AttributedStringBox{string};
auto moveToAttributedStringBox =
AttributedStringBox{std::move(movedFromAttributedStringBox)};
EXPECT_EQ(
moveToAttributedStringBox.getMode(),
AttributedStringBox::Mode::OpaquePointer);
EXPECT_EQ(moveToAttributedStringBox.getOpaquePointer(), string);
EXPECT_EQ(string.use_count(), 2);
}
{
auto attributedString = AttributedString{};
auto fragment = AttributedString::Fragment{};
fragment.string = "test string";
attributedString.appendFragment(std::move(fragment));
auto movedFromAttributedStringBox = AttributedStringBox{attributedString};
auto moveToAttributedStringBox =
AttributedStringBox{std::move(movedFromAttributedStringBox)};
EXPECT_EQ(
moveToAttributedStringBox.getMode(), AttributedStringBox::Mode::Value);
EXPECT_EQ(moveToAttributedStringBox.getValue(), attributedString);
}
}
TEST(AttributedStringBoxTest, testMoveAssignment) {
{
auto string = std::make_shared<std::string>("test string");
auto movedFromAttributedStringBox = AttributedStringBox{string};
auto movedToAttributedStringBox = AttributedStringBox{};
movedToAttributedStringBox = std::move(movedFromAttributedStringBox);
EXPECT_EQ(
movedToAttributedStringBox.getMode(),
AttributedStringBox::Mode::OpaquePointer);
EXPECT_EQ(movedToAttributedStringBox.getOpaquePointer(), string);
EXPECT_EQ(string.use_count(), 2);
}
{
auto attributedString = AttributedString{};
auto fragment = AttributedString::Fragment{};
fragment.string = "test string";
attributedString.appendFragment(std::move(fragment));
auto movedFromAttributedStringBox = AttributedStringBox{attributedString};
auto moveToAttributedStringBox = AttributedStringBox{};
moveToAttributedStringBox = std::move(movedFromAttributedStringBox);
EXPECT_EQ(
moveToAttributedStringBox.getMode(), AttributedStringBox::Mode::Value);
EXPECT_EQ(moveToAttributedStringBox.getValue(), attributedString);
}
}
} // namespace facebook::react