Repository navigation
Commit 6c2acc3
Fix Android text mis-measurement when fontVariationSettings are re-applied to a reused Paint
Summary:
`fontVariationSettings` (#57804, #57805) is applied with `Paint.setFontVariationSettings` after `Paint.setTypeface`. Android's `Paint.setFontVariationSettings` returns early when the requested settings string equals the value the paint already holds, and `Paint.setTypeface` does not clear that stored string. On the legacy variation path (every shipped Android release), the variation instance lives on the typeface, so once `setTypeface` replaces the typeface the axes are gone, but the paint still reports the old settings and the second `setFontVariationSettings` call is a no-op.
React Native hits this sequence on every measurement after the first. `TextLayoutManager.scratchPaintWithAttributes` reuses a thread-local `TextPaint` and resets it with `setTypeface(null)`, which leaves the stale settings string in place. Any subsequent `<Text>` with the same `fontVariationSettings` string is then measured with the un-varied typeface, while the `TextView` draws it through its own paint where the axes apply. The same happens inside `StaticLayout`, whose pooled `MeasuredParagraph`/`TextLine` paints copy the settings string via `Paint.set`. `ReactEditText` has the equivalent problem through `TextView.setFontVariationSettings`, which also skips unchanged values.
With a variable font whose default instance is lighter than the requested `wght`, Yoga receives regular-width metrics while semibold glyphs are drawn, so `numberOfLines={1}` text is ellipsized ("Get Start...") and wrapped text breaks in different places than it renders.
This change clears the paint's settings before re-applying them in `ReactTypefaceUtils.applyFontVariationSettings` and in `ReactEditText.maybeUpdateTypeface`, so the axes are always rebuilt on the current typeface. Paints without previous settings are unaffected.
Changelog:
[ANDROID] [FIXED] - Fix text measured with the wrong width when `fontVariationSettings` are re-applied to a reused `Paint`
Test Plan:
- `./gradlew :packages:react-native:ReactAndroid:testDebugUnitTest --tests "*TextLayoutManagerFontWeightAdjustmentTest*" --tests "*ReactTextInputPropertyTest*"`: 8/8 and 27/27 pass. The three new tests cover the reused-paint reset, the fresh-paint path (no spurious clear), and the measurement scratch paint through `TextLayoutManager.updateTextPaint`. Without the production change, `reused paint re-applies unchanged font variation settings to a new typeface` and `scratch text paint re-applies font variation settings on repeated measurement` fail.
- `yarn lint-kotlin-check` (`./gradlew ktfmtCheck`): passes.
- Manual, Android 17 emulator (API 37), variable Inter font registered via `ReactFontManager.addCustomFont` with entries for weights 100–900, `<Text numberOfLines={1} style={{ fontFamily: "InterVariable", fontWeight: "600", fontVariationSettings: "'wght' 600" }}>`:
- Before: the second and later semibold texts are measured as regular and ellipsized; button label rendered as "Get Start...".
- After: labels render in full and the measured bounds match the drawn text for `'wght' 600`, `650` and `700`, including repeated identical styles and `TextInput`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>1 parent f970054 commit 6c2acc3
3 files changed
Lines changed: 58 additions & 0 deletions
File tree
- packages/react-native/ReactAndroid/src
- main/java/com/facebook/react/views
- textinput
- text
- test/java/com/facebook/react/views/text
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
179 | 179 | | |
180 | 180 | | |
181 | 181 | | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
182 | 186 | | |
183 | 187 | | |
184 | 188 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
689 | 689 | | |
690 | 690 | | |
691 | 691 | | |
| 692 | + | |
| 693 | + | |
| 694 | + | |
| 695 | + | |
692 | 696 | | |
693 | 697 | | |
694 | 698 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
| 27 | + | |
27 | 28 | | |
28 | 29 | | |
29 | 30 | | |
| |||
127 | 128 | | |
128 | 129 | | |
129 | 130 | | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
130 | 180 | | |
131 | 181 | | |
132 | 182 | | |
| |||
0 commit comments