Skip to content

Fix changing font scale breaking text #45978

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

Closed

Conversation

j-piasecki
Copy link
Contributor

@j-piasecki j-piasecki commented Aug 12, 2024

Summary:

Fixes #45857

The general idea behind this PR is the same for both platforms: dirty all nodes with MeasurableYogaNode trait when the layout is constrained with a new fontSizeMultiplier. There were a few caveats:

  • ParagraphShadowNode marks its layout as clean in the constructor in most cases. To prevent that from using a stale measurement, I've added a fontSizeMultiplier_ field in the node that keeps track of the font scale it was last laid out with. That value is then compared with the scale used to create the attributed string kept in the node's state. If those differ, the layout is not cleared.
  • On Android, font scale wasn't passed down to the SurfaceHandler
  • On Android, text measurement relies on cached DisplayMetrics which were not updated when the system font scale changed.
  • AndroidTextInputShadowNode wasn't using fontSizeMultiplier at all. I needed to add it in all places where an AttributedString is constructed.
  • When the fontSizeMultiplier is changed, the entire ShadowTree is cloned. I'm not sure if there's a reliable way of determining whether the node is mutable or not.
  • Changing font scale and navigating back to the app on Android has the potential to cause a SIGSEGV but it also happens without changes in this PR.

Changelog:

[GENERAL] [FIXED] - Fixed text not updating correctly after changing font scale in settings

Test Plan:

So far tested on the following code:

function App() {
  const [counter,setCounter] = useState(0);
  const [text,setText] = useState('TextInput');
  const [flag,setFlag] = useState(true);

  return (
    <SafeAreaView
        style={{
          flex: 1,
          backgroundColor: '#fff',
          alignItems: 'center',
          justifyContent: 'center',
        }}
    >
      <Text style={{fontSize: 24}}>RN 24 Label Testing {flag ? 'A' : 'B'}</Text>
      <TextInput value={text} onChangeText={setText} style={{fontSize: 24, borderWidth: 1}} placeholder="Placeholder" />
      <Pressable onPress={() => setCounter(prevState => prevState + 1)} style={{backgroundColor: counter % 2 === 0 ? 'red' : 'blue', width: 200, height: 50}} />
      <Pressable onPress={() => setFlag(!flag)} style={{backgroundColor: 'green', width: 200, height: 50}} />
    </SafeAreaView>
  );
}

@facebook-github-bot facebook-github-bot added CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. p: Software Mansion Partner: Software Mansion Partner labels Aug 12, 2024
@j-piasecki j-piasecki force-pushed the @jpiasecki/fix-font-scaling branch from 7c19db4 to 257a501 Compare March 10, 2025 15:05
@j-piasecki j-piasecki force-pushed the @jpiasecki/fix-font-scaling branch 2 times, most recently from d72c6d3 to 861b708 Compare March 21, 2025 14:40
@j-piasecki j-piasecki marked this pull request as ready for review March 24, 2025 10:10
@facebook-github-bot
Copy link
Contributor

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

@j-piasecki j-piasecki force-pushed the @jpiasecki/fix-font-scaling branch from 2a2976d to 8a015d4 Compare April 4, 2025 10:06
@facebook-github-bot
Copy link
Contributor

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

j-piasecki added a commit to j-piasecki/react-native that referenced this pull request Apr 7, 2025
Summary:
Fixes facebook#45857

The general idea behind this PR is the same for both platforms: dirty all nodes with `MeasurableYogaNode` trait when the layout is constrained with a new `fontSizeMultiplier`. There were a few caveats:
- `ParagraphShadowNode` marks its layout as clean in the constructor in most cases. To prevent that from using a stale measurement, I've added a `fontSizeMultiplier_` field in the node that keeps track of the font scale it was last laid out with. That value is then compared with the scale used to create the attributed string kept in the node's state. If those differ, the layout is not cleared.
- On Android, font scale wasn't passed down to the `SurfaceHandler`
- On Android, text measurement relies on cached `DisplayMetrics` which were not updated when the system font scale changed.
- `AndroidTextInputShadowNode` wasn't using `fontSizeMultiplier` at all. I needed to add it in all places where an `AttributedString` is constructed.
- When the `fontSizeMultiplier` is changed, the entire `ShadowTree` is cloned. I'm not sure if there's a reliable way of determining whether the node is mutable or not.
- Changing font scale and navigating back to the app on Android has the potential to cause a `SIGSEGV` but it also happens without changes in this PR.

## Changelog:

[GENERAL] [FIXED] - Fixed text not updating correctly after changing font scale in settings


Test Plan:
So far tested on the following code:

