Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix single line text not fully rendered #41770

Open
wants to merge 38 commits into
base: main
Choose a base branch
from

Conversation

fabOnReact
Copy link
Contributor

@fabOnReact fabOnReact commented Dec 3, 2023

Summary:

Please re-state the problem that we are trying to solve in this issue.

ReactTextShadowNode#measure calculates the wrong width for single line Text that has an ellipsis (fixes #39722).

What is the root cause of that problem?

React Native sets the wrong width in the ReactTextShadowNode onMeasure function for single line Text, demonstrated with:

The android minimum reproducible example:

  1. Reproduces the react-native issue using the same implementation of ReactTextShadowNode onMeasure, which calculates the wrong text width with StaticLayout (screenshot)
  2. Demonstrates that the default TextView implementation of onMeasure does not have this issue (screenshot) (the issue does not reproduce when commenting the implementation of onMeasure ). It is not the expected/intended behavior on Android.

What changes do you think we should make in order to solve the problem?

Set the correct width for single-line Text in the measure callback.

Changelog:

[ANDROID] [FIXED] - Fix single line text not fully rendered

Test Plan:

@facebook-github-bot facebook-github-bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Dec 3, 2023
@analysis-bot
Copy link

analysis-bot commented Dec 3, 2023

Platform Engine Arch Size (bytes) Diff
android hermes arm64-v8a 19,558,619 +16,062
android hermes armeabi-v7a n/a --
android hermes x86 n/a --
android hermes x86_64 n/a --
android jsc arm64-v8a 22,928,748 +16,192
android jsc armeabi-v7a n/a --
android jsc x86 n/a --
android jsc x86_64 n/a --

Base commit: 44f9371
Branch: main

@fabOnReact fabOnReact changed the title Fix single line text not fully rendered on old architecture Fix single line text not fully rendered Mar 10, 2024
@fabOnReact
Copy link
Contributor Author

fabOnReact commented Mar 10, 2024

Thanks for the code review.

  • The premise that we need to use the simpler BoringLayout to get the max width text could take without breaking seems strange. StaticLayout has options to not line break (maybe we should be setting that for single line instead?)

I updated the solution to use StaticLayout instead of BoringLayout.

  • This will leave the bug when there is a unicode, RTL, or other complicated character.

The new solution supports not boring text, emoji, RTL, etc (test cases).

  • This code is duplicated into Fabric (TextLayoutManager.java and now some more variants), and this code does not change anything there. If has same issue, we introduce new Fabric regression to Paper. If it isn't there, it has a different solution probably.

I added the Fabric implementation and included tests of the functionalities on Fabric and Paper.

@fabOnReact fabOnReact marked this pull request as ready for review March 10, 2024 08:19
@facebook-github-bot
Copy link
Contributor

@dmytrorykun has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@dmytrorykun
Copy link
Contributor

@fabOnReact there is a new comment in the corresponding issue #39722

@cipolleschi
Copy link
Contributor

@realsoelynn this might be related to the measurement issues you are working on.

@johnsimeroth
Copy link

Is this still being worked on / reviewed? Wondering if I need to use a workaround for all the text in my app or if a fix for this issue is likely to be merged before we ship.

@realsoelynn
Copy link
Contributor

@realsoelynn this might be related to the measurement issues you are working on.

Sorry about missing this.

This is NOT related to measurement issue that i was previously working on.

@fabOnReact Please also attach Output of npx react-native info.

I saw this issues on Expensify/App#43068 is closed and mentioned it's no longer reproducible.

Do you mind retesting your test cases https://gist.github.com/fabOnReact/6a58c713d32ce5bb9c164b6906abac12?permalink_comment_id=4956212 with latest React Native version with New Architecture Enabled ?

@realsoelynn realsoelynn self-requested a review August 7, 2024 18:37
@fabOnReact
Copy link
Contributor Author

fabOnReact commented Aug 10, 2024

@realsoelynn

Do you mind retesting your test cases https://gist.github.com/fabOnReact/6a58c713d32ce5bb9c164b6906abac12?permalink_comment_id=4956212 with latest React Native version with New Architecture Enabled ?

I tested again the test case with the latest React Native main branch and new architecture enabled.

CLICK TO OPEN SOURCECODE

import * as React from 'react';
import {StyleSheet, Text, TextInput, View} from 'react-native';

