Exclude trailing whitespace on measuring text width (#37590)

Summary:
Multiline text in Android shows some extra space. It's easily noticeable when you set the text `alignSelf` to `flex-start`. This is because we are using `layout.getLineWidth` which will include trailing whitespace.
<img width="300" alt="image" src="https://github.com/facebook/react-native/assets/50919443/8939092b-caef-4ad8-9b34-2ccef5d20968">

Based on Android doc, `getLineMax` exclude trailing whitespace.
<img width="625" alt="image" src="https://github.com/facebook/react-native/assets/50919443/0b32e842-5fab-4fc7-8fd9-299877b9c553">

## Changelog:

<!-- Help reviewers and the release process by writing your own changelog entry.

Pick one each for the category and type tags:

[ANDROID] [FIXED] - Exclude trailing whitespace from newline character on measuring text line width

For more details, see:
https://reactnative.dev/contributing/changelogs-in-pull-requests
-->

[ANDROID] [FIXED] - Exclude trailing whitespace from newline character on measuring text line width

Pull Request resolved: https://github.com/facebook/react-native/pull/37590

Test Plan:
After applying the changes:
<img width="300" alt="image" src="https://github.com/facebook/react-native/assets/50919443/bfbf52b0-7e7d-4c89-8958-6af38d8bc1c7">

Code snippet:
```
<Text style={{backgroundColor: 'red', alignSelf: 'flex-start', color: 'white'}}>
    1{'\n'}
</Text>
```

Reviewed By: rshest

Differential Revision: D46501420

Pulled By: NickGerleman

fbshipit-source-id: fba4acd38747f09791ce8af4439ee88ab12ac827
This commit is contained in:
Bernhard Owen Josephus
2023-06-08 12:06:38 -07:00
committed by Facebook GitHub Bot
parent 6b62f12ce9
commit 2674d9bf7c
5 changed files with 21 additions and 7 deletions
@@ -42,12 +42,14 @@ public class FontMetricsUtil {
X_HEIGHT_MEASUREMENT_TEXT, 0, X_HEIGHT_MEASUREMENT_TEXT.length(), xHeightBounds);
double xHeight = xHeightBounds.height() / AMPLIFICATION_FACTOR / dm.density;
for (int i = 0; i < layout.getLineCount(); i++) {
boolean endsWithNewLine = text.charAt(layout.getLineEnd(i) - 1) == '\n';
float lineWidth = endsWithNewLine ? layout.getLineMax(i) : layout.getLineWidth(i);
Rect bounds = new Rect();
layout.getLineBounds(i, bounds);
WritableMap line = Arguments.createMap();
line.putDouble("x", layout.getLineLeft(i) / dm.density);
line.putDouble("y", bounds.top / dm.density);
line.putDouble("width", layout.getLineWidth(i) / dm.density);
line.putDouble("width", lineWidth / dm.density);
line.putDouble("height", bounds.height() / dm.density);
line.putDouble("descender", layout.getLineDescent(i) / dm.density);
line.putDouble("ascender", -layout.getLineAscent(i) / dm.density);
@@ -135,7 +135,9 @@ public class ReactTextShadowNode extends ReactBaseTextShadowNode {
layoutWidth = width;
} else {
for (int lineIndex = 0; lineIndex < lineCount; lineIndex++) {
float lineWidth = layout.getLineWidth(lineIndex);
boolean endsWithNewLine = text.charAt(layout.getLineEnd(lineIndex) - 1) == '\n';
float lineWidth =
endsWithNewLine ? layout.getLineMax(lineIndex) : layout.getLineWidth(lineIndex);
if (lineWidth > layoutWidth) {
layoutWidth = lineWidth;
}
@@ -267,11 +267,13 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie
// the last offset in the layout will result in an endless loop. Work around
// this bug by avoiding getPrimaryHorizontal in that case.
if (start == text.length() - 1) {
boolean endsWithNewLine = text.charAt(layout.getLineEnd(line) - 1) == '\n';
float lineWidth = endsWithNewLine ? layout.getLineMax(line) : layout.getLineWidth(line);
placeholderHorizontalPosition =
isRtlParagraph
// Equivalent to `layout.getLineLeft(line)` but `getLineLeft` returns incorrect
// values when the paragraph is RTL and `setSingleLine(true)`.
? textViewWidth - (int) layout.getLineWidth(line)
? textViewWidth - (int) lineWidth
: (int) layout.getLineRight(line) - width;
} else {
// The direction of the paragraph may not be exactly the direction the string is heading
@@ -407,7 +407,9 @@ public class TextLayoutManager {
calculatedWidth = width;
} else {
for (int lineIndex = 0; lineIndex < calculatedLineCount; lineIndex++) {
float lineWidth = layout.getLineWidth(lineIndex);
boolean endsWithNewLine = text.charAt(layout.getLineEnd(lineIndex) - 1) == '\n';
float lineWidth =
endsWithNewLine ? layout.getLineMax(lineIndex) : layout.getLineWidth(lineIndex);
if (lineWidth > calculatedWidth) {
calculatedWidth = lineWidth;
}
@@ -462,11 +464,13 @@ public class TextLayoutManager {
// the last offset in the layout will result in an endless loop. Work around
// this bug by avoiding getPrimaryHorizontal in that case.
if (start == text.length() - 1) {
boolean endsWithNewLine = text.charAt(layout.getLineEnd(line) - 1) == '\n';
float lineWidth = endsWithNewLine ? layout.getLineMax(line) : layout.getLineWidth(line);
placeholderLeftPosition =
isRtlParagraph
// Equivalent to `layout.getLineLeft(line)` but `getLineLeft` returns incorrect
// values when the paragraph is RTL and `setSingleLine(true)`.
? calculatedWidth - layout.getLineWidth(line)
? calculatedWidth - lineWidth
: layout.getLineRight(line) - placeholderWidth;
} else {
// The direction of the paragraph may not be exactly the direction the string is heading
@@ -429,7 +429,9 @@ public class TextLayoutManagerMapBuffer {
calculatedWidth = width;
} else {
for (int lineIndex = 0; lineIndex < calculatedLineCount; lineIndex++) {
float lineWidth = layout.getLineWidth(lineIndex);
boolean endsWithNewLine = text.charAt(layout.getLineEnd(lineIndex) - 1) == '\n';
float lineWidth =
endsWithNewLine ? layout.getLineMax(lineIndex) : layout.getLineWidth(lineIndex);
if (lineWidth > calculatedWidth) {
calculatedWidth = lineWidth;
}
@@ -484,12 +486,14 @@ public class TextLayoutManagerMapBuffer {
// the last offset in the layout will result in an endless loop. Work around
// this bug by avoiding getPrimaryHorizontal in that case.
if (start == text.length() - 1) {
boolean endsWithNewLine = text.charAt(layout.getLineEnd(line) - 1) == '\n';
float lineWidth = endsWithNewLine ? layout.getLineMax(line) : layout.getLineWidth(line);
placeholderLeftPosition =
isRtlParagraph
// Equivalent to `layout.getLineLeft(line)` but `getLineLeft` returns
// incorrect
// values when the paragraph is RTL and `setSingleLine(true)`.
? calculatedWidth - layout.getLineWidth(line)
? calculatedWidth - lineWidth
: layout.getLineRight(line) - placeholderWidth;
} else {
// The direction of the paragraph may not be exactly the direction the string is