Skip to content
Closed
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,
android_lineBreakStyle: true,
android_lineBreakWordStyle: true,
lineBreakStrategyIOS: true,
},
directEventTypes: {
Expand Down
23 changes: 23 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,29 @@ 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). Matches CSS
* `line-break`. Ignored below Android 13 (API level 33).
*
* @default `'none'`
* @platform android
*/
android_lineBreakStyle?: ?('none' | 'loose' | 'normal' | 'strict'),

/**
* Sets word-based line breaking. `'phrase'` breaks Japanese text at phrase
* boundaries instead of between any two characters. Matches CSS
* `word-break: auto-phrase`. 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, `'phrase'` has no effect.
*
* @default `'none'`
* @platform android
*/
android_lineBreakWordStyle?: ?('none' | '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 @@ -6077,6 +6079,8 @@ public class com/facebook/react/views/text/ReactTextViewManager : com/facebook/r
public final fun setAccessible (Lcom/facebook/react/views/text/ReactTextView;Z)V
public final fun setAdjustFontSizeToFit (Lcom/facebook/react/views/text/ReactTextView;Z)V
public final fun setAndroidHyphenationFrequency (Lcom/facebook/react/views/text/ReactTextView;Ljava/lang/String;)V
public final fun setAndroidLineBreakStyle (Lcom/facebook/react/views/text/ReactTextView;Ljava/lang/String;)V
public final fun setAndroidLineBreakWordStyle (Lcom/facebook/react/views/text/ReactTextView;Ljava/lang/String;)V
public final fun setBorderColor (Lcom/facebook/react/views/text/ReactTextView;ILjava/lang/Integer;)V
public final fun setBorderRadius (Lcom/facebook/react/views/text/ReactTextView;IF)V
public final fun setBorderRadius (Lcom/facebook/react/views/text/ReactTextView;ILcom/facebook/react/bridge/Dynamic;)V
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 @@ -194,6 +197,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 @@ -246,6 +253,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 @@ -575,6 +584,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 @@ -339,6 +339,20 @@ public constructor(
}
}

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

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

@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 @@ -608,6 +610,34 @@ 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+.
@SuppressLint("InlinedApi")
internal fun getLineBreakStyle(lineBreakStyle: String?): Int =
when (lineBreakStyle) {
null,
"none" -> 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 android_lineBreakStyle: $lineBreakStyle")
LineBreakConfig.LINE_BREAK_STYLE_NONE
}
}

@SuppressLint("InlinedApi")
internal fun getLineBreakWordStyle(lineBreakWordStyle: String?): Int =
when (lineBreakWordStyle) {
null,
"none" -> LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE
"phrase" -> LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE
else -> {
FLog.w(ReactConstants.TAG, "Invalid android_lineBreakWordStyle: $lineBreakWordStyle")
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_STYLE: Int = 11
const val PA_KEY_LINE_BREAK_WORD_STYLE: Int = 12

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

Expand Down Expand Up @@ -847,6 +850,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 @@ -893,6 +898,8 @@ internal object TextLayoutManager {
includeFontPadding,
textBreakStrategy,
hyphenationFrequency,
lineBreakStyle,
lineBreakWordStyle,
alignment,
justificationMode,
ellipsizeMode,
Expand All @@ -911,6 +918,8 @@ internal object TextLayoutManager {
includeFontPadding,
textBreakStrategy,
hyphenationFrequency,
lineBreakStyle,
lineBreakWordStyle,
alignment,
justificationMode,
ellipsizeMode,
Expand Down Expand Up @@ -961,6 +970,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 @@ -988,6 +999,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 @@ -1147,6 +1173,18 @@ internal object TextLayoutManager {
TextAttributeProps.getHyphenationFrequency(
paragraphAttributes.getString(PA_KEY_HYPHENATION_FREQUENCY),
)
val lineBreakStyle =
TextAttributeProps.getLineBreakStyle(
if (paragraphAttributes.contains(PA_KEY_LINE_BREAK_STYLE))
paragraphAttributes.getString(PA_KEY_LINE_BREAK_STYLE)
else null,
)
val lineBreakWordStyle =
TextAttributeProps.getLineBreakWordStyle(
if (paragraphAttributes.contains(PA_KEY_LINE_BREAK_WORD_STYLE))
paragraphAttributes.getString(PA_KEY_LINE_BREAK_WORD_STYLE)
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 @@ -1189,6 +1227,8 @@ internal object TextLayoutManager {
includeFontPadding,
textBreakStrategy,
hyphenationFrequency,
lineBreakStyle,
lineBreakWordStyle,
alignment,
justificationMode,
paint,
Expand All @@ -1204,6 +1244,8 @@ internal object TextLayoutManager {
includeFontPadding,
textBreakStrategy,
hyphenationFrequency,
lineBreakStyle,
lineBreakWordStyle,
alignment,
justificationMode,
ellipsizeMode,
Expand All @@ -1227,6 +1269,8 @@ internal object TextLayoutManager {
includeFontPadding,
textBreakStrategy,
hyphenationFrequency,
lineBreakStyle,
lineBreakWordStyle,
alignment,
justificationMode,
ellipsizeMode,
Expand Down Expand Up @@ -1349,6 +1393,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 @@ -1413,6 +1459,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