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
2 changes: 2 additions & 0 deletions packages/react-native/Libraries/Text/TextNativeComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ const textViewConfig = {
onTextLayout: true,
dataDetectorType: true,
android_hyphenationFrequency: true,
lineBreak: true,
wordBreak: true,
lineBreakStrategyIOS: true,
},
directEventTypes: {
Expand Down
25 changes: 25 additions & 0 deletions packages/react-native/Libraries/Text/TextProps.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,31 @@ type TextBaseProps = Readonly<{
*/
android_hyphenationFrequency?: ?('normal' | 'none' | 'full'),

/**
* Sets the strictness of the line-breaking rules, such as which characters
* may start or end a line in Chinese and Japanese text (kinsoku), as CSS
* `line-break`. Currently Android only, and ignored below Android 13 (API
* level 33).
*
* @default `'auto'`
* @platform android
*/
lineBreak?: ?('auto' | 'loose' | 'normal' | 'strict'),

/**
* Sets word-based line breaking, as CSS `word-break`. `'auto-phrase'` breaks
* Japanese text at phrase boundaries instead of between any two characters.
* Currently Android only, and ignored below Android 13 (API level 33).
*
* Support depends on the locale, and text is laid out in the device locale:
* where that locale has no phrase-based breaking, `'auto-phrase'` has no
* effect.
*
* @default `'normal'`
* @platform android
*/
wordBreak?: ?('normal' | 'auto-phrase'),

children?: ?React.Node,

/**
Expand Down
4 changes: 4 additions & 0 deletions packages/react-native/ReactAndroid/api/ReactAndroid.api
Original file line number Diff line number Diff line change
Expand Up @@ -6042,6 +6042,8 @@ public class com/facebook/react/views/text/ReactTextView : androidx/appcompat/wi
public fun setHyphenationFrequency (I)V
public fun setIncludeFontPadding (Z)V
public fun setLetterSpacing (F)V
public fun setLineBreakStyle (I)V
public fun setLineBreakWordStyle (I)V
public fun setLinkifyMask (I)V
public fun setMinimumFontScale (F)V
public fun setMinimumFontSize (F)V
Expand Down Expand Up @@ -6088,6 +6090,7 @@ public class com/facebook/react/views/text/ReactTextViewManager : com/facebook/r
public final fun setFontSize (Lcom/facebook/react/views/text/ReactTextView;F)V
public final fun setIncludeFontPadding (Lcom/facebook/react/views/text/ReactTextView;Z)V
public final fun setLetterSpacing (Lcom/facebook/react/views/text/ReactTextView;F)V
public final fun setLineBreak (Lcom/facebook/react/views/text/ReactTextView;Ljava/lang/String;)V
public final fun setNumberOfLines (Lcom/facebook/react/views/text/ReactTextView;I)V
public final fun setOverflow (Lcom/facebook/react/views/text/ReactTextView;Ljava/lang/String;)V
public synthetic fun setPadding (Landroid/view/View;IIII)V
Expand All @@ -6096,6 +6099,7 @@ public class com/facebook/react/views/text/ReactTextViewManager : com/facebook/r
public final fun setSelectable (Lcom/facebook/react/views/text/ReactTextView;Z)V
public final fun setSelectionColor (Lcom/facebook/react/views/text/ReactTextView;Ljava/lang/Integer;)V
public final fun setTextAlignVertical (Lcom/facebook/react/views/text/ReactTextView;Ljava/lang/String;)V
public final fun setWordBreak (Lcom/facebook/react/views/text/ReactTextView;Ljava/lang/String;)V
public synthetic fun updateExtraData (Landroid/view/View;Ljava/lang/Object;)V
public fun updateExtraData (Lcom/facebook/react/views/text/ReactTextView;Ljava/lang/Object;)V
public synthetic fun updateState (Landroid/view/View;Lcom/facebook/react/uimanager/ReactStylesDiffMap;Lcom/facebook/react/uimanager/StateWrapper;)Ljava/lang/Object;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@

package com.facebook.react.views.text;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.text.LineBreakConfig;
import android.os.Build;
import android.text.Layout;
import android.text.Spannable;
Expand All @@ -26,6 +28,7 @@
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.core.view.AccessibilityDelegateCompat;
import androidx.core.view.ViewCompat;
Expand Down Expand Up @@ -197,6 +200,10 @@ private void initView() {
}

setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_NONE);
setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE);
}
updateView(); // call after changing ellipsizeLocation in particular
}

Expand Down Expand Up @@ -249,6 +256,8 @@ protected void onDraw(Canvas canvas) {
getIncludeFontPadding(),
getBreakStrategy(),
getHyphenationFrequency(),
getLineBreakStyleCompat(),
getLineBreakWordStyleCompat(),
// always passing ALIGN_NORMAL here should be fine, since this method doesn't depend on
// how exactly lines are aligned, just their width
Layout.Alignment.ALIGN_NORMAL,
Expand Down Expand Up @@ -597,6 +606,34 @@ public void setHyphenationFrequency(int hyphenationFrequency) {
mShouldAdjustSpannableFontSize = true;
}

@Override
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void setLineBreakStyle(int lineBreakStyle) {
super.setLineBreakStyle(lineBreakStyle);
mShouldAdjustSpannableFontSize = true;
}

@Override
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void setLineBreakWordStyle(int lineBreakWordStyle) {
super.setLineBreakWordStyle(lineBreakWordStyle);
mShouldAdjustSpannableFontSize = true;
}

@SuppressLint("InlinedApi")
private int getLineBreakStyleCompat() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
? getLineBreakStyle()
: LineBreakConfig.LINE_BREAK_STYLE_NONE;
}

@SuppressLint("InlinedApi")
private int getLineBreakWordStyleCompat() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
? getLineBreakWordStyle()
: LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
}

@Override
public void setLetterSpacing(float letterSpacing) {
if (Float.isNaN(letterSpacing)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,20 @@ public constructor(
}
}

@ReactProp(name = "lineBreak")
public fun setLineBreak(view: ReactTextView, lineBreak: String?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
view.lineBreakStyle = TextAttributeProps.getLineBreakStyle(lineBreak)
}
}

@ReactProp(name = "wordBreak")
public fun setWordBreak(view: ReactTextView, wordBreak: String?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
view.lineBreakWordStyle = TextAttributeProps.getLineBreakWordStyle(wordBreak)
}
}

@ReactPropGroup(
names =
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

package com.facebook.react.views.text

import android.annotation.SuppressLint
import android.graphics.text.LineBreakConfig
import android.os.Build
import android.text.Layout
import android.text.TextUtils.TruncateAt
Expand Down Expand Up @@ -612,6 +614,36 @@ public class TextAttributeProps private constructor() {
else -> Layout.HYPHENATION_FREQUENCY_FULL
}

// The LineBreakConfig constants are inlined at compile time, so reading them is safe below API
// 33. Callers apply the result only on API 33+.
// Maps CSS `line-break` values to LineBreakConfig.LINE_BREAK_STYLE_*.
@SuppressLint("InlinedApi")
internal fun getLineBreakStyle(lineBreak: String?): Int =
when (lineBreak) {
null,
"auto" -> LineBreakConfig.LINE_BREAK_STYLE_NONE
"loose" -> LineBreakConfig.LINE_BREAK_STYLE_LOOSE
"normal" -> LineBreakConfig.LINE_BREAK_STYLE_NORMAL
"strict" -> LineBreakConfig.LINE_BREAK_STYLE_STRICT
else -> {
FLog.w(ReactConstants.TAG, "Invalid lineBreak: $lineBreak")
LineBreakConfig.LINE_BREAK_STYLE_NONE
}
}

// Maps CSS `word-break` values to LineBreakConfig.LINE_BREAK_WORD_STYLE_*.
@SuppressLint("InlinedApi")
internal fun getLineBreakWordStyle(wordBreak: String?): Int =
when (wordBreak) {
null,
"normal" -> LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE
"auto-phrase" -> LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE
else -> {
FLog.w(ReactConstants.TAG, "Invalid wordBreak: $wordBreak")
LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE
}
}

public fun getEllipsizeMode(ellipsizeMode: String?): TruncateAt? =
when (ellipsizeMode) {
"head" -> TruncateAt.START
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import android.content.res.AssetManager
import android.graphics.Color
import android.graphics.RectF
import android.graphics.Typeface
import android.graphics.text.LineBreakConfig
import android.os.Build
import android.text.BoringLayout
import android.text.Layout
Expand Down Expand Up @@ -99,6 +100,8 @@ internal object TextLayoutManager {
const val PA_KEY_TEXT_ALIGN_VERTICAL: Int = 8
const val PA_KEY_TEXT_WIDTH_MODE: Int = 9
const val PA_KEY_MINIMUM_FONT_SCALE: Int = 10
const val PA_KEY_LINE_BREAK: Int = 11
const val PA_KEY_WORD_BREAK: Int = 12

private val TAG: String = TextLayoutManager::class.java.simpleName

Expand Down Expand Up @@ -849,6 +852,8 @@ internal object TextLayoutManager {
includeFontPadding: Boolean,
textBreakStrategy: Int,
hyphenationFrequency: Int,
lineBreakStyle: Int,
lineBreakWordStyle: Int,
alignment: Layout.Alignment,
justificationMode: Int,
ellipsizeMode: TextUtils.TruncateAt?,
Expand Down Expand Up @@ -895,6 +900,8 @@ internal object TextLayoutManager {
includeFontPadding,
textBreakStrategy,
hyphenationFrequency,
lineBreakStyle,
lineBreakWordStyle,
alignment,
justificationMode,
ellipsizeMode,
Expand All @@ -913,6 +920,8 @@ internal object TextLayoutManager {
includeFontPadding,
textBreakStrategy,
hyphenationFrequency,
lineBreakStyle,
lineBreakWordStyle,
alignment,
justificationMode,
ellipsizeMode,
Expand Down Expand Up @@ -963,6 +972,8 @@ internal object TextLayoutManager {
includeFontPadding: Boolean,
textBreakStrategy: Int,
hyphenationFrequency: Int,
lineBreakStyle: Int,
lineBreakWordStyle: Int,
alignment: Layout.Alignment,
justificationMode: Int,
ellipsizeMode: TextUtils.TruncateAt?,
Expand Down Expand Up @@ -990,6 +1001,21 @@ internal object TextLayoutManager {
builder.setUseLineSpacingFromFallbacks(true)
}

// Leave the platform default in place unless a style was requested, so text without these props
// lays out exactly as before.
if (
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
(lineBreakStyle != LineBreakConfig.LINE_BREAK_STYLE_NONE ||
lineBreakWordStyle != LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
) {
builder.setLineBreakConfig(
LineBreakConfig.Builder()
.setLineBreakStyle(lineBreakStyle)
.setLineBreakWordStyle(lineBreakWordStyle)
.build()
)
}

// Android shifts negative (left-side) start overhang itself. RTL start overhang is on the
// right, so createLayout reserves that space in a second pass while preserving the EXACT Yoga
// measurement returned to the caller.
Expand Down Expand Up @@ -1169,6 +1195,18 @@ internal object TextLayoutManager {
TextAttributeProps.getHyphenationFrequency(
paragraphAttributes.getString(PA_KEY_HYPHENATION_FREQUENCY),
)
val lineBreakStyle =
TextAttributeProps.getLineBreakStyle(
if (paragraphAttributes.contains(PA_KEY_LINE_BREAK))
paragraphAttributes.getString(PA_KEY_LINE_BREAK)
else null,
)
val lineBreakWordStyle =
TextAttributeProps.getLineBreakWordStyle(
if (paragraphAttributes.contains(PA_KEY_WORD_BREAK))
paragraphAttributes.getString(PA_KEY_WORD_BREAK)
else null,
)
val adjustFontSizeToFit =
if (paragraphAttributes.contains(PA_KEY_ADJUST_FONT_SIZE_TO_FIT))
paragraphAttributes.getBoolean(PA_KEY_ADJUST_FONT_SIZE_TO_FIT)
Expand Down Expand Up @@ -1211,6 +1249,8 @@ internal object TextLayoutManager {
includeFontPadding,
textBreakStrategy,
hyphenationFrequency,
lineBreakStyle,
lineBreakWordStyle,
alignment,
justificationMode,
paint,
Expand All @@ -1226,6 +1266,8 @@ internal object TextLayoutManager {
includeFontPadding,
textBreakStrategy,
hyphenationFrequency,
lineBreakStyle,
lineBreakWordStyle,
alignment,
justificationMode,
ellipsizeMode,
Expand All @@ -1249,6 +1291,8 @@ internal object TextLayoutManager {
includeFontPadding,
textBreakStrategy,
hyphenationFrequency,
lineBreakStyle,
lineBreakWordStyle,
alignment,
justificationMode,
ellipsizeMode,
Expand Down Expand Up @@ -1372,6 +1416,8 @@ internal object TextLayoutManager {
includeFontPadding: Boolean,
textBreakStrategy: Int,
hyphenationFrequency: Int,
lineBreakStyle: Int,
lineBreakWordStyle: Int,
alignment: Layout.Alignment,
justificationMode: Int,
paint: TextPaint,
Expand Down Expand Up @@ -1436,6 +1482,8 @@ internal object TextLayoutManager {
includeFontPadding,
textBreakStrategy,
hyphenationFrequency,
lineBreakStyle,
lineBreakWordStyle,
alignment,
justificationMode,
null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package com.facebook.react.views.text

import android.annotation.SuppressLint
import android.graphics.text.LineBreakConfig
import android.text.BoringLayout
import android.text.Layout
import android.text.SpannableString
Expand Down Expand Up @@ -104,6 +105,8 @@ class TextLayoutManagerAbsoluteLayoutWithFractionalPixelTest {
java.lang.Boolean.TYPE,
java.lang.Integer.TYPE,
java.lang.Integer.TYPE,
java.lang.Integer.TYPE,
java.lang.Integer.TYPE,
Layout.Alignment::class.java,
java.lang.Integer.TYPE,
TextUtils.TruncateAt::class.java,
Expand All @@ -121,6 +124,8 @@ class TextLayoutManagerAbsoluteLayoutWithFractionalPixelTest {
/* includeFontPadding = */ false,
/* textBreakStrategy = */ Layout.BREAK_STRATEGY_HIGH_QUALITY,
/* hyphenationFrequency = */ Layout.HYPHENATION_FREQUENCY_NONE,
/* lineBreakStyle = */ LineBreakConfig.LINE_BREAK_STYLE_NONE,
/* lineBreakWordStyle = */ LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE,
Layout.Alignment.ALIGN_NORMAL,
/* justificationMode = */ 0,
/* ellipsizeMode = */ null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

package com.facebook.react.views.text

import android.graphics.text.LineBreakConfig
import android.text.Layout
import android.text.SpannableString
import android.text.Spanned
Expand Down Expand Up @@ -123,6 +124,8 @@ class TextLayoutManagerMinimumFontScaleTest {
true,
Layout.BREAK_STRATEGY_SIMPLE,
Layout.HYPHENATION_FREQUENCY_NONE,
/* lineBreakStyle = */ LineBreakConfig.LINE_BREAK_STYLE_NONE,
/* lineBreakWordStyle = */ LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE,
Layout.Alignment.ALIGN_NORMAL,
0,
newPaint(),
Expand All @@ -149,6 +152,8 @@ class TextLayoutManagerMinimumFontScaleTest {
true,
Layout.BREAK_STRATEGY_SIMPLE,
Layout.HYPHENATION_FREQUENCY_NONE,
/* lineBreakStyle = */ LineBreakConfig.LINE_BREAK_STYLE_NONE,
/* lineBreakWordStyle = */ LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE,
Layout.Alignment.ALIGN_NORMAL,
0,
newPaint(),
Expand Down
Loading
Loading