```jsx
function App() {
  const [counter,setCounter] = useState(0);
  const [text,setText] = useState('TextInput');
  const [flag,setFlag] = useState(true);

  return (
    <SafeAreaView
        style={{
          flex: 1,
          backgroundColor: '#fff',
          alignItems: 'center',
          justifyContent: 'center',
        }}
    >
      <Text style={{fontSize: 24}}>RN 24 Label Testing {flag ? 'A' : 'B'}</Text>
      <TextInput value={text} onChangeText={setText} style={{fontSize: 24, borderWidth: 1}} placeholder="Placeholder" />
      <Pressable onPress={() => setCounter(prevState => prevState + 1)} style={{backgroundColor: counter % 2 === 0 ? 'red' : 'blue', width: 200, height: 50}} />
      <Pressable onPress={() => setFlag(!flag)} style={{backgroundColor: 'green', width: 200, height: 50}} />
    </SafeAreaView>
  );
}
```

Differential Revision: D71727907

Pulled By: j-piasecki
@j-piasecki j-piasecki force-pushed the @jpiasecki/fix-font-scaling branch from 8a015d4 to 57ab723 Compare April 7, 2025 09:57
@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D71727907

@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D71727907

@j-piasecki j-piasecki force-pushed the @jpiasecki/fix-font-scaling branch from 57ab723 to b8d1587 Compare April 7, 2025 10:44
j-piasecki added a commit to j-piasecki/react-native that referenced this pull request Apr 7, 2025
Summary:
Fixes facebook#45857

The general idea behind this PR is the same for both platforms: dirty all nodes with `MeasurableYogaNode` trait when the layout is constrained with a new `fontSizeMultiplier`. There were a few caveats:
- `ParagraphShadowNode` marks its layout as clean in the constructor in most cases. To prevent that from using a stale measurement, I've added a `fontSizeMultiplier_` field in the node that keeps track of the font scale it was last laid out with. That value is then compared with the scale used to create the attributed string kept in the node's state. If those differ, the layout is not cleared.
- On Android, font scale wasn't passed down to the `SurfaceHandler`
- On Android, text measurement relies on cached `DisplayMetrics` which were not updated when the system font scale changed.
- `AndroidTextInputShadowNode` wasn't using `fontSizeMultiplier` at all. I needed to add it in all places where an `AttributedString` is constructed.
- When the `fontSizeMultiplier` is changed, the entire `ShadowTree` is cloned. I'm not sure if there's a reliable way of determining whether the node is mutable or not.
- Changing font scale and navigating back to the app on Android has the potential to cause a `SIGSEGV` but it also happens without changes in this PR.

## Changelog:

[GENERAL] [FIXED] - Fixed text not updating correctly after changing font scale in settings

Pull Request resolved: facebook#45978

Test Plan:
So far tested on the following code:

```jsx
function App() {
  const [counter,setCounter] = useState(0);
  const [text,setText] = useState('TextInput');
  const [flag,setFlag] = useState(true);

  return (
    <SafeAreaView
        style={{
          flex: 1,
          backgroundColor: '#fff',
          alignItems: 'center',
          justifyContent: 'center',
        }}
    >
      <Text style={{fontSize: 24}}>RN 24 Label Testing {flag ? 'A' : 'B'}</Text>
      <TextInput value={text} onChangeText={setText} style={{fontSize: 24, borderWidth: 1}} placeholder="Placeholder" />
      <Pressable onPress={() => setCounter(prevState => prevState + 1)} style={{backgroundColor: counter % 2 === 0 ? 'red' : 'blue', width: 200, height: 50}} />
      <Pressable onPress={() => setFlag(!flag)} style={{backgroundColor: 'green', width: 200, height: 50}} />
    </SafeAreaView>
  );
}
```

Differential Revision: D71727907

Pulled By: j-piasecki
@facebook-github-bot
Copy link
Contributor

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

@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D71727907

j-piasecki added a commit to j-piasecki/react-native that referenced this pull request Apr 7, 2025
Summary:
Fixes facebook#45857

The general idea behind this PR is the same for both platforms: dirty all nodes with `MeasurableYogaNode` trait when the layout is constrained with a new `fontSizeMultiplier`. There were a few caveats:
- `ParagraphShadowNode` marks its layout as clean in the constructor in most cases. To prevent that from using a stale measurement, I've added a `fontSizeMultiplier_` field in the node that keeps track of the font scale it was last laid out with. That value is then compared with the scale used to create the attributed string kept in the node's state. If those differ, the layout is not cleared.
- On Android, font scale wasn't passed down to the `SurfaceHandler`
- On Android, text measurement relies on cached `DisplayMetrics` which were not updated when the system font scale changed.
- `AndroidTextInputShadowNode` wasn't using `fontSizeMultiplier` at all. I needed to add it in all places where an `AttributedString` is constructed.
- When the `fontSizeMultiplier` is changed, the entire `ShadowTree` is cloned. I'm not sure if there's a reliable way of determining whether the node is mutable or not.
- Changing font scale and navigating back to the app on Android has the potential to cause a `SIGSEGV` but it also happens without changes in this PR.

