Skip to content

Fix text wrapping in absolutely positioned elements on Android (#56651)#56651

Closed
clocksarestupid wants to merge 1 commit into
react:mainfrom
clocksarestupid:export-D102920508
Closed

Fix text wrapping in absolutely positioned elements on Android (#56651)#56651
clocksarestupid wants to merge 1 commit into
react:mainfrom
clocksarestupid:export-D102920508

Conversation

@clocksarestupid

@clocksarestupid clocksarestupid commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Summary:

Context

On Android TV, text inside absolutely-positioned Views wraps unexpectedly when the computed width from Yoga is fractional. This manifests in Instagram TV as "Keep watching" breaking to two lines in the exit dialog when the button text is focused (rendered via an absolutely-positioned overlay for color animation).

The root cause is a rounding mismatch in React Native's Android TextLayoutManager: desiredWidth (the text's needed width) uses ceil() (rounds up), but layoutWidth in EXACTLY mode uses floor() (rounds down). When Yoga passes a fractional width (e.g. 258.5px), the container gets floor(258.5) = 258px but the text needs ceil(258.3) = 259px, causing a 1px shortfall that triggers wrapping.

Fix

In TextLayoutManager.createLayout(), change floor(width).toInt() to ceil(width).toInt() for YogaMeasureMode.EXACTLY in both layout paths so the behavior is consistent regardless of which Layout class is chosen:

  • BoringLayout (single-line text that fits)
  • StaticLayout (multi-line or complex text)

YogaMeasureMode.AT_MOST is intentionally left as floor(width).toInt(). AT_MOST is a constraint contract from Yoga ("do not exceed this width"), so flooring remains the correct conservative behavior — ceiling there could violate the constraint by up to 1px.

The BoringLayout entry guard (boring.width <= floor(width)) is also left unchanged. If a boring text fails the guard, it falls through to the StaticLayout path, which now also ceils for EXACTLY, so no truncation results — the only effect is a slightly less optimal layout class choice in a narrow edge case.

Why ceiling EXACTLY is safe

EXACTLY means Yoga has guaranteed this width — the container has been allocated at least the full fractional width upstream. Ceiling the local layout width by 1px cannot exceed what Yoga has reserved, while flooring it produced a 1px shortfall that mismatched desiredWidth's ceiling and triggered wrapping.

The compensating mechanism in calculateWidth() — which returns the raw fractional width to Yoga for EXACTLY mode rather than the floored layout.width — is preserved, so Yoga's upstream allocation reasoning is unchanged. This was the subpixel-safety property introduced in D74685353; only the local pixel rounding inside createLayout changes from "round down 1px and risk wrapping" to "round up 1px and match desiredWidth".

Testing

Added a new test case to cover this behavior. If this needs to be adjusted in the future, a failure will highlight this bug and ensure it's covered or mitigated with additional test cases.

Notes

This was confirmed as a problem with an absolutely positioned style with left:0, right:0 applied. Width sizing was confirmed to be the issue when left:-1, right:-1 resolved the issue. Further investigation found this fix in text sizing.

Only EXACTLY is needed to fix the observed Instagram TV bug. AT_MOST is left untouched because the constraint semantics differ.

Changelog: [Android] [Fixed] - Fix 1px text wrapping in absolutely positioned elements caused by fractional Yoga widths

Reviewed By: Abbondanzo

Differential Revision: D102920508

@meta-cla meta-cla 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 Apr 29, 2026
@meta-codesync

meta-codesync Bot commented Apr 29, 2026

Copy link
Copy Markdown

@clocksarestupid has exported this pull request. If you are a Meta employee, you can view the originating Diff in D102920508.

@meta-codesync meta-codesync Bot changed the title Fix text wrapping in absolutely positioned elements on Android Fix text wrapping in absolutely positioned elements on Android (#56651) Apr 29, 2026
clocksarestupid added a commit to clocksarestupid/react-native that referenced this pull request Apr 29, 2026
…#56651)

Summary:

## Context

On Android TV, text inside absolutely-positioned Views wraps unexpectedly when the computed width from Yoga is fractional. This manifests in Instagram TV as "Keep watching" breaking to two lines in the exit dialog when the button text is focused (rendered via an absolutely-positioned overlay for color animation).

The root cause is a rounding mismatch in React Native's Android `TextLayoutManager`: `desiredWidth` (the text's needed width) uses `ceil()` (rounds up), but `layoutWidth` in `EXACTLY` mode uses `floor()` (rounds down). When Yoga passes a fractional width (e.g. 258.5px), the container gets `floor(258.5) = 258px` but the text needs `ceil(258.3) = 259px`, causing a 1px shortfall that triggers wrapping.

## Fix

In `TextLayoutManager.createLayout()`, change `floor(width).toInt()` to `ceil(width).toInt()` for `YogaMeasureMode.EXACTLY` in **both** layout paths so the behavior is consistent regardless of which `Layout` class is chosen:

- BoringLayout (single-line text that fits)
- StaticLayout (multi-line or complex text)

`YogaMeasureMode.AT_MOST` is intentionally left as `floor(width).toInt()`. `AT_MOST` is a constraint contract from Yoga ("do not exceed this width"), so flooring remains the correct conservative behavior — ceiling there could violate the constraint by up to 1px.

The BoringLayout entry guard (`boring.width <= floor(width)`) is also left unchanged. If a boring text fails the guard, it falls through to the StaticLayout path, which now also ceils for `EXACTLY`, so no truncation results — the only effect is a slightly less optimal layout class choice in a narrow edge case.

## Why ceiling `EXACTLY` is safe

`EXACTLY` means Yoga has guaranteed this width — the container has been allocated at least the full fractional width upstream. Ceiling the local layout width by 1px cannot exceed what Yoga has reserved, while flooring it produced a 1px shortfall that mismatched `desiredWidth`'s ceiling and triggered wrapping.

The compensating mechanism in `calculateWidth()` — which returns the raw fractional width to Yoga for `EXACTLY` mode rather than the floored `layout.width` — is preserved, so Yoga's upstream allocation reasoning is unchanged. This was the subpixel-safety property introduced in D74685353; only the local pixel rounding inside `createLayout` changes from "round down 1px and risk wrapping" to "round up 1px and match `desiredWidth`".

## Notes

This was confirmed as a problem with an absolutely positioned style with `left:0, right:0` applied. Width sizing was confirmed to be the issue when `left:-1, right:-1` resolved the issue. Further investigation found this fix in text sizing.

Only `EXACTLY` is needed to fix the observed Instagram TV bug. `AT_MOST` is left untouched because the constraint semantics differ.

## Changelog: [Android] [Fixed] - Fix 1px text wrapping in absolutely positioned elements caused by fractional Yoga widths

Differential Revision: D102920508
clocksarestupid added a commit to clocksarestupid/react-native that referenced this pull request Apr 30, 2026
…#56651)

Summary:

## Context

On Android TV, text inside absolutely-positioned Views wraps unexpectedly when the computed width from Yoga is fractional. This manifests in Instagram TV as "Keep watching" breaking to two lines in the exit dialog when the button text is focused (rendered via an absolutely-positioned overlay for color animation).

The root cause is a rounding mismatch in React Native's Android `TextLayoutManager`: `desiredWidth` (the text's needed width) uses `ceil()` (rounds up), but `layoutWidth` in `EXACTLY` mode uses `floor()` (rounds down). When Yoga passes a fractional width (e.g. 258.5px), the container gets `floor(258.5) = 258px` but the text needs `ceil(258.3) = 259px`, causing a 1px shortfall that triggers wrapping.

