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
This commit is contained in:
Samuel Susla
2023-04-03 15:19:06 +01:00
committed by Lorenzo Sciandra
parent 6d6b6a8345
commit 921bbb6cf1
@@ -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);