export default function App() {
  const email =
    'From vincenzoddragon+five@gmail.com  From vincenzoddrlxagon+five@gmail.com';
  return (
    <>
      <View style={styles.container}>
        <View style={styles.flexBrokenStyle}>
          <Text
            style={styles.parentText}
            numberOfLines={1}
          >
            {email}
          </Text>
        </View>
      </View>
    </>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    padding: 8,
    backgroundColor: 'yellow',
  },
  flexBrokenStyle: {
    flexDirection: 'row',
  },
  parentText: {
    backgroundColor: 'red',
  },
});

I added a log statement in TextLayoutManager.java and verified that we are running on the new architecture.

Screenshot 2024-08-10 at 3 49 34 PM

The width is calculated with TextLayoutManager (newArchitecture). The text does not take the full width, because TextLayoutManager calculates the wrong width.

Screenshot 2024-08-10 at 3 50 49 PM

Metro output shows Fabric is enabled.

Screenshot 2024-08-10 at 4 07 22 PM

I saw this issues on Expensify/App#43068 is closed and mentioned it's no longer reproducible.

The PR was not created to fix Issue Expensify/App#43068. The Expensify Issue is Expensify/App#21219.

Please also attach Output of npx react-native info.

OUTPUT OF NPX REACT-NATIVE INFO

info Fetching system and libraries information...
System:
  OS: macOS 14.6
  CPU: (10) arm64 Apple M1 Pro
  Memory: 176.52 MB / 16.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 20.15.1
    path: ~/.nvm/versions/node/v20.15.1/bin/node
  Yarn:
    version: 1.22.21
    path: /opt/homebrew/bin/yarn
  npm:
    version: 10.7.0
    path: ~/.nvm/versions/node/v20.15.1/bin/npm
  Watchman:
    version: 2024.07.15.00
    path: /opt/homebrew/bin/watchman
Managers:
  CocoaPods:
    version: 1.14.3
    path: /Users/fabriziobertoglio/.rbenv/shims/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 23.5
      - iOS 17.5
      - macOS 14.5
      - tvOS 17.5
      - visionOS 1.2
      - watchOS 10.5
  Android SDK: Not Found
IDEs:
  Android Studio: 2023.3 AI-233.14808.21.2331.11709847
  Xcode:
    version: 15.4/15F31d
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.12
    path: /usr/bin/javac
  Ruby:
    version: 2.7.5
    path: /Users/fabriziobertoglio/.rbenv/shims/ruby
npmPackages:
  "@react-native-community/cli": Not Found
  react:
    installed: 18.2.0
    wanted: 18.2.0
  react-native:
    installed: 0.73.4
    wanted: 0.73.4
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: true
iOS:
  hermesEnabled: true
  newArchEnabled: true

➜  expensify git:(main) npx react-native info
info Fetching system and libraries information...
System:
  OS: macOS 14.6
  CPU: (10) arm64 Apple M1 Pro
  Memory: 145.38 MB / 16.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 20.15.1
    path: ~/.nvm/versions/node/v20.15.1/bin/node
  Yarn:
    version: 1.22.21
    path: /opt/homebrew/bin/yarn
  npm:
    version: 10.7.0
    path: ~/.nvm/versions/node/v20.15.1/bin/npm
  Watchman:
    version: 2024.07.15.00
    path: /opt/homebrew/bin/watchman
Managers:
  CocoaPods:
    version: 1.14.3
    path: /Users/fabriziobertoglio/.rbenv/shims/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 23.5
      - iOS 17.5
      - macOS 14.5
      - tvOS 17.5
      - visionOS 1.2
      - watchOS 10.5
  Android SDK: Not Found
IDEs:
  Android Studio: 2023.3 AI-233.14808.21.2331.11709847
  Xcode:
    version: 15.4/15F31d
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.12
    path: /usr/bin/javac
  Ruby:
    version: 2.7.5
    path: /Users/fabriziobertoglio/.rbenv/shims/ruby
npmPackages:
  "@react-native-community/cli": Not Found
  react:
    installed: 18.2.0
    wanted: 18.2.0
  react-native:
    installed: 0.73.4
    wanted: 0.73.4
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: true
iOS:
  hermesEnabled: true
  newArchEnabled: true

@s77rt
Copy link
Contributor

