react-native icon indicating copy to clipboard operation
react-native copied to clipboard

Fix layout width calculation in onTextLayout

Open reepush opened this issue 2 years ago • 18 comments

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:

Screenshot 2023-02-19 at 23 48 53

This happens because (int) width casting works like Math.floor, but after changing it to Math.round we get correct result:

Screenshot 2023-02-19 at 23 51 11

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

reepush avatar Feb 20 '23 18:02 reepush

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!

facebook-github-bot avatar Feb 20 '23 18:02 facebook-github-bot

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

analysis-bot avatar Feb 20 '23 19:02 analysis-bot

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!

facebook-github-bot avatar Feb 20 '23 20:02 facebook-github-bot

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!

facebook-github-bot avatar Feb 20 '23 20:02 facebook-github-bot

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!

facebook-github-bot avatar Feb 20 '23 22:02 facebook-github-bot

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!

facebook-github-bot avatar Feb 21 '23 00:02 facebook-github-bot

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!

facebook-github-bot avatar Feb 21 '23 02:02 facebook-github-bot

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!

facebook-github-bot avatar Feb 21 '23 04:02 facebook-github-bot

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!

facebook-github-bot avatar Feb 21 '23 06:02 facebook-github-bot

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!

facebook-github-bot avatar Feb 21 '23 08:02 facebook-github-bot

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!

facebook-github-bot avatar Feb 21 '23 10:02 facebook-github-bot

This seems similar to https://github.com/facebook/react-native/commit/8b00b4f2860de50e5cefa43735d32bf4bdb8518d (cc @lunaleaps). Should we constrain the fix similarly?

javache avatar Feb 21 '23 11:02 javache

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!

facebook-github-bot avatar Feb 21 '23 11:02 facebook-github-bot

@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.

reepush avatar Feb 21 '23 17:02 reepush

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+?

javache avatar Feb 22 '23 12:02 javache

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

lunaleaps avatar Feb 22 '23 18:02 lunaleaps

@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.

reepush avatar Mar 01 '23 18:03 reepush

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

facebook-github-bot avatar Mar 08 '23 13:03 facebook-github-bot

@javache merged this pull request in facebook/react-native@ccbbcaab9cf3e6148e72f94fe63f77ce5f92416c.

facebook-github-bot avatar Mar 14 '23 19:03 facebook-github-bot