## Fix

In `TextLayoutManager.createLayout()`, change `floor(width).toInt()` to `ceil(width).toInt()` for `YogaMeasureMode.EXACTLY` in **both** layout paths so the behavior is consistent regardless of which `Layout` class is chosen:

- BoringLayout (single-line text that fits)
- StaticLayout (multi-line or complex text)

`YogaMeasureMode.AT_MOST` is intentionally left as `floor(width).toInt()`. `AT_MOST` is a constraint contract from Yoga ("do not exceed this width"), so flooring remains the correct conservative behavior — ceiling there could violate the constraint by up to 1px.

The BoringLayout entry guard (`boring.width <= floor(width)`) is also left unchanged. If a boring text fails the guard, it falls through to the StaticLayout path, which now also ceils for `EXACTLY`, so no truncation results — the only effect is a slightly less optimal layout class choice in a narrow edge case.

## Why ceiling `EXACTLY` is safe

`EXACTLY` means Yoga has guaranteed this width — the container has been allocated at least the full fractional width upstream. Ceiling the local layout width by 1px cannot exceed what Yoga has reserved, while flooring it produced a 1px shortfall that mismatched `desiredWidth`'s ceiling and triggered wrapping.

The compensating mechanism in `calculateWidth()` — which returns the raw fractional width to Yoga for `EXACTLY` mode rather than the floored `layout.width` — is preserved, so Yoga's upstream allocation reasoning is unchanged. This was the subpixel-safety property introduced in D74685353; only the local pixel rounding inside `createLayout` changes from "round down 1px and risk wrapping" to "round up 1px and match `desiredWidth`".