## Changelog:

[GENERAL] [FIXED] - Fixed text not updating correctly after changing font scale in settings

Pull Request resolved: facebook#45978

Test Plan:
So far tested on the following code:

```jsx
function App() {
  const [counter,setCounter] = useState(0);
  const [text,setText] = useState('TextInput');
  const [flag,setFlag] = useState(true);

  return (
    <SafeAreaView
        style={{
          flex: 1,
          backgroundColor: '#fff',
          alignItems: 'center',
          justifyContent: 'center',
        }}
    >
      <Text style={{fontSize: 24}}>RN 24 Label Testing {flag ? 'A' : 'B'}</Text>
      <TextInput value={text} onChangeText={setText} style={{fontSize: 24, borderWidth: 1}} placeholder="Placeholder" />
      <Pressable onPress={() => setCounter(prevState => prevState + 1)} style={{backgroundColor: counter % 2 === 0 ? 'red' : 'blue', width: 200, height: 50}} />
      <Pressable onPress={() => setFlag(!flag)} style={{backgroundColor: 'green', width: 200, height: 50}} />
    </SafeAreaView>
  );
}
```

Differential Revision: D71727907

Pulled By: j-piasecki
@j-piasecki j-piasecki force-pushed the @jpiasecki/fix-font-scaling branch from 83fc24f to 829e111 Compare April 7, 2025 13:17
@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D71727907

j-piasecki added a commit to j-piasecki/react-native that referenced this pull request Apr 7, 2025
Summary:
Fixes facebook#45857

The general idea behind this PR is the same for both platforms: dirty all nodes with `MeasurableYogaNode` trait when the layout is constrained with a new `fontSizeMultiplier`. There were a few caveats:
- `ParagraphShadowNode` marks its layout as clean in the constructor in most cases. To prevent that from using a stale measurement, I've added a `fontSizeMultiplier_` field in the node that keeps track of the font scale it was last laid out with. That value is then compared with the scale used to create the attributed string kept in the node's state. If those differ, the layout is not cleared.
- On Android, font scale wasn't passed down to the `SurfaceHandler`
- On Android, text measurement relies on cached `DisplayMetrics` which were not updated when the system font scale changed.
- `AndroidTextInputShadowNode` wasn't using `fontSizeMultiplier` at all. I needed to add it in all places where an `AttributedString` is constructed.
- When the `fontSizeMultiplier` is changed, the entire `ShadowTree` is cloned. I'm not sure if there's a reliable way of determining whether the node is mutable or not.
- Changing font scale and navigating back to the app on Android has the potential to cause a `SIGSEGV` but it also happens without changes in this PR.

## Changelog:

[GENERAL] [FIXED] - Fixed text not updating correctly after changing font scale in settings

Pull Request resolved: facebook#45978

Test Plan:
So far tested on the following code:

```jsx
function App() {
  const [counter,setCounter] = useState(0);
  const [text,setText] = useState('TextInput');
  const [flag,setFlag] = useState(true);

  return (
    <SafeAreaView
        style={{
          flex: 1,
          backgroundColor: '#fff',
          alignItems: 'center',
          justifyContent: 'center',
        }}
    >
      <Text style={{fontSize: 24}}>RN 24 Label Testing {flag ? 'A' : 'B'}</Text>
      <TextInput value={text} onChangeText={setText} style={{fontSize: 24, borderWidth: 1}} placeholder="Placeholder" />
      <Pressable onPress={() => setCounter(prevState => prevState + 1)} style={{backgroundColor: counter % 2 === 0 ? 'red' : 'blue', width: 200, height: 50}} />
      <Pressable onPress={() => setFlag(!flag)} style={{backgroundColor: 'green', width: 200, height: 50}} />
    </SafeAreaView>
  );
}
```

Differential Revision: D71727907

Pulled By: j-piasecki
@j-piasecki j-piasecki force-pushed the @jpiasecki/fix-font-scaling branch from 829e111 to 02a8f8d Compare April 7, 2025 13:29
@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D71727907

j-piasecki added a commit to j-piasecki/react-native that referenced this pull request Apr 8, 2025
Summary:
Fixes facebook#45857

