From 921bbb6cf1bb09427551864cc195c194aab45844 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Mon, 3 Apr 2023 05:19:28 -0700 Subject: [PATCH] Correct offset when drawing text to TextStorage coordinate system. (#36771) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/36771 Changelog: [internal] Previously, when `NSTextStorage` was cached, we were not accounting for case where text which was aligned to centre or left, was used to size its container. The result was that it was painted outside of its container, therefore invisible. To fix this, we adjust the offset to make sure text is painted correctly. This bug only happens if: - Text is not aligned to left in right to left writing system. - The canvas where text is drawn is not stretched to full width of its parent. - The offset needs to be large enough for this to matter, otherwise the text is just slightly off. - Because of the caching mechanism, it had to be a piece of text that was rendered before. Otherwise it would work. This complexity is worth the trouble to avoid invalidation of layout inside NSTextContainer, which is expensive. Reviewed By: cipolleschi Differential Revision: D44624085 fbshipit-source-id: 1bb8ef88933a49b478a2606dba6bf16b4e728b2b --- .../textlayoutmanager/RCTTextLayoutManager.mm | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm index 35aa4119b81..368c3341055 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm @@ -52,8 +52,6 @@ static NSLineBreakMode RCTNSLineBreakModeFromEllipsizeMode(EllipsizeMode ellipsi textStorage = [self _textStorageForNSAttributesString:attributedString paragraphAttributes:paragraphAttributes size:maximumSize]; - } else { - textStorage.layoutManagers.firstObject.textContainers.firstObject.size = maximumSize; } return [self _measureTextStorage:textStorage]; @@ -79,13 +77,33 @@ static NSLineBreakMode RCTNSLineBreakModeFromEllipsizeMode(EllipsizeMode ellipsi frame:(CGRect)frame textStorage:(NSTextStorage *_Nullable)textStorage { + BOOL createdStorageForFrame = NO; + if (!textStorage) { textStorage = [self textStorageForAttributesString:attributedString paragraphAttributes:paragraphAttributes size:frame.size]; + createdStorageForFrame = YES; } + NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject; NSTextContainer *textContainer = layoutManager.textContainers.firstObject; + CGPoint origin = frame.origin; + + if (!createdStorageForFrame) { + CGRect rect = [layoutManager usedRectForTextContainer:textContainer]; + static auto threshold = 1.0 / RCTScreenScale() + 0.01; // Size of a pixel plus some small threshold. + + // `rect`'s width is stored in double precesion. + // `frame`'s width is also in double precesion but was stored as float in Yoga previously, precesion was lost. + if (std::abs(RCTCeilPixelValue(rect.size.width) - frame.size.width) < threshold) { + // `textStorage` passed to this method was used to calculate size of frame. If that's the case, it's + // width is the same as frame's width. Origin must be adjusted, otherwise glyhps will be painted in wrong + // place. + // We could create new `NSTextStorage` for the specific frame, but that is expensive. + origin.x -= RCTCeilPixelValue(rect.origin.x); + } + } #if TARGET_OS_MACCATALYST CGContextRef context = UIGraphicsGetCurrentContext(); @@ -94,8 +112,8 @@ static NSLineBreakMode RCTNSLineBreakModeFromEllipsizeMode(EllipsizeMode ellipsi #endif NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; - [layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:frame.origin]; - [layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:frame.origin]; + [layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:origin]; + [layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:origin]; #if TARGET_OS_MACCATALYST CGContextRestoreGState(context);