## Testing

Added a new test case to cover this behavior. If this needs to be adjusted in the future, a failure will highlight this bug and ensure it's covered or mitigated with additional test cases.

## Notes

This was confirmed as a problem with an absolutely positioned style with `left:0, right:0` applied. Width sizing was confirmed to be the issue when `left:-1, right:-1` resolved the issue. Further investigation found this fix in text sizing.

Only `EXACTLY` is needed to fix the observed Instagram TV bug. `AT_MOST` is left untouched because the constraint semantics differ.

## Changelog: [Android] [Fixed] - Fix 1px text wrapping in absolutely positioned elements caused by fractional Yoga widths

Differential Revision: D102920508
clocksarestupid added a commit to clocksarestupid/react-native that referenced this pull request Apr 30, 2026
…#56651)

Summary:

## Context

On Android TV, text inside absolutely-positioned Views wraps unexpectedly when the computed width from Yoga is fractional. This manifests in Instagram TV as "Keep watching" breaking to two lines in the exit dialog when the button text is focused (rendered via an absolutely-positioned overlay for color animation).

The root cause is a rounding mismatch in React Native's Android `TextLayoutManager`: `desiredWidth` (the text's needed width) uses `ceil()` (rounds up), but `layoutWidth` in `EXACTLY` mode uses `floor()` (rounds down). When Yoga passes a fractional width (e.g. 258.5px), the container gets `floor(258.5) = 258px` but the text needs `ceil(258.3) = 259px`, causing a 1px shortfall that triggers wrapping.

## Fix

In `TextLayoutManager.createLayout()`, change `floor(width).toInt()` to `ceil(width).toInt()` for `YogaMeasureMode.EXACTLY` in **both** layout paths so the behavior is consistent regardless of which `Layout` class is chosen:

- BoringLayout (single-line text that fits)
- StaticLayout (multi-line or complex text)

`YogaMeasureMode.AT_MOST` is intentionally left as `floor(width).toInt()`. `AT_MOST` is a constraint contract from Yoga ("do not exceed this width"), so flooring remains the correct conservative behavior — ceiling there could violate the constraint by up to 1px.

The BoringLayout entry guard (`boring.width <= floor(width)`) is also left unchanged. If a boring text fails the guard, it falls through to the StaticLayout path, which now also ceils for `EXACTLY`, so no truncation results — the only effect is a slightly less optimal layout class choice in a narrow edge case.

## Why ceiling `EXACTLY` is safe

`EXACTLY` means Yoga has guaranteed this width — the container has been allocated at least the full fractional width upstream. Ceiling the local layout width by 1px cannot exceed what Yoga has reserved, while flooring it produced a 1px shortfall that mismatched `desiredWidth`'s ceiling and triggered wrapping.

The compensating mechanism in `calculateWidth()` — which returns the raw fractional width to Yoga for `EXACTLY` mode rather than the floored `layout.width` — is preserved, so Yoga's upstream allocation reasoning is unchanged. This was the subpixel-safety property introduced in D74685353; only the local pixel rounding inside `createLayout` changes from "round down 1px and risk wrapping" to "round up 1px and match `desiredWidth`".