The general idea behind this PR is the same for both platforms: dirty all nodes with `MeasurableYogaNode` trait when the layout is constrained with a new `fontSizeMultiplier`. There were a few caveats:
- `ParagraphShadowNode` marks its layout as clean in the constructor in most cases. To prevent that from using a stale measurement, I've added a `fontSizeMultiplier_` field in the node that keeps track of the font scale it was last laid out with. That value is then compared with the scale used to create the attributed string kept in the node's state. If those differ, the layout is not cleared.
- On Android, font scale wasn't passed down to the `SurfaceHandler`
- On Android, text measurement relies on cached `DisplayMetrics` which were not updated when the system font scale changed.
- `AndroidTextInputShadowNode` wasn't using `fontSizeMultiplier` at all. I needed to add it in all places where an `AttributedString` is constructed.
- When the `fontSizeMultiplier` is changed, the entire `ShadowTree` is cloned. I'm not sure if there's a reliable way of determining whether the node is mutable or not.
- Changing font scale and navigating back to the app on Android has the potential to cause a `SIGSEGV` but it also happens without changes in this PR.

## Changelog:

[GENERAL] [FIXED] - Fixed text not updating correctly after changing font scale in settings

Pull Request resolved: facebook#45978

Test Plan:
So far tested on the following code:

```jsx
function App() {
  const [counter,setCounter] = useState(0);
  const [text,setText] = useState('TextInput');
  const [flag,setFlag] = useState(true);

  return (
    <SafeAreaView
        style={{
          flex: 1,
          backgroundColor: '#fff',
          alignItems: 'center',
          justifyContent: 'center',
        }}
    >
      <Text style={{fontSize: 24}}>RN 24 Label Testing {flag ? 'A' : 'B'}</Text>
      <TextInput value={text} onChangeText={setText} style={{fontSize: 24, borderWidth: 1}} placeholder="Placeholder" />
      <Pressable onPress={() => setCounter(prevState => prevState + 1)} style={{backgroundColor: counter % 2 === 0 ? 'red' : 'blue', width: 200, height: 50}} />
      <Pressable onPress={() => setFlag(!flag)} style={{backgroundColor: 'green', width: 200, height: 50}} />
    </SafeAreaView>
  );
}
```

Differential Revision: D71727907

Pulled By: j-piasecki
@j-piasecki j-piasecki force-pushed the @jpiasecki/fix-font-scaling branch from 02a8f8d to 8135477 Compare April 8, 2025 06:06
@j-piasecki j-piasecki force-pushed the @jpiasecki/fix-font-scaling branch from fdad3c6 to f4ec65b Compare April 9, 2025 13:16
j-piasecki added a commit to j-piasecki/react-native that referenced this pull request Apr 9, 2025
Summary:
Fixes facebook#45857

The general idea behind this PR is the same for both platforms: dirty all nodes with `MeasurableYogaNode` trait when the layout is constrained with a new `fontSizeMultiplier`. There were a few caveats:
- `ParagraphShadowNode` marks its layout as clean in the constructor in most cases. To prevent that from using a stale measurement, I've added a `fontSizeMultiplier_` field in the node that keeps track of the font scale it was last laid out with. That value is then compared with the scale used to create the attributed string kept in the node's state. If those differ, the layout is not cleared.
- On Android, font scale wasn't passed down to the `SurfaceHandler`
- On Android, text measurement relies on cached `DisplayMetrics` which were not updated when the system font scale changed.
- `AndroidTextInputShadowNode` wasn't using `fontSizeMultiplier` at all. I needed to add it in all places where an `AttributedString` is constructed.
- When the `fontSizeMultiplier` is changed, the entire `ShadowTree` is cloned. I'm not sure if there's a reliable way of determining whether the node is mutable or not.
- Changing font scale and navigating back to the app on Android has the potential to cause a `SIGSEGV` but it also happens without changes in this PR.

## Changelog:

[GENERAL] [FIXED] - Fixed text not updating correctly after changing font scale in settings


Test Plan:
So far tested on the following code:

```jsx
function App() {
  const [counter,setCounter] = useState(0);
  const [text,setText] = useState('TextInput');
  const [flag,setFlag] = useState(true);

  return (
    <SafeAreaView
        style={{
          flex: 1,
          backgroundColor: '#fff',
          alignItems: 'center',
          justifyContent: 'center',
        }}
    >
      <Text style={{fontSize: 24}}>RN 24 Label Testing {flag ? 'A' : 'B'}</Text>
      <TextInput value={text} onChangeText={setText} style={{fontSize: 24, borderWidth: 1}} placeholder="Placeholder" />
      <Pressable onPress={() => setCounter(prevState => prevState + 1)} style={{backgroundColor: counter % 2 === 0 ? 'red' : 'blue', width: 200, height: 50}} />
      <Pressable onPress={() => setFlag(!flag)} style={{backgroundColor: 'green', width: 200, height: 50}} />
    </SafeAreaView>
  );
}
```

