Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = {
textDecorationColor: colorAttributes,
textDecorationLine: true,
textDecorationStyle: true,
textShadow: true,
textShadowColor: colorAttributes,
textShadowOffset: true,
textShadowRadius: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,15 @@ export interface TextStyle extends TextStyleIOS, TextStyleAndroid, ViewStyle {
| undefined;
textDecorationStyle?: 'solid' | 'double' | 'dotted' | 'dashed' | undefined;
textDecorationColor?: ColorValue | undefined;
textShadow?:
| string
| ReadonlyArray<{
offsetX: number;
offsetY: number;
blurRadius: number;
color?: ColorValue | undefined;
}>
| undefined;
textShadowColor?: ColorValue | undefined;
textShadowOffset?: {width: number; height: number} | undefined;
textShadowRadius?: number | undefined;
Expand Down
8 changes: 8 additions & 0 deletions packages/react-native/Libraries/StyleSheet/StyleSheetTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,14 @@ type ____TextStyle_InternalBase = Readonly<{
fontStyle?: 'normal' | 'italic',
fontWeight?: ____FontWeight_Internal,
fontVariant?: ____FontVariantArray_Internal | string,
textShadow?:
| string
| ReadonlyArray<{
offsetX: number,
offsetY: number,
blurRadius: number,
color?: ____ColorValue_Internal,
}>,
textShadowOffset?: Readonly<{
width: number,
height: number,
Expand Down
20 changes: 20 additions & 0 deletions packages/react-native/Libraries/Text/__tests__/Text-itest.js
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,26 @@ describe('<Text>', () => {
);
});
});

describe('textShadow', () => {
it('parses CSS text-shadow string', () => {
const root = Fantom.createRoot();

Fantom.runTask(() => {
root.render(
<Text style={{textShadow: '2px 4px 8px red'}}>{TEST_TEXT}</Text>,
);
});

expect(
root.getRenderedOutput({props: ['textShadow']}).toJSX(),
).toEqual(
<rn-paragraph textShadow="[TextShadow{offsetX: 2.000000, offsetY: 4.000000, blurRadius: 8.000000, color: rgba(255, 0, 0, 1)}]">
{TEST_TEXT}
</rn-paragraph>,
);
});
});
});

describe('aria-hidden', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ import com.facebook.react.views.text.ReactTypefaceUtils.parseFontVariant
import com.facebook.react.views.text.ReactTypefaceUtils.parseFontWeight
import kotlin.math.ceil

/** Data class representing a single text shadow from the CSS text-shadow property. */
public data class TextShadowData(
val offsetX: Float,
val offsetY: Float,
val blurRadius: Float,
val color: Int,
)

// TODO: T63643819 refactor naming of TextAttributeProps to make explicit that this represents
// TextAttributes and not TextProps. As part of this refactor extract methods that don't belong to
// TextAttributeProps (e.g. TextAlign)
Expand Down Expand Up @@ -314,6 +322,10 @@ public class TextAttributeProps private constructor() {
}
}

/** List of text shadows from the CSS text-shadow property. */
public var textShadows: List<TextShadowData> = emptyList()
private set

private fun setTextTransform(textTransform: String?) {
this.textTransform =
when (textTransform) {
Expand Down Expand Up @@ -376,6 +388,13 @@ public class TextAttributeProps private constructor() {
public const val TA_KEY_ROLE: Int = 26
public const val TA_KEY_TEXT_TRANSFORM: Int = 27
public const val TA_KEY_MAX_FONT_SIZE_MULTIPLIER: Int = 29
public const val TA_KEY_TEXT_SHADOW: Int = 30

// constants for TextShadow serialization (nested within TA_KEY_TEXT_SHADOW)
private const val TS_KEY_OFFSET_X: Int = 0
private const val TS_KEY_OFFSET_Y: Int = 1
private const val TS_KEY_BLUR_RADIUS: Int = 2
private const val TS_KEY_COLOR: Int = 3

public const val UNSET: Int = -1

Expand Down Expand Up @@ -423,6 +442,32 @@ public class TextAttributeProps private constructor() {
TA_KEY_TEXT_SHADOW_COLOR -> result.textShadowColor = entry.intValue
TA_KEY_TEXT_SHADOW_OFFSET_DX -> result.textShadowOffsetDx = entry.doubleValue.toFloat()
TA_KEY_TEXT_SHADOW_OFFSET_DY -> result.textShadowOffsetDy = entry.doubleValue.toFloat()
TA_KEY_TEXT_SHADOW -> {
val shadowsBuffer = entry.mapBufferValue
val shadows = mutableListOf<TextShadowData>()
val shadowIterator = shadowsBuffer.iterator()
while (shadowIterator.hasNext()) {
val shadowEntry = shadowIterator.next()
val shadowBuffer = shadowEntry.mapBufferValue
val offsetX =
if (shadowBuffer.contains(TS_KEY_OFFSET_X))
toPixelFromDIP(shadowBuffer.getDouble(TS_KEY_OFFSET_X).toFloat())
else 0f
val offsetY =
if (shadowBuffer.contains(TS_KEY_OFFSET_Y))
toPixelFromDIP(shadowBuffer.getDouble(TS_KEY_OFFSET_Y).toFloat())
else 0f
val blurRadius =
if (shadowBuffer.contains(TS_KEY_BLUR_RADIUS))
shadowBuffer.getDouble(TS_KEY_BLUR_RADIUS).toFloat()
else 0f
val color =
if (shadowBuffer.contains(TS_KEY_COLOR)) shadowBuffer.getInt(TS_KEY_COLOR)
else DEFAULT_TEXT_SHADOW_COLOR
shadows.add(TextShadowData(offsetX, offsetY, blurRadius, color))
}
result.textShadows = shadows
}
TA_KEY_IS_HIGHLIGHTED -> {}
TA_KEY_LAYOUT_DIRECTION -> result.setLayoutDirection(entry.stringValue)
TA_KEY_ACCESSIBILITY_ROLE -> result.setAccessibilityRole(entry.stringValue)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,20 @@ internal object TextLayoutManager {
if (textAttributes.isLineThroughTextDecorationSet) {
ops.add(SetSpanOperation(start, end, ReactStrikethroughSpan()))
}
if (
// prefer CSS textShadow array over legacy properties
if (textAttributes.textShadows.isNotEmpty()) {
// Currently, we only support one shadow
val shadow = textAttributes.textShadows[0]
if (Color.alpha(shadow.color) != 0) {
ops.add(
SetSpanOperation(
start,
end,
ShadowStyleSpan(shadow.offsetX, shadow.offsetY, shadow.blurRadius, shadow.color),
)
)
}
} else if (
(textAttributes.textShadowOffsetDx != 0f ||
textAttributes.textShadowOffsetDy != 0f ||
textAttributes.textShadowRadius != 0f) &&
Expand Down Expand Up @@ -488,7 +501,18 @@ internal object TextLayoutManager {
spannable.setSpan(ReactStrikethroughSpan(), start, end, spanFlags)
}

if (
// prefer CSS textShadow array over legacy properties
if (fragment.props.textShadows.isNotEmpty()) {
val shadow = fragment.props.textShadows[0]
if (Color.alpha(shadow.color) != 0) {
spannable.setSpan(
ShadowStyleSpan(shadow.offsetX, shadow.offsetY, shadow.blurRadius, shadow.color),
start,
end,
spanFlags,
)
}
} else if (
(fragment.props.textShadowOffsetDx != 0f ||
fragment.props.textShadowOffsetDy != 0f ||
fragment.props.textShadowRadius != 0f) &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ void TextAttributes::apply(TextAttributes textAttributes) {
textShadowColor = textAttributes.textShadowColor
? textAttributes.textShadowColor
: textShadowColor;
textShadow = !textAttributes.textShadow.empty()
? textAttributes.textShadow
: textShadow;

// Special
isHighlighted = textAttributes.isHighlighted.has_value()
Expand Down Expand Up @@ -136,6 +139,7 @@ bool TextAttributes::operator==(const TextAttributes& rhs) const {
textDecorationStyle,
textShadowOffset,
textShadowColor,
textShadow,
isHighlighted,
isPressable,
layoutDirection,
Expand All @@ -159,6 +163,7 @@ bool TextAttributes::operator==(const TextAttributes& rhs) const {
rhs.textDecorationStyle,
rhs.textShadowOffset,
rhs.textShadowColor,
rhs.textShadow,
rhs.isHighlighted,
rhs.isPressable,
rhs.layoutDirection,
Expand Down Expand Up @@ -268,6 +273,8 @@ SharedDebugStringConvertibleList TextAttributes::getDebugProps() const {
textAttributes.textShadowRadius),
debugStringConvertibleItem(
"textShadowColor", textShadowColor, textAttributes.textShadowColor),
debugStringConvertibleItem(
"textShadow", textShadow, textAttributes.textShadow),

// Special
debugStringConvertibleItem(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <react/renderer/graphics/Color.h>
#include <react/renderer/graphics/Float.h>
#include <react/renderer/graphics/Size.h>
#include <react/renderer/graphics/TextShadow.h>
#include <react/utils/hash_combine.h>

namespace facebook::react {
Expand Down Expand Up @@ -74,6 +75,9 @@ class TextAttributes : public DebugStringConvertible {
Float textShadowRadius{std::numeric_limits<Float>::quiet_NaN()};
SharedColor textShadowColor{};

// TextShadow (CSS text-shadow syntax)
std::vector<TextShadow> textShadow{};

// Special
std::optional<bool> isHighlighted{};
std::optional<bool> isPressable{};
Expand Down Expand Up @@ -134,6 +138,7 @@ struct hash<facebook::react::TextAttributes> {
textAttributes.textShadowOffset,
textAttributes.textShadowRadius,
textAttributes.textShadowColor,
textAttributes.textShadow,
textAttributes.isHighlighted,
textAttributes.isPressable,
textAttributes.layoutDirection,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,13 @@ constexpr static MapBuffer::Key TA_KEY_ROLE = 26;
constexpr static MapBuffer::Key TA_KEY_TEXT_TRANSFORM = 27;
constexpr static MapBuffer::Key TA_KEY_ALIGNMENT_VERTICAL = 28;
constexpr static MapBuffer::Key TA_KEY_MAX_FONT_SIZE_MULTIPLIER = 29;
constexpr static MapBuffer::Key TA_KEY_TEXT_SHADOW = 30;

// constants for TextShadow serialization (nested within TA_KEY_TEXT_SHADOW)
constexpr static MapBuffer::Key TS_KEY_OFFSET_X = 0;
constexpr static MapBuffer::Key TS_KEY_OFFSET_Y = 1;
constexpr static MapBuffer::Key TS_KEY_BLUR_RADIUS = 2;
constexpr static MapBuffer::Key TS_KEY_COLOR = 3;

// constants for ParagraphAttributes serialization
constexpr static MapBuffer::Key PA_KEY_MAX_NUMBER_OF_LINES = 0;
Expand Down Expand Up @@ -1218,6 +1225,22 @@ inline MapBuffer toMapBuffer(const TextAttributes &textAttributes)
builder.putDouble(TA_KEY_TEXT_SHADOW_OFFSET_DX, textAttributes.textShadowOffset->width);
builder.putDouble(TA_KEY_TEXT_SHADOW_OFFSET_DY, textAttributes.textShadowOffset->height);
}
// TextShadow (CSS text-shadow syntax)
if (!textAttributes.textShadow.empty()) {
auto textShadowBuilder = MapBufferBuilder();
for (size_t i = 0; i < textAttributes.textShadow.size(); ++i) {
const auto &shadow = textAttributes.textShadow[i];
auto shadowBuilder = MapBufferBuilder();
shadowBuilder.putDouble(TS_KEY_OFFSET_X, shadow.offsetX);
shadowBuilder.putDouble(TS_KEY_OFFSET_Y, shadow.offsetY);
shadowBuilder.putDouble(TS_KEY_BLUR_RADIUS, shadow.blurRadius);
if (shadow.color) {
shadowBuilder.putInt(TS_KEY_COLOR, toAndroidRepr(shadow.color));
}
textShadowBuilder.putMapBuffer(static_cast<MapBuffer::Key>(i), shadowBuilder.build());
}
builder.putMapBuffer(TA_KEY_TEXT_SHADOW, textShadowBuilder.build());
}
// Special
if (textAttributes.isHighlighted.has_value()) {
builder.putBool(TA_KEY_IS_HIGHLIGHTED, *textAttributes.isHighlighted);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/attributedstring/conversions.h>
#include <react/renderer/components/text/TextShadowPropsConversions.h>
#include <react/renderer/core/graphicsConversions.h>
#include <react/renderer/core/propsConversions.h>
#include <react/renderer/debug/DebugStringConvertibleItem.h>
Expand Down Expand Up @@ -170,6 +171,12 @@ static TextAttributes convertRawProp(
"textShadowColor",
sourceTextAttributes.textShadowColor,
defaultTextAttributes.textShadowColor);
textAttributes.textShadow = convertRawProp(
context,
rawProps,
"textShadow",
sourceTextAttributes.textShadow,
defaultTextAttributes.textShadow);

// Special
textAttributes.isHighlighted = convertRawProp(
Expand Down Expand Up @@ -319,6 +326,8 @@ void BaseTextProps::setProp(
defaults, value, textAttributes, textShadowRadius, "textShadowRadius");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, textShadowColor, "textShadowColor");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, textShadow, "textShadow");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, isHighlighted, "isHighlighted");
REBUILD_FIELD_SWITCH_CASE(
Expand Down
Loading
Loading