s77rt commented Nov 3, 2024

The problem is that the created layout renders the text with line break enabled and what we end up measuring is just the first line which does not include all the text (since the rest wrapped). IOW we are measuring the text as if numberOfLines=undefined.

Screenshot 2024-11-03 at 11 21 25 PM Screenshot 2024-11-03 at 11 21 34 PM

@s77rt
Copy link
Contributor

s77rt commented Nov 3, 2024

The suggested solution causes a regression since all Texts with numberOfLines={1} will take full width

Before After
Screenshot 2024-11-04 at 12 03 46 AM Screenshot 2024-11-04 at 12 04 46 AM
<Text numberOfLines={1} style={{backgroundColor: 'red', alignSelf: 'flex-start', color: 'white'}}>
    1{'\n'}
</Text>

@s77rt
Copy link
Contributor

s77rt commented Nov 3, 2024

The easiest solution that come to mind is to compare the layout's number of lines (layout.getLineCount()) with the specified numberOfLines, if it's greater, then the text is wider than the view, set calculatedWidth = width. However this have the same regression above since we can enter \n to increase the number of lines but not necessary exceeding the view's width.

I think what we can do is take the number of lines that contain only text and stop at the first one that is just a line break (so we can differentiate between a line wrap and line break). Edit: not going with this solution exactly but somehow similar, see referenced PR below

Screenshot 2024-11-04 at 12 30 32 AM

@s77rt
Copy link
Contributor

s77rt commented Nov 3, 2024

An alternate solution is to render the text without line break (setLineBreakConfig) but I got the app crashed trying to work with this one.

Edit: This won't fix the problem if numberOfLines > 1

facebook-github-bot pushed a commit that referenced this pull request Nov 21, 2024
Summary:
### Problem
The calculated width for a multiline text is based on the longest line. However it does not account for text that wraps.

**Example** if numberOfLines=1 and the text wraps

```
+---------------------------+
This is a long text that
will wrap
+---------------------------+
```

The TextView will render
```
+---------------------------+
This is a long text t...
+---------------------------+
```

because the `calculatedWidth` took the width of the first line.

Also see #41770 (comment) for additional context.

### Solution

If the text wraps, take the whole width.

```
+---------------------------+
This is a long text that w...
+---------------------------+
```

Fixes #39722
Fixes facebook/yoga#1730

## Changelog:

[GENERAL] [FIXED] - Fix text not taking full width

Pull Request resolved: #47435

Test Plan:
```tsx
        <Text
          numberOfLines={1}
          style={{
            backgroundColor: 'red',
            alignSelf: 'flex-start',
            color: 'white',
            fontSize: 34,
          }}>
          {'This is a long text that will wrap.'}
        </Text>
        <Text
          numberOfLines={3}
          style={{
            backgroundColor: 'red',
            alignSelf: 'flex-start',
            color: 'white',
            fontSize: 34,
          }}>
          {
            '1\n\ntest\nThis is a long text that will wrap.This is a long text that will wrap.This is a long text that will wrap.\n'
          }
        </Text>
        <Text
          numberOfLines={3}
          style={{
            backgroundColor: 'red',
            alignSelf: 'flex-start',
            color: 'white',
            fontSize: 34,
          }}>
          {
            '1\n\nThis is a long text that will wrap.This is a long text that will wrap.This is a long text that will wrap.\n'
          }
        </Text>
```
1. Verify that the first and third text take full width
2. Verify that the second text does not take full width

| Before | After |
|:------:|:-----:|
|   <img width="480" alt="Screenshot 2024-11-05 at 9 00 24 PM" src="https://github.com/user-attachments/assets/b8d765c0-f4b1-42c6-afc7-75862c52612a">     |    <img width="480" alt="Screenshot 2024-11-05 at 9 01 49 PM" src="https://github.com/user-attachments/assets/f1534c14-a56a-4d44-8edc-4d9f75166cb2">   |

Reviewed By: NickGerleman

Differential Revision: D65521732

Pulled By: realsoelynn

fbshipit-source-id: 0bb0bb306445e73e8b24ff4c02921739d15ee07e
@cipolleschi
Copy link
Contributor

commit 550b0c0 landed, can we close this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

numberOfLines={1} and alignSelf: 'flex-start' when we set this text is break middle of in Android
8 participants