Differential Revision: D71727907

Pulled By: j-piasecki
@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D71727907

@j-piasecki j-piasecki force-pushed the @jpiasecki/fix-font-scaling branch from f4ec65b to c1c34b5 Compare April 10, 2025 06:17
j-piasecki added a commit to j-piasecki/react-native that referenced this pull request Apr 10, 2025
Summary:
Fixes facebook#45857

The general idea behind this PR is the same for both platforms: dirty all nodes with `MeasurableYogaNode` trait when the layout is constrained with a new `fontSizeMultiplier`. There were a few caveats:
- `ParagraphShadowNode` marks its layout as clean in the constructor in most cases. To prevent that from using a stale measurement I'm using the font scale multiplier stored in `content_` property of the node. That value is then compared with the scale used to create the attributed string kept in the node's state. If those differ, the layout is not cleared.
- On Android, font scale wasn't passed down to the `SurfaceHandler`
- On Android, text measurement relies on cached `DisplayMetrics` which were not updated when the system font scale changed.
- `AndroidTextInputShadowNode` wasn't using `fontSizeMultiplier` at all. I needed to add it in all places where an `AttributedString` is constructed.

## Changelog:

[GENERAL] [FIXED] - Fixed text not updating correctly after changing font scale in settings


Test Plan:
So far tested on the following code:

```jsx
function App() {
  const [counter,setCounter] = useState(0);
  const [text,setText] = useState('TextInput');
  const [flag,setFlag] = useState(true);

  return (
    <SafeAreaView
        style={{
          flex: 1,
          backgroundColor: '#fff',
          alignItems: 'center',
          justifyContent: 'center',
        }}
    >
      <Text style={{fontSize: 24}}>RN 24 Label Testing {flag ? 'A' : 'B'}</Text>
      <TextInput value={text} onChangeText={setText} style={{fontSize: 24, borderWidth: 1}} placeholder="Placeholder" />
      <Pressable onPress={() => setCounter(prevState => prevState + 1)} style={{backgroundColor: counter % 2 === 0 ? 'red' : 'blue', width: 200, height: 50}} />
      <Pressable onPress={() => setFlag(!flag)} style={{backgroundColor: 'green', width: 200, height: 50}} />
    </SafeAreaView>
  );
}
```

Differential Revision: D71727907

Pulled By: j-piasecki
@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D71727907

@j-piasecki j-piasecki force-pushed the @jpiasecki/fix-font-scaling branch from c1c34b5 to 8140336 Compare April 11, 2025 10:14
j-piasecki added a commit to j-piasecki/react-native that referenced this pull request Apr 11, 2025
Summary:
Fixes facebook#45857

The general idea behind this PR is the same for both platforms: dirty all nodes with `MeasurableYogaNode` trait when the layout is constrained with a new `fontSizeMultiplier`. There were a few caveats:
- `ParagraphShadowNode` marks its layout as clean in the constructor in most cases. To prevent that from using a stale measurement I'm using the font scale multiplier stored in `content_` property of the node. That value is then compared with the scale used to create the attributed string kept in the node's state. If those differ, the layout is not cleared.
- On Android, font scale wasn't passed down to the `SurfaceHandler`
- On Android, text measurement relies on cached `DisplayMetrics` which were not updated when the system font scale changed.
- `AndroidTextInputShadowNode` wasn't using `fontSizeMultiplier` at all. I needed to add it in all places where an `AttributedString` is constructed.

## Changelog:

[GENERAL] [FIXED] - Fixed text not updating correctly after changing font scale in settings


Test Plan:
So far tested on the following code:

```jsx
function App() {
  const [counter,setCounter] = useState(0);
  const [text,setText] = useState('TextInput');
  const [flag,setFlag] = useState(true);

  return (
    <SafeAreaView
        style={{
          flex: 1,
          backgroundColor: '#fff',
          alignItems: 'center',
          justifyContent: 'center',
        }}
    >
      <Text style={{fontSize: 24}}>RN 24 Label Testing {flag ? 'A' : 'B'}</Text>
      <TextInput value={text} onChangeText={setText} style={{fontSize: 24, borderWidth: 1}} placeholder="Placeholder" />
      <Pressable onPress={() => setCounter(prevState => prevState + 1)} style={{backgroundColor: counter % 2 === 0 ? 'red' : 'blue', width: 200, height: 50}} />
      <Pressable onPress={() => setFlag(!flag)} style={{backgroundColor: 'green', width: 200, height: 50}} />
    </SafeAreaView>
  );
}
```

Differential Revision: D71727907

Pulled By: j-piasecki
@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D71727907

