Repository navigation
Conversation
|
Hi @nowex35! Thank you for your pull request and welcome to our community. Action RequiredIn 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. ProcessIn 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 If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks! |
b3f81db to
2b08f9a
Compare
|
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks! |
| // Does it fit the size? Yoga's pixel-grid rounding can leave the height a float ULP short of | ||
| // the line height (#52642), so a shortfall smaller than one physical pixel still counts as fitting. | ||
| CGFloat heightTolerance = 1.0 / RCTScreenScale(); | ||
| BOOL fitsSize = size.width >= measuredSize.width && size.height + heightTolerance > measuredSize.height; |
There was a problem hiding this comment.
got this suggestion from @coderabbitai:
Compare pixel-rounded heights, not a one-pixel range.
usedRectForTextContainer: returns the raw measured height. React Native rounds that height only later, afterscaleFontSizeToFitSize: has already run. The added tolerance can therefore classify a raw height that exceeds the text container as fitting. The same storage is then drawn in the bounded text view, so the excess can remain clipped.
There was a problem hiding this comment.
Thanks — agreed the 1px range is looser than it needs to be. I've changed compareToSize: to round both the measured height and the container height to the pixel grid (RCTRoundPixelValue) before comparing.
What this fixes: the Yoga float error from #52642 (container 19.99999237 vs measured 20). It also fixes the reverse case, where the measured height is a ULP above the grid, and the larger frame drift Yoga produces at large y offsets (around 1e-3pt beyond y≈2^15).
Trade-off: overflow of less than half a pixel is treated as fitting (the previous revision allowed up to 1px). In my tests, overflow of up to 0.75px stayed inside the line's bottom padding and wasn't visibly clipped.
Compared with main: I found no case where it shrinks text that main renders at full size. Ceil-both rounding gave position-dependent results, and a fixed epsilon failed at large y.
Tested on iPhone 17 (3x); screenshots updated in the description.
Yoga snaps each edge of a node to the pixel grid independently and derives the height from their difference. On screens whose scale is not a power of two, a node far enough down the screen can get a frame one float ULP short of its `lineHeight` (19.999992 for 20, react/yoga#2011). `compareToSize:` had no tolerance on the height, and the line height does not change when the font is scaled, so such a row never fit and was scaled down to `minimumFontSize` even though the text fit (react#52642). Treat a height shortfall smaller than one physical pixel as fitting. Width keeps its existing check. Adds an RNTester example that reproduces the condition on a 3x device.
Round both the container height and the measured text height with RCTRoundPixelValue before comparing them, instead of accepting any shortfall smaller than one physical pixel. The float error from Yoga's pixel-grid snapping (react#52642) still rounds away, while a container that is genuinely a pixel short of the text no longer counts as fitting.
2b08f9a to
6b33da3
Compare
Summary:
<Text adjustsFontSizeToFit numberOfLines={1} style={{lineHeight: N}}>sometimes collapses to the minimum font size on iOS even though the text fits, and which row it happens to depends on the row's position on screen (#52642).Two things combine:
pointScaleFactorand narrowed tofloat, so on a 3x screen a node far enough down the screen can get a height one float ULP short of itslineHeight(19.99999237060547forlineHeight: 20;10.999969482421875for11on a production screen). Subtract rounded edges in pixel space when rounding layout results yoga#2011 addresses that side.compareToSize:inNSTextStorage+FontScaling.mrequiressize.height >= measuredSize.heightwith no tolerance. The text is laid out at the fixed line height at every font ratio, so such a frame never fits,lastRatioWhichFitskeeps its initial value and the text ends atminimumFontSize(4pt before FixminimumFontScalewithadjustsFontSizeToFitin the New Architecture #58492).This PR rounds both the container height and the measured height to the pixel grid with
RCTRoundPixelValue()before comparing them. A float error far below a pixel rounds away, so the frame above fits again, while a container that is genuinely short of the text on the pixel grid (for examplelineHeight: 20.25in a 20pt frame on a 3x screen) still counts as not fitting and shrinks as before. Snapping to the pixel grid matches what the Fabric measure path already does (RCTTextLayoutManagermeasureAttributedString:ceils topointScaleFactor).compareToSize:has no layout context in scope, so the scale comes fromRCTScreenScale()viaRCTRoundPixelValue()inRCTUtils. The width check is unchanged. The file is shared by Paper and Fabric, so both renderers get the same behavior.It also adds an RNTester example (
Font Size Adjustment with lineHeight) that reproduces the condition: tenlineHeight: 20rows after a 0.5pt separator. On a 3x device one of the rows (which one depends on the device) gets a frame of19.99999237060547and collapsed before this change.Fixes #52642
Changelog:
[IOS] [FIXED] - Fix
adjustsFontSizeToFittext collapsing to the minimum size when Yoga's pixel-grid rounding leaves the frame a sub-pixel short oflineHeightTest Plan:
Verified by applying the
NSTextStorage+FontScaling.mchange to React Native 0.86.2 (Expo SDK 57, New Architecture, React-Core built from source) and rebuilding the app, on an iPhone 17 simulator (3x, iOS 26.5). The number next to each row is theonLayoutheight.The ten-row layout from the RNTester example. Row 2 gets a frame of
19.99999237060547(the #52642 case, which collapsed before this change) and row 8 gets20.000015258789062; all ten rows stay at 12pt. Below them, a row whose text is genuinely too wide for 200pt still shrinks to fit, and a row withlineHeight: 10, fontSize: 15is still left unscaled (the width check is untouched and that frame equals the line height):Single-line rows with
lineHeightfrom 20.05 to 20.5 in a 20pt frame. With this change,lineHeight20.25, 20.4 and 20.5 shrink, and 20.05 to 20.2 and 20.3 are not shrunk: