From 15400510cfb7fe9ff0f670eb30330a102e175eb6 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 26 May 2023 19:13:43 +0800 Subject: [PATCH 1/4] ignore trailing whitespace on measuring the text width --- .../java/com/facebook/react/views/text/FontMetricsUtil.java | 2 +- .../com/facebook/react/views/text/ReactTextShadowNode.java | 2 +- .../java/com/facebook/react/views/text/ReactTextView.java | 2 +- .../java/com/facebook/react/views/text/TextLayoutManager.java | 4 ++-- .../facebook/react/views/text/TextLayoutManagerMapBuffer.java | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/FontMetricsUtil.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/FontMetricsUtil.java index f19a1b042588..70e010f5abff 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/FontMetricsUtil.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/FontMetricsUtil.java @@ -47,7 +47,7 @@ public static WritableArray getFontMetrics( 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", layout.getLineMax(i) / 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); 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 2e237f01970f..91c9386d601f 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 @@ -135,7 +135,7 @@ public long measure( layoutWidth = width; } else { for (int lineIndex = 0; lineIndex < lineCount; lineIndex++) { - float lineWidth = layout.getLineWidth(lineIndex); + float lineWidth = layout.getLineMax(lineIndex); if (lineWidth > layoutWidth) { layoutWidth = lineWidth; } 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 6cda1af267c0..2049290a92ee 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 @@ -271,7 +271,7 @@ protected void onLayout( 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) layout.getLineMax(line) : (int) layout.getLineRight(line) - width; } else { // The direction of the paragraph may not be exactly the direction the string is heading 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 25bfe983255c..7cbb02701355 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 @@ -407,7 +407,7 @@ public static long measureText( calculatedWidth = width; } else { for (int lineIndex = 0; lineIndex < calculatedLineCount; lineIndex++) { - float lineWidth = layout.getLineWidth(lineIndex); + float lineWidth = layout.getLineMax(lineIndex); if (lineWidth > calculatedWidth) { calculatedWidth = lineWidth; } @@ -466,7 +466,7 @@ public static long measureText( isRtlParagraph // Equivalent to `layout.getLineLeft(line)` but `getLineLeft` returns incorrect // values when the paragraph is RTL and `setSingleLine(true)`. - ? calculatedWidth - layout.getLineWidth(line) + ? calculatedWidth - layout.getLineMax(line) : layout.getLineRight(line) - placeholderWidth; } else { // The direction of the paragraph may not be exactly the direction the string is heading diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java index 717037555972..5f672f0b449b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java @@ -429,7 +429,7 @@ public static long measureText( calculatedWidth = width; } else { for (int lineIndex = 0; lineIndex < calculatedLineCount; lineIndex++) { - float lineWidth = layout.getLineWidth(lineIndex); + float lineWidth = layout.getLineMax(lineIndex); if (lineWidth > calculatedWidth) { calculatedWidth = lineWidth; } @@ -489,7 +489,7 @@ public static long measureText( // Equivalent to `layout.getLineLeft(line)` but `getLineLeft` returns // incorrect // values when the paragraph is RTL and `setSingleLine(true)`. - ? calculatedWidth - layout.getLineWidth(line) + ? calculatedWidth - layout.getLineMax(line) : layout.getLineRight(line) - placeholderWidth; } else { // The direction of the paragraph may not be exactly the direction the string is From 8cb8498a24ca45d9ba1265f90d5a30443ac87678 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 2 Jun 2023 16:30:24 +0800 Subject: [PATCH 2/4] only exclude whitespace from newline character on measuring text line width --- .../com/facebook/react/views/text/FontMetricsUtil.java | 4 +++- .../facebook/react/views/text/ReactTextShadowNode.java | 6 +++++- .../com/facebook/react/views/text/ReactTextView.java | 4 +++- .../facebook/react/views/text/TextLayoutManager.java | 10 ++++++++-- .../react/views/text/TextLayoutManagerMapBuffer.java | 10 ++++++++-- 5 files changed, 27 insertions(+), 7 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/FontMetricsUtil.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/FontMetricsUtil.java index 70e010f5abff..af97ee9cb91f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/FontMetricsUtil.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/FontMetricsUtil.java @@ -42,12 +42,14 @@ public static WritableArray getFontMetrics( 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.getLineMax(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); 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 91c9386d601f..8328b00f7657 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 @@ -135,7 +135,11 @@ public long measure( layoutWidth = width; } else { for (int lineIndex = 0; lineIndex < lineCount; lineIndex++) { - float lineWidth = layout.getLineMax(lineIndex); + boolean endsWithNewLine = text.charAt(layout.getLineEnd(lineIndex) - 1) == '\n'; + float lineWidth = + endsWithNewLine + ? layout.getLineMax(lineIndex) + : layout.getLineWidth(lineIndex); if (lineWidth > layoutWidth) { layoutWidth = lineWidth; } 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 2049290a92ee..06bd603bc705 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 @@ -267,11 +267,13 @@ protected void onLayout( // 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.getLineMax(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 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 7cbb02701355..bcf8c785a7d8 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 @@ -407,7 +407,11 @@ public static long measureText( calculatedWidth = width; } else { for (int lineIndex = 0; lineIndex < calculatedLineCount; lineIndex++) { - float lineWidth = layout.getLineMax(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 +466,13 @@ public static long measureText( // 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.getLineMax(line) + ? calculatedWidth - lineWidth : layout.getLineRight(line) - placeholderWidth; } else { // The direction of the paragraph may not be exactly the direction the string is heading diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java index 5f672f0b449b..1f5e8333b5b1 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java @@ -429,7 +429,11 @@ public static long measureText( calculatedWidth = width; } else { for (int lineIndex = 0; lineIndex < calculatedLineCount; lineIndex++) { - float lineWidth = layout.getLineMax(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 +488,14 @@ public static long measureText( // 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.getLineMax(line) + ? calculatedWidth - lineWidth : layout.getLineRight(line) - placeholderWidth; } else { // The direction of the paragraph may not be exactly the direction the string is From 60fe7e56cb7f1d7e0dab2bbf37bbfca833c6ce8a Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 9 Jun 2023 13:04:31 +0800 Subject: [PATCH 3/4] fix index oob on empty text --- .../java/com/facebook/react/views/text/FontMetricsUtil.java | 2 +- .../com/facebook/react/views/text/ReactTextShadowNode.java | 3 ++- .../java/com/facebook/react/views/text/ReactTextView.java | 3 ++- .../com/facebook/react/views/text/TextLayoutManager.java | 6 ++++-- .../react/views/text/TextLayoutManagerMapBuffer.java | 6 ++++-- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/FontMetricsUtil.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/FontMetricsUtil.java index af97ee9cb91f..84dc328edab3 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/FontMetricsUtil.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/FontMetricsUtil.java @@ -42,7 +42,7 @@ public static WritableArray getFontMetrics( 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'; + boolean endsWithNewLine = text.length() > 0 && text.charAt(layout.getLineEnd(i) - 1) == '\n'; float lineWidth = endsWithNewLine ? layout.getLineMax(i) : layout.getLineWidth(i); Rect bounds = new Rect(); layout.getLineBounds(i, bounds); 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 8328b00f7657..caaf8de55bee 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 @@ -135,7 +135,8 @@ public long measure( layoutWidth = width; } else { for (int lineIndex = 0; lineIndex < lineCount; lineIndex++) { - boolean endsWithNewLine = text.charAt(layout.getLineEnd(lineIndex) - 1) == '\n'; + boolean endsWithNewLine = text.length() > 0 + && text.charAt(layout.getLineEnd(lineIndex) - 1) == '\n'; float lineWidth = endsWithNewLine ? layout.getLineMax(lineIndex) 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 06bd603bc705..be48f7f57999 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 @@ -267,7 +267,8 @@ protected void onLayout( // 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'; + boolean endsWithNewLine = text.length() > 0 + && text.charAt(layout.getLineEnd(line) - 1) == '\n'; float lineWidth = endsWithNewLine ? layout.getLineMax(line) : layout.getLineWidth(line); placeholderHorizontalPosition = isRtlParagraph 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 bcf8c785a7d8..eab7680f8848 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 @@ -407,7 +407,8 @@ public static long measureText( calculatedWidth = width; } else { for (int lineIndex = 0; lineIndex < calculatedLineCount; lineIndex++) { - boolean endsWithNewLine = text.charAt(layout.getLineEnd(lineIndex) - 1) == '\n'; + boolean endsWithNewLine = text.length() > 0 + && text.charAt(layout.getLineEnd(lineIndex) - 1) == '\n'; float lineWidth = endsWithNewLine ? layout.getLineMax(lineIndex) @@ -466,7 +467,8 @@ public static long measureText( // 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'; + boolean endsWithNewLine = text.length() > 0 + && text.charAt(layout.getLineEnd(line) - 1) == '\n'; float lineWidth = endsWithNewLine ? layout.getLineMax(line) : layout.getLineWidth(line); placeholderLeftPosition = isRtlParagraph diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java index 1f5e8333b5b1..201c5f33865e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java @@ -429,7 +429,8 @@ public static long measureText( calculatedWidth = width; } else { for (int lineIndex = 0; lineIndex < calculatedLineCount; lineIndex++) { - boolean endsWithNewLine = text.charAt(layout.getLineEnd(lineIndex) - 1) == '\n'; + boolean endsWithNewLine = text.length() > 0 + && text.charAt(layout.getLineEnd(lineIndex) - 1) == '\n'; float lineWidth = endsWithNewLine ? layout.getLineMax(lineIndex) @@ -488,7 +489,8 @@ public static long measureText( // 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'; + boolean endsWithNewLine = text.length() > 0 + && text.charAt(layout.getLineEnd(line) - 1) == '\n'; float lineWidth = endsWithNewLine ? layout.getLineMax(line) : layout.getLineWidth(line); placeholderLeftPosition = isRtlParagraph From 5fd7ff9cc6ffae532c6d8a9a4f74450870bcb4e4 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 9 Jun 2023 13:08:19 +0800 Subject: [PATCH 4/4] rm trailing space --- .../java/com/facebook/react/views/text/TextLayoutManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 eab7680f8848..2cb875ceadd9 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 @@ -467,7 +467,7 @@ public static long measureText( // 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.length() > 0 + boolean endsWithNewLine = text.length() > 0 && text.charAt(layout.getLineEnd(line) - 1) == '\n'; float lineWidth = endsWithNewLine ? layout.getLineMax(line) : layout.getLineWidth(line); placeholderLeftPosition =