react-native
                                
                                 react-native copied to clipboard
                                
                                    react-native copied to clipboard
                            
                            
                            
                        Fix layout width calculation in onTextLayout
Summary
There is a rounding issue in layout calculation when using onTextLayout method in Text component on Android. As you can see in the example below onTextLayout returns 3 lines, but in fact text is rendered in 2 lines:
 
This happens because (int) width casting works like Math.floor, but after changing it to Math.round we get correct result:
 
Changelog
[ANDROID] [FIXED] - Fix layout width calculation in onTextLayout
Test Plan
This issue can be tricky to reproduce as width calculation depends on device width. I'm attaching code that I used to reproduce it. You need to tap on the screen to run through different sentences and sooner or later you will get the one with this rounding issue.
Code to reproduce
import React, { useState, useEffect } from 'react'
import { SafeAreaView, Text, View } from 'react-native'
function App() {
  const [state, setState] = useState({
    index: 0,
    lines: [],
    sentences: [],
  })
  const onTextLayout = (event) => {
    const lines = event.nativeEvent.lines
    console.log(JSON.stringify(lines, null, 2))
    setState(state => ({ ...state, lines }))
  }
  useEffect(() => {
    fetch('https://content.duoreading.io/20-the-adventures-of-tom-sawyer/translations/english.json')
      .then(response => response.text())
      .then(response => {
        setState(state => ({ ...state, sentences: JSON.parse(response) }))
      })
  }, [])
  return (
    <SafeAreaView style={{ flex: 1, padding: 30 }}>
      <View style={{ flex: 1 }} onTouchStart={() => setState(state => ({ ...state, index: state.index + 1 }))}>
        <Text style={{ fontSize: 22 }} onTextLayout={onTextLayout}>
          {state.sentences[state.index]}
        </Text>
        {state.lines.map((line, index) => (
          <View
            key={index}
            style={{
              position: 'absolute',
              top: line.y,
              left: line.x,
              width: line.width,
              height: line.height,
              opacity: 0.3,
              backgroundColor: ['red', 'yellow', 'blue'][index % 3],
            }}>
          </View>
        ))}
      </View>
    </SafeAreaView>
  )
}
export default App
Hi @reepush!
Thank you for your pull request and welcome to our community.
Action Required
In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.
Process
In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.
Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.
If you have received this in error or have any questions, please contact us at [email protected]. Thanks!
| Platform | Engine | Arch | Size (bytes) | Diff | 
|---|---|---|---|---|
| android | hermes | arm64-v8a | 8,477,195 | +100 | 
| android | hermes | armeabi-v7a | 7,799,170 | +91 | 
| android | hermes | x86 | 8,953,176 | +87 | 
| android | hermes | x86_64 | 8,810,681 | +94 | 
| android | jsc | arm64-v8a | 9,108,256 | +80 | 
| android | jsc | armeabi-v7a | 8,304,926 | +82 | 
| android | jsc | x86 | 9,159,108 | +76 | 
| android | jsc | x86_64 | 9,418,382 | +63 | 
Base commit: a49446b5b5df0204a6727de19bcab1e6d18c596a Branch: main
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!
This seems similar to https://github.com/facebook/react-native/commit/8b00b4f2860de50e5cefa43735d32bf4bdb8518d (cc @lunaleaps). Should we constrain the fix similarly?
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!
@javache I checked commit that you mentioned and it seems Math.ceil is used everywhere and is preferable way to do it. I updated my changes replacing Math.round with Math.ceil and everything works just fine.
The changes in https://github.com/facebook/react-native/commit/8b00b4f2860de50e5cefa43735d32bf4bdb8518d are only applied in Android 11+. Your change affects all versions of Android, including pre-6.0, can you check if we need to move this to a separate branch, just for 11+?
Yea for https://github.com/facebook/react-native/commit/8b00b4f2860de50e5cefa43735d32bf4bdb8518d, the problem was only expressing in Android 11+, @reepush is that the case for you? Also when finding this issue are you on 0.71? Just want to make sure you're on latest and are on changes from https://github.com/facebook/react-native/commit/8b00b4f2860de50e5cefa43735d32bf4bdb8518d
@javache @lunaleaps Yes, I'm testing on Android 11+ and 0.71.3. Also now I understand what checks for VERSION_CODES.M (Android 6) and VERSION_CODES.Q (Android 10) means and we need to implement this fix only for Android 11+. I just copied the code that @lunaleaps implemented.
@javache has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.
@javache merged this pull request in facebook/react-native@ccbbcaab9cf3e6148e72f94fe63f77ce5f92416c.