j-piasecki added a commit to j-piasecki/react-native that referenced this pull request Apr 11, 2025
Summary:
Fixes facebook#45857

The general idea behind this PR is the same for both platforms: dirty all nodes with `MeasurableYogaNode` trait when the layout is constrained with a new `fontSizeMultiplier`. There were a few caveats:
- `ParagraphShadowNode` marks its layout as clean in the constructor in most cases. To prevent that from using a stale measurement I'm using the font scale multiplier stored in `content_` property of the node. That value is then compared with the scale used to create the attributed string kept in the node's state. If those differ, the layout is not cleared.
- On Android, font scale wasn't passed down to the `SurfaceHandler`
- On Android, text measurement relies on cached `DisplayMetrics` which were not updated when the system font scale changed.
- `AndroidTextInputShadowNode` wasn't using `fontSizeMultiplier` at all. I needed to add it in all places where an `AttributedString` is constructed.

## Changelog:

[GENERAL] [FIXED] - Fixed text not updating correctly after changing font scale in settings


Test Plan:
So far tested on the following code:

```jsx
function App() {
  const [counter,setCounter] = useState(0);
  const [text,setText] = useState('TextInput');
  const [flag,setFlag] = useState(true);

  return (
    <SafeAreaView
        style={{
          flex: 1,
          backgroundColor: '#fff',
          alignItems: 'center',
          justifyContent: 'center',
        }}
    >
      <Text style={{fontSize: 24}}>RN 24 Label Testing {flag ? 'A' : 'B'}</Text>
      <TextInput value={text} onChangeText={setText} style={{fontSize: 24, borderWidth: 1}} placeholder="Placeholder" />
      <Pressable onPress={() => setCounter(prevState => prevState + 1)} style={{backgroundColor: counter % 2 === 0 ? 'red' : 'blue', width: 200, height: 50}} />
      <Pressable onPress={() => setFlag(!flag)} style={{backgroundColor: 'green', width: 200, height: 50}} />
    </SafeAreaView>
  );
}
```

Differential Revision: D71727907

Pulled By: j-piasecki
@j-piasecki j-piasecki force-pushed the @jpiasecki/fix-font-scaling branch from 8140336 to 4aebab2 Compare April 11, 2025 10:35
@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D71727907

j-piasecki added a commit to j-piasecki/react-native that referenced this pull request Apr 14, 2025
Summary:
Fixes facebook#45857

The general idea behind this PR is the same for both platforms: dirty all nodes with `MeasurableYogaNode` trait when the layout is constrained with a new `fontSizeMultiplier`. There were a few caveats:
- `ParagraphShadowNode` marks its layout as clean in the constructor in most cases. To prevent that from using a stale measurement I'm using the font scale multiplier stored in `content_` property of the node. That value is then compared with the scale used to create the attributed string kept in the node's state. If those differ, the layout is not cleared.
- On Android, font scale wasn't passed down to the `SurfaceHandler`
- On Android, text measurement relies on cached `DisplayMetrics` which were not updated when the system font scale changed.
- `AndroidTextInputShadowNode` wasn't using `fontSizeMultiplier` at all. I needed to add it in all places where an `AttributedString` is constructed.

## Changelog:

[GENERAL] [FIXED] - Fixed text not updating correctly after changing font scale in settings


Test Plan:
So far tested on the following code:

```jsx
function App() {
  const [counter,setCounter] = useState(0);
  const [text,setText] = useState('TextInput');
  const [flag,setFlag] = useState(true);

  return (
    <SafeAreaView
        style={{
          flex: 1,
          backgroundColor: '#fff',
          alignItems: 'center',
          justifyContent: 'center',
        }}
    >
      <Text style={{fontSize: 24}}>RN 24 Label Testing {flag ? 'A' : 'B'}</Text>
      <TextInput value={text} onChangeText={setText} style={{fontSize: 24, borderWidth: 1}} placeholder="Placeholder" />
      <Pressable onPress={() => setCounter(prevState => prevState + 1)} style={{backgroundColor: counter % 2 === 0 ? 'red' : 'blue', width: 200, height: 50}} />
      <Pressable onPress={() => setFlag(!flag)} style={{backgroundColor: 'green', width: 200, height: 50}} />
    </SafeAreaView>
  );
}
```

Reviewed By: NickGerleman

Differential Revision: D71727907

Pulled By: j-piasecki
@j-piasecki j-piasecki force-pushed the @jpiasecki/fix-font-scaling branch from 4aebab2 to db9ab31 Compare April 14, 2025 05:42
@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D71727907

j-piasecki added a commit to j-piasecki/react-native that referenced this pull request Apr 14, 2025
Summary:
Fixes facebook#45857