## Testing

Added a new test case to cover this behavior. If this needs to be adjusted in the future, a failure will highlight this bug and ensure it's covered or mitigated with additional test cases.

## Notes

This was confirmed as a problem with an absolutely positioned style with `left:0, right:0` applied. Width sizing was confirmed to be the issue when `left:-1, right:-1` resolved the issue. Further investigation found this fix in text sizing.

Only `EXACTLY` is needed to fix the observed Instagram TV bug. `AT_MOST` is left untouched because the constraint semantics differ.

## Changelog: [Android] [Fixed] - Fix 1px text wrapping in absolutely positioned elements caused by fractional Yoga widths

Reviewed By: Abbondanzo

Differential Revision: D102920508
…#56651)

Summary:

## Context

On Android TV, text inside absolutely-positioned Views wraps unexpectedly when the computed width from Yoga is fractional. This manifests in Instagram TV as "Keep watching" breaking to two lines in the exit dialog when the button text is focused (rendered via an absolutely-positioned overlay for color animation).

The root cause is a rounding mismatch in React Native's Android `TextLayoutManager`: `desiredWidth` (the text's needed width) uses `ceil()` (rounds up), but `layoutWidth` in `EXACTLY` mode uses `floor()` (rounds down). When Yoga passes a fractional width (e.g. 258.5px), the container gets `floor(258.5) = 258px` but the text needs `ceil(258.3) = 259px`, causing a 1px shortfall that triggers wrapping.

## Fix

In `TextLayoutManager.createLayout()`, change `floor(width).toInt()` to `ceil(width).toInt()` for `YogaMeasureMode.EXACTLY` in **both** layout paths so the behavior is consistent regardless of which `Layout` class is chosen:

- BoringLayout (single-line text that fits)
- StaticLayout (multi-line or complex text)

`YogaMeasureMode.AT_MOST` is intentionally left as `floor(width).toInt()`. `AT_MOST` is a constraint contract from Yoga ("do not exceed this width"), so flooring remains the correct conservative behavior — ceiling there could violate the constraint by up to 1px.

The BoringLayout entry guard (`boring.width <= floor(width)`) is also left unchanged. If a boring text fails the guard, it falls through to the StaticLayout path, which now also ceils for `EXACTLY`, so no truncation results — the only effect is a slightly less optimal layout class choice in a narrow edge case.

## Why ceiling `EXACTLY` is safe

`EXACTLY` means Yoga has guaranteed this width — the container has been allocated at least the full fractional width upstream. Ceiling the local layout width by 1px cannot exceed what Yoga has reserved, while flooring it produced a 1px shortfall that mismatched `desiredWidth`'s ceiling and triggered wrapping.

The compensating mechanism in `calculateWidth()` — which returns the raw fractional width to Yoga for `EXACTLY` mode rather than the floored `layout.width` — is preserved, so Yoga's upstream allocation reasoning is unchanged. This was the subpixel-safety property introduced in D74685353; only the local pixel rounding inside `createLayout` changes from "round down 1px and risk wrapping" to "round up 1px and match `desiredWidth`".

## Testing

Added a new test case to cover this behavior. If this needs to be adjusted in the future, a failure will highlight this bug and ensure it's covered or mitigated with additional test cases.

## Notes

This was confirmed as a problem with an absolutely positioned style with `left:0, right:0` applied. Width sizing was confirmed to be the issue when `left:-1, right:-1` resolved the issue. Further investigation found this fix in text sizing.

Only `EXACTLY` is needed to fix the observed Instagram TV bug. `AT_MOST` is left untouched because the constraint semantics differ.

## Changelog: [Android] [Fixed] - Fix 1px text wrapping in absolutely positioned elements caused by fractional Yoga widths

Reviewed By: Abbondanzo

Differential Revision: D102920508
@react-native-bot

Copy link
Copy Markdown
Collaborator

This pull request was successfully merged by @clocksarestupid in 90c2c59

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 30, 2026
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. meta-exported p: Facebook Partner: Facebook Partner

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants