diff --git a/ReactCommon/react/renderer/attributedstring/TextAttributes.cpp b/ReactCommon/react/renderer/attributedstring/TextAttributes.cpp index 3728f0c361259a..f56563a8f916cd 100644 --- a/ReactCommon/react/renderer/attributedstring/TextAttributes.cpp +++ b/ReactCommon/react/renderer/attributedstring/TextAttributes.cpp @@ -63,6 +63,9 @@ void TextAttributes::apply(TextAttributes textAttributes) { baseWritingDirection = textAttributes.baseWritingDirection.has_value() ? textAttributes.baseWritingDirection : baseWritingDirection; + lineBreakStrategy = textAttributes.lineBreakStrategy.has_value() + ? textAttributes.lineBreakStrategy + : lineBreakStrategy; // Decoration textDecorationColor = textAttributes.textDecorationColor @@ -111,6 +114,7 @@ bool TextAttributes::operator==(const TextAttributes &rhs) const { allowFontScaling, alignment, baseWritingDirection, + lineBreakStrategy, textDecorationColor, textDecorationLineType, textDecorationStyle, @@ -130,6 +134,7 @@ bool TextAttributes::operator==(const TextAttributes &rhs) const { rhs.allowFontScaling, rhs.alignment, rhs.baseWritingDirection, + rhs.lineBreakStrategy, rhs.textDecorationColor, rhs.textDecorationLineType, rhs.textDecorationStyle, @@ -188,6 +193,7 @@ SharedDebugStringConvertibleList TextAttributes::getDebugProps() const { debugStringConvertibleItem("lineHeight", lineHeight), debugStringConvertibleItem("alignment", alignment), debugStringConvertibleItem("baseWritingDirection", baseWritingDirection), + debugStringConvertibleItem("lineBreakStrategyIOS", lineBreakStrategy), // Decoration debugStringConvertibleItem("textDecorationColor", textDecorationColor), diff --git a/ReactCommon/react/renderer/attributedstring/TextAttributes.h b/ReactCommon/react/renderer/attributedstring/TextAttributes.h index 82b8547507e574..69400f2ed172f7 100644 --- a/ReactCommon/react/renderer/attributedstring/TextAttributes.h +++ b/ReactCommon/react/renderer/attributedstring/TextAttributes.h @@ -57,6 +57,7 @@ class TextAttributes : public DebugStringConvertible { Float lineHeight{std::numeric_limits::quiet_NaN()}; std::optional alignment{}; std::optional baseWritingDirection{}; + std::optional lineBreakStrategy{}; // Decoration SharedColor textDecorationColor{}; @@ -121,6 +122,7 @@ struct hash { textAttributes.lineHeight, textAttributes.alignment, textAttributes.baseWritingDirection, + textAttributes.lineBreakStrategy, textAttributes.textDecorationColor, textAttributes.textDecorationLineType, textAttributes.textDecorationStyle, diff --git a/ReactCommon/react/renderer/attributedstring/conversions.h b/ReactCommon/react/renderer/attributedstring/conversions.h index 96cbc0c5d7e1de..e2c7eb4e5027ec 100644 --- a/ReactCommon/react/renderer/attributedstring/conversions.h +++ b/ReactCommon/react/renderer/attributedstring/conversions.h @@ -420,6 +420,53 @@ inline std::string toString(const WritingDirection &writingDirection) { return "auto"; } + +inline void fromRawValue( + const PropsParserContext &context, + const RawValue &value, + LineBreakStrategy &result) { + react_native_assert(value.hasType()); + if (value.hasType()) { + auto string = (std::string)value; + if (string == "none") { + result = LineBreakStrategy::None; + } else if (string == "push-out") { + result = LineBreakStrategy::PushOut; + } else if (string == "hangul-word") { + result = LineBreakStrategy::HangulWordPriority; + } else if (string == "standard") { + result = LineBreakStrategy::Standard; + } else { + LOG(ERROR) << "Unsupported LineBreakStrategy value: " << string; + react_native_assert(false); + // sane default for prod + result = LineBreakStrategy::None; + } + return; + } + + LOG(ERROR) << "Unsupported LineBreakStrategy type"; + // sane default for prod + result = LineBreakStrategy::None; +} + +inline std::string toString(const LineBreakStrategy &lineBreakStrategy) { + switch (lineBreakStrategy) { + case LineBreakStrategy::None: + return "none"; + case LineBreakStrategy::PushOut: + return "push-out"; + case LineBreakStrategy::HangulWordPriority: + return "hangul-word"; + case LineBreakStrategy::Standard: + return "standard"; + } + + LOG(ERROR) << "Unsupported LineBreakStrategy value"; + // sane default for prod + return "none"; +} + inline void fromRawValue( const PropsParserContext &context, const RawValue &value, @@ -873,6 +920,10 @@ inline folly::dynamic toDynamic(const TextAttributes &textAttributes) { _textAttributes( "baseWritingDirection", toString(*textAttributes.baseWritingDirection)); } + if (textAttributes.lineBreakStrategy.has_value()) { + _textAttributes( + "lineBreakStrategyIOS", toString(*textAttributes.lineBreakStrategy)); + } // Decoration if (textAttributes.textDecorationColor) { _textAttributes( @@ -982,6 +1033,7 @@ constexpr static MapBuffer::Key TA_KEY_TEXT_SHADOW_COLOR = 19; constexpr static MapBuffer::Key TA_KEY_IS_HIGHLIGHTED = 20; constexpr static MapBuffer::Key TA_KEY_LAYOUT_DIRECTION = 21; constexpr static MapBuffer::Key TA_KEY_ACCESSIBILITY_ROLE = 22; +constexpr static MapBuffer::Key TA_KEY_LINE_BREAK_STRATEGY = 23; // constants for ParagraphAttributes serialization constexpr static MapBuffer::Key PA_KEY_MAX_NUMBER_OF_LINES = 0; @@ -1084,6 +1136,11 @@ inline MapBuffer toMapBuffer(const TextAttributes &textAttributes) { TA_KEY_BEST_WRITING_DIRECTION, toString(*textAttributes.baseWritingDirection)); } + if (textAttributes.lineBreakStrategy.has_value()) { + builder.putString( + TA_KEY_LINE_BREAK_STRATEGY, + toString(*textAttributes.lineBreakStrategy)); + } // Decoration if (textAttributes.textDecorationColor) { builder.putInt( diff --git a/ReactCommon/react/renderer/attributedstring/primitives.h b/ReactCommon/react/renderer/attributedstring/primitives.h index 1535210aca2b64..116cc24faf04d0 100644 --- a/ReactCommon/react/renderer/attributedstring/primitives.h +++ b/ReactCommon/react/renderer/attributedstring/primitives.h @@ -74,6 +74,13 @@ enum class WritingDirection { 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 TextDecorationLineType { None, Underline, diff --git a/ReactCommon/react/renderer/components/text/BaseTextProps.cpp b/ReactCommon/react/renderer/components/text/BaseTextProps.cpp index c1606d0ddae811..fc4c9fc3a1f3d6 100644 --- a/ReactCommon/react/renderer/components/text/BaseTextProps.cpp +++ b/ReactCommon/react/renderer/components/text/BaseTextProps.cpp @@ -120,6 +120,12 @@ static TextAttributes convertRawProp( "baseWritingDirection", sourceTextAttributes.baseWritingDirection, defaultTextAttributes.baseWritingDirection); + textAttributes.lineBreakStrategy = convertRawProp( + context, + rawProps, + "lineBreakStrategyIOS", + sourceTextAttributes.lineBreakStrategy, + defaultTextAttributes.lineBreakStrategy); // Decoration textAttributes.textDecorationColor = convertRawProp( @@ -258,6 +264,12 @@ void BaseTextProps::setProp( textAttributes, baseWritingDirection, "baseWritingDirection"); + REBUILD_FIELD_SWITCH_CASE( + defaults, + value, + textAttributes, + lineBreakStrategy, + "lineBreakStrategyIOS"); REBUILD_FIELD_SWITCH_CASE( defaults, value, diff --git a/ReactCommon/react/renderer/textlayoutmanager/platform/ios/RCTAttributedTextUtils.mm b/ReactCommon/react/renderer/textlayoutmanager/platform/ios/RCTAttributedTextUtils.mm index 0a5828ab7f5dbd..dcd4936369bbcc 100644 --- a/ReactCommon/react/renderer/textlayoutmanager/platform/ios/RCTAttributedTextUtils.mm +++ b/ReactCommon/react/renderer/textlayoutmanager/platform/ios/RCTAttributedTextUtils.mm @@ -155,6 +155,12 @@ inline static CGFloat RCTEffectiveFontSizeMultiplierFromTextAttributes(const Tex RCTNSWritingDirectionFromWritingDirection(textAttributes.baseWritingDirection.value()); isParagraphStyleUsed = YES; } + + if (textAttributes.lineBreakStrategy.has_value()) { + paragraphStyle.lineBreakStrategy = + RCTNSLineBreakStrategyFromLineBreakStrategy(textAttributes.lineBreakStrategy.value()); + isParagraphStyleUsed = YES; + } if (!isnan(textAttributes.lineHeight)) { CGFloat lineHeight = textAttributes.lineHeight * RCTEffectiveFontSizeMultiplierFromTextAttributes(textAttributes); diff --git a/ReactCommon/react/renderer/textlayoutmanager/platform/ios/RCTTextPrimitivesConversions.h b/ReactCommon/react/renderer/textlayoutmanager/platform/ios/RCTTextPrimitivesConversions.h index 235137bde0c13a..a5d26c92945387 100644 --- a/ReactCommon/react/renderer/textlayoutmanager/platform/ios/RCTTextPrimitivesConversions.h +++ b/ReactCommon/react/renderer/textlayoutmanager/platform/ios/RCTTextPrimitivesConversions.h @@ -40,6 +40,28 @@ inline static NSWritingDirection RCTNSWritingDirectionFromWritingDirection(Writi } } +inline static NSLineBreakStrategy RCTNSLineBreakStrategyFromLineBreakStrategy(LineBreakStrategy lineBreakStrategy) +{ + switch (lineBreakStrategy) { + case LineBreakStrategy::None: + return NSLineBreakStrategyNone; + case LineBreakStrategy::PushOut: + return NSLineBreakStrategyPushOut; + case LineBreakStrategy::HangulWordPriority: + if (@available(iOS 14.0, *)) { + return NSLineBreakStrategyHangulWordPriority; + } else { + return NSLineBreakStrategyNone; + } + case LineBreakStrategy::Standard: + if (@available(iOS 14.0, *)) { + return NSLineBreakStrategyStandard; + } else { + return NSLineBreakStrategyNone; + } + } +} + inline static RCTFontStyle RCTFontStyleFromFontStyle(FontStyle fontStyle) { switch (fontStyle) {