The general idea behind this PR is the same for both platforms: dirty all nodes with `MeasurableYogaNode` trait when the layout is constrained with a new `fontSizeMultiplier`. There were a few caveats:
- `ParagraphShadowNode` marks its layout as clean in the constructor in most cases. To prevent that from using a stale measurement I'm using the font scale multiplier stored in `content_` property of the node. That value is then compared with the scale used to create the attributed string kept in the node's state. If those differ, the layout is not cleared.
- On Android, font scale wasn't passed down to the `SurfaceHandler`
- On Android, text measurement relies on cached `DisplayMetrics` which were not updated when the system font scale changed.
- `AndroidTextInputShadowNode` wasn't using `fontSizeMultiplier` at all. I needed to add it in all places where an `AttributedString` is constructed.

## Changelog:

[GENERAL] [FIXED] - Fixed text not updating correctly after changing font scale in settings


Test Plan:
So far tested on the following code:

```jsx
function App() {
  const [counter,setCounter] = useState(0);
  const [text,setText] = useState('TextInput');
  const [flag,setFlag] = useState(true);

  return (
    <SafeAreaView
        style={{
          flex: 1,
          backgroundColor: '#fff',
          alignItems: 'center',
          justifyContent: 'center',
        }}
    >
      <Text style={{fontSize: 24}}>RN 24 Label Testing {flag ? 'A' : 'B'}</Text>
      <TextInput value={text} onChangeText={setText} style={{fontSize: 24, borderWidth: 1}} placeholder="Placeholder" />
      <Pressable onPress={() => setCounter(prevState => prevState + 1)} style={{backgroundColor: counter % 2 === 0 ? 'red' : 'blue', width: 200, height: 50}} />
      <Pressable onPress={() => setFlag(!flag)} style={{backgroundColor: 'green', width: 200, height: 50}} />
    </SafeAreaView>
  );
}
```

Reviewed By: NickGerleman

Differential Revision: D71727907

Pulled By: j-piasecki
@j-piasecki j-piasecki force-pushed the @jpiasecki/fix-font-scaling branch from db9ab31 to 6db29a1 Compare April 14, 2025 07:20
@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D71727907

@j-piasecki j-piasecki force-pushed the @jpiasecki/fix-font-scaling branch from 6db29a1 to cb9ae42 Compare April 14, 2025 12:10
j-piasecki added a commit to j-piasecki/react-native that referenced this pull request Apr 14, 2025
Summary:
Fixes facebook#45857

The general idea behind this PR is the same for both platforms: dirty all nodes with `MeasurableYogaNode` trait when the layout is constrained with a new `fontSizeMultiplier`. There were a few caveats:
- `ParagraphShadowNode` marks its layout as clean in the constructor in most cases. To prevent that from using a stale measurement I'm using the font scale multiplier stored in `content_` property of the node. That value is then compared with the scale used to create the attributed string kept in the node's state. If those differ, the layout is not cleared.
- On Android, font scale wasn't passed down to the `SurfaceHandler`
- On Android, text measurement relies on cached `DisplayMetrics` which were not updated when the system font scale changed.
- `AndroidTextInputShadowNode` wasn't using `fontSizeMultiplier` at all. I needed to add it in all places where an `AttributedString` is constructed.

## Changelog:

[GENERAL] [FIXED] - Fixed text not updating correctly after changing font scale in settings


Test Plan:
So far tested on the following code:

```jsx
function App() {
  const [counter,setCounter] = useState(0);
  const [text,setText] = useState('TextInput');
  const [flag,setFlag] = useState(true);

  return (
    <SafeAreaView
        style={{
          flex: 1,
          backgroundColor: '#fff',
          alignItems: 'center',
          justifyContent: 'center',
        }}
    >
      <Text style={{fontSize: 24}}>RN 24 Label Testing {flag ? 'A' : 'B'}</Text>
      <TextInput value={text} onChangeText={setText} style={{fontSize: 24, borderWidth: 1}} placeholder="Placeholder" />
      <Pressable onPress={() => setCounter(prevState => prevState + 1)} style={{backgroundColor: counter % 2 === 0 ? 'red' : 'blue', width: 200, height: 50}} />
      <Pressable onPress={() => setFlag(!flag)} style={{backgroundColor: 'green', width: 200, height: 50}} />
    </SafeAreaView>
  );
}
```

Reviewed By: NickGerleman

Differential Revision: D71727907

Pulled By: j-piasecki
@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D71727907

Summary:
Fixes facebook#45857

