diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java
index be27e77d13956a..918716670230f5 100644
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java
@@ -270,6 +270,10 @@ private Layout measureSpannedText(Spannable text, float width, YogaMeasureMode w
.setBreakStrategy(mTextBreakStrategy)
.setHyphenationFrequency(mHyphenationFrequency);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ builder.setJustificationMode(mJustificationMode);
+ }
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
builder.setUseLineSpacingFromFallbacks(true);
}
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java
index fbbc11768a3daa..cb735fc85630a5 100644
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java
@@ -380,8 +380,9 @@ protected void onDraw(Canvas canvas) {
getBreakStrategy(),
getHyphenationFrequency(),
// always passing ALIGN_NORMAL here should be fine, since this method doesn't depend on
- // how exacly lines are aligned, just their width
+ // how exactly lines are aligned, just their width
Layout.Alignment.ALIGN_NORMAL,
+ (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) ? -1 : getJustificationMode(),
getPaint());
setText(getSpanned());
}
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java
index 63bb805fcac1c4..882114336146aa 100644
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java
@@ -146,13 +146,41 @@ public static boolean isRTL(MapBuffer attributedString) {
== LayoutDirection.RTL;
}
- private static Layout.Alignment getTextAlignment(MapBuffer attributedString, Spannable spanned) {
+ @Nullable
+ private static String getTextAlignmentAttr(MapBuffer attributedString) {
// TODO: Don't read AS_KEY_FRAGMENTS, which may be expensive, and is not present when using
// cached Spannable
if (!attributedString.contains(AS_KEY_FRAGMENTS)) {
- return Layout.Alignment.ALIGN_NORMAL;
+ return null;
}
+ MapBuffer fragments = attributedString.getMapBuffer(AS_KEY_FRAGMENTS);
+ if (fragments.getCount() != 0) {
+ MapBuffer fragment = fragments.getMapBuffer(0);
+ MapBuffer textAttributes = fragment.getMapBuffer(FR_KEY_TEXT_ATTRIBUTES);
+
+ if (textAttributes.contains(TextAttributeProps.TA_KEY_ALIGNMENT)) {
+ return textAttributes.getString(TextAttributeProps.TA_KEY_ALIGNMENT);
+ }
+ }
+
+ return null;
+ }
+
+ private static int getTextJustificationMode(@Nullable String alignmentAttr) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ return -1;
+ }
+
+ if (alignmentAttr != null && alignmentAttr.equals("justified")) {
+ return Layout.JUSTIFICATION_MODE_INTER_WORD;
+ }
+
+ return Layout.JUSTIFICATION_MODE_NONE;
+ }
+
+ private static Layout.Alignment getTextAlignment(
+ MapBuffer attributedString, Spannable spanned, @Nullable String alignmentAttr) {
// Android will align text based on the script, so normal and opposite alignment needs to be
// swapped when the directions of paragraph and script don't match.
// I.e. paragraph is LTR but script is RTL, text needs to be aligned to the left, which means
@@ -165,23 +193,15 @@ private static Layout.Alignment getTextAlignment(MapBuffer attributedString, Spa
Layout.Alignment alignment =
swapNormalAndOpposite ? Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_NORMAL;
- MapBuffer fragments = attributedString.getMapBuffer(AS_KEY_FRAGMENTS);
- if (fragments.getCount() != 0) {
- MapBuffer fragment = fragments.getMapBuffer(0);
- MapBuffer textAttributes = fragment.getMapBuffer(FR_KEY_TEXT_ATTRIBUTES);
+ if (alignmentAttr == null) {
+ return alignment;
+ }
- if (textAttributes.contains(TextAttributeProps.TA_KEY_ALIGNMENT)) {
- String alignmentAttr = textAttributes.getString(TextAttributeProps.TA_KEY_ALIGNMENT);
-
- if (alignmentAttr.equals("center")) {
- alignment = Layout.Alignment.ALIGN_CENTER;
- } else if (alignmentAttr.equals("right")) {
- alignment =
- swapNormalAndOpposite
- ? Layout.Alignment.ALIGN_NORMAL
- : Layout.Alignment.ALIGN_OPPOSITE;
- }
- }
+ if (alignmentAttr.equals("center")) {
+ alignment = Layout.Alignment.ALIGN_CENTER;
+ } else if (alignmentAttr.equals("right")) {
+ alignment =
+ swapNormalAndOpposite ? Layout.Alignment.ALIGN_NORMAL : Layout.Alignment.ALIGN_OPPOSITE;
}
return alignment;
@@ -190,7 +210,8 @@ private static Layout.Alignment getTextAlignment(MapBuffer attributedString, Spa
public static int getTextGravity(
MapBuffer attributedString, Spannable spanned, int defaultValue) {
int gravity = defaultValue;
- Layout.Alignment alignment = getTextAlignment(attributedString, spanned);
+ @Nullable String alignmentAttr = getTextAlignmentAttr(attributedString);
+ Layout.Alignment alignment = getTextAlignment(attributedString, spanned, alignmentAttr);
// depending on whether the script is LTR or RTL, ALIGN_NORMAL and ALIGN_OPPOSITE may mean
// different things
@@ -363,6 +384,7 @@ private static Layout createLayout(
int textBreakStrategy,
int hyphenationFrequency,
Layout.Alignment alignment,
+ int justificationMode,
TextPaint paint) {
Layout layout;
@@ -420,6 +442,10 @@ private static Layout createLayout(
.setTextDirection(
isScriptRTL ? TextDirectionHeuristics.RTL : TextDirectionHeuristics.LTR);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ builder.setJustificationMode(justificationMode);
+ }
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
builder.setUseLineSpacingFromFallbacks(true);
}
@@ -506,7 +532,9 @@ private static Layout createLayout(
? paragraphAttributes.getInt(PA_KEY_MAX_NUMBER_OF_LINES)
: ReactConstants.UNSET;
- Layout.Alignment alignment = getTextAlignment(attributedString, text);
+ @Nullable String alignmentAttr = getTextAlignmentAttr(attributedString);
+ Layout.Alignment alignment = getTextAlignment(attributedString, text, alignmentAttr);
+ int justificationMode = getTextJustificationMode(alignmentAttr);
if (adjustFontSizeToFit) {
double minimumFontSize =
@@ -526,6 +554,7 @@ private static Layout createLayout(
textBreakStrategy,
hyphenationFrequency,
alignment,
+ justificationMode,
paint);
}
@@ -538,6 +567,7 @@ private static Layout createLayout(
textBreakStrategy,
hyphenationFrequency,
alignment,
+ justificationMode,
paint);
}
@@ -553,6 +583,7 @@ private static Layout createLayout(
int textBreakStrategy,
int hyphenationFrequency,
Layout.Alignment alignment,
+ int justificationMode,
TextPaint paint) {
BoringLayout.Metrics boring = BoringLayout.isBoring(text, paint);
Layout layout =
@@ -565,6 +596,7 @@ private static Layout createLayout(
textBreakStrategy,
hyphenationFrequency,
alignment,
+ justificationMode,
paint);
// Minimum font size is 4pts to match the iOS implementation.
@@ -613,6 +645,7 @@ private static Layout createLayout(
textBreakStrategy,
hyphenationFrequency,
alignment,
+ justificationMode,
paint);
}
}
diff --git a/packages/rn-tester/js/examples/Text/TextExample.android.js b/packages/rn-tester/js/examples/Text/TextExample.android.js
index 751052f4ec8121..b9cb1a2f126ced 100644
--- a/packages/rn-tester/js/examples/Text/TextExample.android.js
+++ b/packages/rn-tester/js/examples/Text/TextExample.android.js
@@ -696,7 +696,7 @@ function NestedExample(props: {}): React.Node {
function TextAlignExample(props: {}): React.Node {
return (
- <>
+
auto (default) - english LTR
أحب اللغة العربية auto (default) - arabic RTL
@@ -717,7 +717,7 @@ function TextAlignExample(props: {}): React.Node {
as you can see all of the lines except the last one span the available
width of the parent container.
- >
+
);
}
diff --git a/packages/rn-tester/js/examples/Text/TextExample.ios.js b/packages/rn-tester/js/examples/Text/TextExample.ios.js
index 0316e0b47eb36d..6b65d167894f26 100644
--- a/packages/rn-tester/js/examples/Text/TextExample.ios.js
+++ b/packages/rn-tester/js/examples/Text/TextExample.ios.js
@@ -60,7 +60,9 @@ class TextAlignRTLExample extends React.Component<
const {isRTL} = this.state;
const toggleRTL = () => this.setState({isRTL: !isRTL});
return (
-
+
auto (default) - english LTR
{'\u0623\u062D\u0628 \u0627\u0644\u0644\u063A\u0629 ' +
@@ -1254,6 +1256,7 @@ const examples = [
},
{
title: 'Text Align with RTL',
+ name: 'textAlign',
render: function (): React.Node {
return ;
},