Skip to content

Commit 55ad87f

Browse files
Apply the ellipsize only when adjusted text still exceeds numberOfLines
1 parent 3d41699 commit 55ad87f

4 files changed

Lines changed: 125 additions & 25 deletions

File tree

‎packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java‎

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie
7676
private int mLinkifyMaskType;
7777
private boolean mTextIsSelectable;
7878
private boolean mShouldAdjustSpannableFontSize;
79+
private boolean mAdjustedTextExceedsLineLimit;
7980
private Overflow mOverflow = Overflow.VISIBLE;
8081

8182
private @Nullable Spannable mSpanned;
@@ -130,6 +131,7 @@ private void initView() {
130131
mLinkifyMaskType = 0;
131132
mTextIsSelectable = false;
132133
mShouldAdjustSpannableFontSize = false;
134+
mAdjustedTextExceedsLineLimit = false;
133135
mEllipsizeLocation = TextUtils.TruncateAt.END;
134136
mFontSize = Float.NaN;
135137
mMinimumFontSize = Float.NaN;
@@ -232,22 +234,27 @@ protected void onDraw(Canvas canvas) {
232234
Spannable spanned = getSpanned();
233235
if (mAdjustsFontSizeToFit && spanned != null && mShouldAdjustSpannableFontSize) {
234236
mShouldAdjustSpannableFontSize = false;
235-
TextLayoutManager.adjustSpannableFontToFit(
236-
spanned,
237-
getWidth(),
238-
YogaMeasureMode.EXACTLY,
239-
getHeight(),
240-
YogaMeasureMode.EXACTLY,
241-
mMinimumFontSize,
242-
mNumberOfLines,
243-
getIncludeFontPadding(),
244-
getBreakStrategy(),
245-
getHyphenationFrequency(),
246-
// always passing ALIGN_NORMAL here should be fine, since this method doesn't depend on
247-
// how exactly lines are aligned, just their width
248-
Layout.Alignment.ALIGN_NORMAL,
249-
(Build.VERSION.SDK_INT < Build.VERSION_CODES.O) ? -1 : getJustificationMode(),
250-
getPaint());
237+
boolean exceedsLineLimit =
238+
TextLayoutManager.adjustSpannableFontToFit(
239+
spanned,
240+
getWidth(),
241+
YogaMeasureMode.EXACTLY,
242+
getHeight(),
243+
YogaMeasureMode.EXACTLY,
244+
mMinimumFontSize,
245+
mNumberOfLines,
246+
getIncludeFontPadding(),
247+
getBreakStrategy(),
248+
getHyphenationFrequency(),
249+
// always passing ALIGN_NORMAL here should be fine, since this method doesn't
250+
// depend on how exactly lines are aligned, just their width
251+
Layout.Alignment.ALIGN_NORMAL,
252+
(Build.VERSION.SDK_INT < Build.VERSION_CODES.O) ? -1 : getJustificationMode(),
253+
getPaint());
254+
if (exceedsLineLimit != mAdjustedTextExceedsLineLimit) {
255+
mAdjustedTextExceedsLineLimit = exceedsLineLimit;
256+
updateView();
257+
}
251258
setText(spanned);
252259
}
253260

@@ -585,7 +592,10 @@ public void setEllipsizeLocation(@Nullable TextUtils.TruncateAt ellipsizeLocatio
585592
public void updateView() {
586593
@Nullable
587594
TextUtils.TruncateAt ellipsizeLocation =
588-
mNumberOfLines == ViewDefaults.NUMBER_OF_LINES ? null : mEllipsizeLocation;
595+
mNumberOfLines == ViewDefaults.NUMBER_OF_LINES
596+
|| (mAdjustsFontSizeToFit && !mAdjustedTextExceedsLineLimit)
597+
? null
598+
: mEllipsizeLocation;
589599
setEllipsize(ellipsizeLocation);
590600
}
591601

‎packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.kt‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,7 +1345,7 @@ internal object TextLayoutManager {
13451345
alignment: Layout.Alignment,
13461346
justificationMode: Int,
13471347
paint: TextPaint,
1348-
) {
1348+
): Boolean {
13491349
var boring = isBoring(text, paint)
13501350
var layout: Layout
13511351

@@ -1405,11 +1405,6 @@ internal object TextLayoutManager {
14051405
paint,
14061406
)
14071407

1408-
if (intervalStart == intervalEnd) {
1409-
// everything is updated at this point
1410-
break
1411-
}
1412-
14131408
val singleLineTextExceedsWidth = text.length == 1 && layout.getLineWidth(0) > width
14141409
val exceedsHeight =
14151410
heightYogaMeasureMode != YogaMeasureMode.UNDEFINED && layout.height > height
@@ -1418,6 +1413,11 @@ internal object TextLayoutManager {
14181413
maximumNumberOfLines != 0 &&
14191414
layout.lineCount > maximumNumberOfLines
14201415

1416+
if (intervalStart == intervalEnd) {
1417+
// everything is updated at this point
1418+
return exceedsMaximumNumberOfLines || singleLineTextExceedsWidth
1419+
}
1420+
14211421
if (
14221422
currentFontSize > minimumFontSize &&
14231423
(exceedsMaximumNumberOfLines || exceedsHeight || singleLineTextExceedsWidth)

‎packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/text/ReactTextViewTest.kt‎

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,15 +111,34 @@ class ReactTextViewTest {
111111
}
112112

113113
@Test
114-
fun adjustsFontSizeToFitKeepsEllipsizeLocation() {
114+
fun adjustsFontSizeToFitEllipsizesOnlyWhileTextExceedsNumberOfLines() {
115+
val text = SpannableString("Hello\nworld")
116+
text.setSpan(ReactAbsoluteSizeSpan(40), 0, text.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
117+
115118
val view = TestReactTextView(RuntimeEnvironment.getApplication())
119+
view.layoutParams =
120+
ViewGroup.LayoutParams(
121+
ViewGroup.LayoutParams.WRAP_CONTENT,
122+
ViewGroup.LayoutParams.WRAP_CONTENT,
123+
)
124+
view.setTextColor(Color.BLACK)
125+
view.setMinimumFontSize(20f)
116126
view.setNumberOfLines(1)
117127
view.setEllipsizeLocation(TextUtils.TruncateAt.END)
118128
view.setAdjustFontSizeToFit(true)
119-
129+
view.setSpanned(text)
130+
view.text = text
120131
view.updateView()
121132

133+
// Two lines can never fit in one, so the ellipsize applies.
134+
layoutAndDraw(view, 200, 60)
122135
assertThat(view.ellipsize).isEqualTo(TextUtils.TruncateAt.END)
136+
137+
// Once two lines are allowed the text fits, so the ellipsize is dropped again.
138+
view.setNumberOfLines(2)
139+
view.updateView()
140+
layoutAndDraw(view, 200, 60)
141+
assertThat(view.ellipsize).isNull()
123142
}
124143

125144
private fun layoutAndDraw(view: TestReactTextView, width: Int, height: Int) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.react.views.text
9+
10+
import android.text.Layout
11+
import android.text.SpannableString
12+
import android.text.Spanned
13+
import android.text.TextPaint
14+
import com.facebook.react.common.ReactConstants
15+
import com.facebook.react.views.text.internal.span.ReactAbsoluteSizeSpan
16+
import com.facebook.yoga.YogaMeasureMode
17+
import org.assertj.core.api.Assertions.assertThat
18+
import org.junit.Test
19+
import org.junit.runner.RunWith
20+
import org.robolectric.RobolectricTestRunner
21+
22+
@RunWith(RobolectricTestRunner::class)
23+
class TextLayoutManagerAdjustSpannableFontToFitTest {
24+
25+
@Test
26+
fun `reports that the line limit is not exceeded when the text fits`() {
27+
val exceedsLineLimit = adjustToFit("Hello world", maximumNumberOfLines = 1)
28+
29+
assertThat(exceedsLineLimit).isFalse()
30+
}
31+
32+
@Test
33+
fun `reports that the line limit is exceeded when the text has more lines at the minimum size`() {
34+
val exceedsLineLimit = adjustToFit("Hello\nworld", maximumNumberOfLines = 1)
35+
36+
assertThat(exceedsLineLimit).isTrue()
37+
}
38+
39+
@Test
40+
fun `reports that the line limit is not exceeded when only the height is exceeded`() {
41+
val exceedsLineLimit =
42+
adjustToFit("Hello world", height = 1f, maximumNumberOfLines = ReactConstants.UNSET)
43+
44+
assertThat(exceedsLineLimit).isFalse()
45+
}
46+
47+
private fun adjustToFit(
48+
string: String,
49+
height: Float = 10_000f,
50+
maximumNumberOfLines: Int,
51+
): Boolean {
52+
val text = SpannableString(string)
53+
text.setSpan(ReactAbsoluteSizeSpan(40), 0, text.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
54+
55+
return TextLayoutManager.adjustSpannableFontToFit(
56+
text,
57+
10_000f,
58+
YogaMeasureMode.EXACTLY,
59+
height,
60+
YogaMeasureMode.EXACTLY,
61+
20f,
62+
maximumNumberOfLines,
63+
true,
64+
Layout.BREAK_STRATEGY_SIMPLE,
65+
Layout.HYPHENATION_FREQUENCY_NONE,
66+
Layout.Alignment.ALIGN_NORMAL,
67+
0,
68+
TextPaint(TextPaint.ANTI_ALIAS_FLAG).apply { textSize = 40f },
69+
)
70+
}
71+
}

0 commit comments

Comments
 (0)