The general idea behind this PR is the same for both platforms: dirty all nodes with `MeasurableYogaNode` trait when the layout is constrained with a new `fontSizeMultiplier`. There were a few caveats:
- `ParagraphShadowNode` marks its layout as clean in the constructor in most cases. To prevent that from using a stale measurement I'm using the font scale multiplier stored in `content_` property of the node. That value is then compared with the scale used to create the attributed string kept in the node's state. If those differ, the layout is not cleared.
- On Android, font scale wasn't passed down to the `SurfaceHandler`
- On Android, text measurement relies on cached `DisplayMetrics` which were not updated when the system font scale changed.
- `AndroidTextInputShadowNode` wasn't using `fontSizeMultiplier` at all. I needed to add it in all places where an `AttributedString` is constructed.

## Changelog:

[GENERAL] [FIXED] - Fixed text not updating correctly after changing font scale in settings


Test Plan:
So far tested on the following code:

```jsx
function App() {
  const [counter,setCounter] = useState(0);
  const [text,setText] = useState('TextInput');
  const [flag,setFlag] = useState(true);

  return (
    <SafeAreaView
        style={{
          flex: 1,
          backgroundColor: '#fff',
          alignItems: 'center',
          justifyContent: 'center',
        }}
    >
      <Text style={{fontSize: 24}}>RN 24 Label Testing {flag ? 'A' : 'B'}</Text>
      <TextInput value={text} onChangeText={setText} style={{fontSize: 24, borderWidth: 1}} placeholder="Placeholder" />
      <Pressable onPress={() => setCounter(prevState => prevState + 1)} style={{backgroundColor: counter % 2 === 0 ? 'red' : 'blue', width: 200, height: 50}} />
      <Pressable onPress={() => setFlag(!flag)} style={{backgroundColor: 'green', width: 200, height: 50}} />
    </SafeAreaView>
  );
}
```

Reviewed By: NickGerleman

Differential Revision: D71727907

Pulled By: j-piasecki
@j-piasecki j-piasecki force-pushed the @jpiasecki/fix-font-scaling branch from cb9ae42 to 5f899bb Compare April 14, 2025 13:42
@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D71727907

@react-native-bot
Copy link
Collaborator

This pull request was successfully merged by @j-piasecki in c008604

When will my fix make it into a release? | How to file a pick request?

@react-native-bot react-native-bot added the Merged This PR has been merged. label Apr 14, 2025
@facebook-github-bot
Copy link
Contributor

@j-piasecki merged this pull request in c008604.

uffoltzl pushed a commit to uffoltzl/react-native that referenced this pull request Apr 18, 2025
Summary:
Fixes facebook#45857

The general idea behind this PR is the same for both platforms: dirty all nodes with `MeasurableYogaNode` trait when the layout is constrained with a new `fontSizeMultiplier`. There were a few caveats:
- `ParagraphShadowNode` marks its layout as clean in the constructor in most cases. To prevent that from using a stale measurement I'm using the font scale multiplier stored in `content_` property of the node. That value is then compared with the scale used to create the attributed string kept in the node's state. If those differ, the layout is not cleared.
- On Android, font scale wasn't passed down to the `SurfaceHandler`
- On Android, text measurement relies on cached `DisplayMetrics` which were not updated when the system font scale changed.
- `AndroidTextInputShadowNode` wasn't using `fontSizeMultiplier` at all. I needed to add it in all places where an `AttributedString` is constructed.

## Changelog:

[GENERAL] [FIXED] - Fixed text not updating correctly after changing font scale in settings

Pull Request resolved: facebook#45978

Test Plan:
So far tested on the following code:

```jsx
function App() {
  const [counter,setCounter] = useState(0);
  const [text,setText] = useState('TextInput');
  const [flag,setFlag] = useState(true);

  return (
    <SafeAreaView
        style={{
          flex: 1,
          backgroundColor: '#fff',
          alignItems: 'center',
          justifyContent: 'center',
        }}
    >
      <Text style={{fontSize: 24}}>RN 24 Label Testing {flag ? 'A' : 'B'}</Text>
      <TextInput value={text} onChangeText={setText} style={{fontSize: 24, borderWidth: 1}} placeholder="Placeholder" />
      <Pressable onPress={() => setCounter(prevState => prevState + 1)} style={{backgroundColor: counter % 2 === 0 ? 'red' : 'blue', width: 200, height: 50}} />
      <Pressable onPress={() => setFlag(!flag)} style={{backgroundColor: 'green', width: 200, height: 50}} />
    </SafeAreaView>
  );
}
```

Reviewed By: NickGerleman

Differential Revision: D71727907

Pulled By: j-piasecki

fbshipit-source-id: 240fb5fa4967a9182bce7e885798b233d1e25aea
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. fb-exported Merged This PR has been merged. p: Software Mansion Partner: Software Mansion Partner
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[iOS] increasing font scale (content size catgory) breaks text
4 participants