Skip to content

Commit 6ad3898

Browse files
sbuggayfacebook-github-bot
authored andcommitted
Fix text clipping
Summary: Gate the iOS compressed text adjustment behind `enableIOSCompressedTextFrameAdjustment`, expand the paragraph text drawing frame when compressed glyph bounds exceed the view bounds, and center glyph drawing for explicit line heights shorter than the font metrics. Changelog: [internal] Differential Revision: D110430304
1 parent a92c922 commit 6ad3898

3 files changed

Lines changed: 96 additions & 8 deletions

File tree

packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#import "RCTParagraphComponentAccessibilityProvider.h"
1010

1111
#import <MobileCoreServices/UTCoreTypes.h>
12+
#import <react/featureflags/ReactNativeFeatureFlags.h>
1213
#import <react/renderer/components/text/ParagraphComponentDescriptor.h>
1314
#import <react/renderer/components/text/ParagraphProps.h>
1415
#import <react/renderer/components/text/ParagraphState.h>
@@ -24,6 +25,14 @@
2425

2526
using namespace facebook::react;
2627

28+
@interface RCTTextLayoutManager (RCTParagraphComponentViewPrivate)
29+
30+
- (CGFloat)drawingHeightForAttributedString:(facebook::react::AttributedString)attributedString
31+
paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes
32+
size:(CGSize)size;
33+
34+
@end
35+
2736
// ParagraphTextView is an auxiliary view we set as contentView so the drawing
2837
// can happen on top of the layers manipulated by RCTViewComponentView (the parent view)
2938
@interface RCTParagraphTextView : UIView
@@ -149,7 +158,30 @@ - (void)layoutSubviews
149158
{
150159
[super layoutSubviews];
151160

152-
_textView.frame = self.bounds;
161+
CGRect textFrame = self.bounds;
162+
if (!ReactNativeFeatureFlags::enableIOSCompressedTextFrameAdjustment()) {
163+
_textView.frame = textFrame;
164+
return;
165+
}
166+
167+
if (_textView.state && textFrame.size.height > 0) {
168+
const auto &stateData = _textView.state->getData();
169+
auto textLayoutManager = stateData.layoutManager.lock();
170+
if (textLayoutManager) {
171+
RCTTextLayoutManager *nativeTextLayoutManager =
172+
(RCTTextLayoutManager *)unwrapManagedObject(textLayoutManager->getNativeTextLayoutManager());
173+
CGFloat drawingHeight = [nativeTextLayoutManager drawingHeightForAttributedString:stateData.attributedString
174+
paragraphAttributes:_paragraphAttributes
175+
size:textFrame.size];
176+
if (drawingHeight > textFrame.size.height) {
177+
CGFloat extraHeight = drawingHeight - textFrame.size.height;
178+
textFrame.origin.y -= extraHeight / 2.0;
179+
textFrame.size.height = drawingHeight;
180+
}
181+
}
182+
}
183+
184+
_textView.frame = textFrame;
153185
}
154186

155187
#pragma mark - Accessibility
@@ -194,7 +226,7 @@ - (NSArray *)accessibilityElements
194226
if (textLayoutManager) {
195227
RCTTextLayoutManager *nativeTextLayoutManager =
196228
(RCTTextLayoutManager *)unwrapManagedObject(textLayoutManager->getNativeTextLayoutManager());
197-
CGRect frame = RCTCGRectFromRect(_layoutMetrics.getContentFrame());
229+
CGRect frame = self.bounds;
198230
_accessibilityProvider =
199231
[[RCTParagraphComponentAccessibilityProvider alloc] initWithString:data.attributedString
200232
layoutManager:nativeTextLayoutManager
@@ -268,7 +300,7 @@ - (SharedTouchEventEmitter)touchEventEmitterAtPoint:(CGPoint)point
268300

269301
RCTTextLayoutManager *nativeTextLayoutManager =
270302
(RCTTextLayoutManager *)unwrapManagedObject(textLayoutManager->getNativeTextLayoutManager());
271-
CGRect frame = RCTCGRectFromRect(_layoutMetrics.getContentFrame());
303+
CGRect frame = self.bounds;
272304

273305
auto eventEmitter = [nativeTextLayoutManager getEventEmitterWithAttributeString:stateData.attributedString
274306
paragraphAttributes:_paragraphAttributes
@@ -404,7 +436,7 @@ - (void)drawRect:(CGRect)rect
404436
RCTTextLayoutManager *nativeTextLayoutManager =
405437
(RCTTextLayoutManager *)unwrapManagedObject(textLayoutManager->getNativeTextLayoutManager());
406438

407-
CGRect frame = RCTCGRectFromRect(_layoutMetrics.getContentFrame());
439+
CGRect frame = self.bounds;
408440

409441
[nativeTextLayoutManager drawAttributedString:stateData.attributedString
410442
paragraphAttributes:_paragraphAttributes

packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ static void RCTApplyBaselineOffsetForRange(NSMutableAttributedString *attributed
369369
maximumFontLineHeight = MAX(font.lineHeight, maximumFontLineHeight);
370370
}];
371371

372-
if (maximumLineHeight < maximumFontLineHeight) {
372+
if (maximumLineHeight < maximumFontLineHeight && !ReactNativeFeatureFlags::enableIOSCompressedTextFrameAdjustment()) {
373373
return;
374374
}
375375

packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,42 @@ - (TextMeasurement)measureAttributedString:(AttributedString)attributedString
6868
layoutConstraints:layoutConstraints];
6969
}
7070

71+
- (CGFloat)drawingHeightForAttributedString:(AttributedString)attributedString
72+
paragraphAttributes:(ParagraphAttributes)paragraphAttributes
73+
size:(CGSize)size
74+
{
75+
if (!ReactNativeFeatureFlags::enableIOSCompressedTextFrameAdjustment()) {
76+
return size.height;
77+
}
78+
79+
NSTextStorage *textStorage = [self
80+
_textStorageAndLayoutManagerWithAttributesString:[self _nsAttributedStringFromAttributedString:attributedString]
81+
paragraphAttributes:paragraphAttributes
82+
size:size];
83+
NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject;
84+
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
85+
[layoutManager ensureLayoutForTextContainer:textContainer];
86+
87+
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
88+
__block CGFloat maximumLineHeight = 0;
89+
[textStorage enumerateAttribute:NSParagraphStyleAttributeName
90+
inRange:NSMakeRange(0, textStorage.length)
91+
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
92+
usingBlock:^(NSParagraphStyle *paragraphStyle, __unused NSRange range, __unused BOOL *stop) {
93+
if (paragraphStyle != nil) {
94+
maximumLineHeight = MAX(paragraphStyle.maximumLineHeight, maximumLineHeight);
95+
}
96+
}];
97+
if (maximumLineHeight == 0 || glyphRange.length == 0) {
98+
return size.height;
99+
}
100+
101+
CGRect glyphBounds = [layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer];
102+
CGFloat glyphHeight = CGRectGetMaxY(glyphBounds) - MIN(glyphBounds.origin.y, 0);
103+
CGFloat drawingHeight = MAX(size.height, glyphHeight);
104+
return drawingHeight;
105+
}
106+
71107
- (void)drawAttributedString:(AttributedString)attributedString
72108
paragraphAttributes:(ParagraphAttributes)paragraphAttributes
73109
frame:(CGRect)frame
@@ -90,8 +126,27 @@ - (void)drawAttributedString:(AttributedString)attributedString
90126

91127
[self processTruncatedAttributedText:textStorage textContainer:textContainer layoutManager:layoutManager];
92128

93-
[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:frame.origin];
94-
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:frame.origin];
129+
CGPoint textDrawPoint = frame.origin;
130+
if (ReactNativeFeatureFlags::enableIOSCompressedTextFrameAdjustment() && glyphRange.length > 0) {
131+
__block CGFloat maximumDrawLineHeight = 0;
132+
[textStorage enumerateAttribute:NSParagraphStyleAttributeName
133+
inRange:NSMakeRange(0, textStorage.length)
134+
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
135+
usingBlock:^(NSParagraphStyle *paragraphStyle, __unused NSRange range, __unused BOOL *stop) {
136+
if (paragraphStyle != nil) {
137+
maximumDrawLineHeight = MAX(paragraphStyle.maximumLineHeight, maximumDrawLineHeight);
138+
}
139+
}];
140+
if (maximumDrawLineHeight > 0) {
141+
CGRect finalGlyphBounds = [layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer];
142+
if (finalGlyphBounds.origin.y < 0) {
143+
textDrawPoint.y += (frame.size.height - finalGlyphBounds.size.height) / 2.0 - finalGlyphBounds.origin.y;
144+
}
145+
}
146+
}
147+
148+
[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:textDrawPoint];
149+
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:textDrawPoint];
95150

96151
#if TARGET_OS_MACCATALYST
97152
CGContextRestoreGState(context);
@@ -504,7 +559,8 @@ - (TextMeasurement)_measureTextStorage:(NSTextStorage *)textStorage
504559
}
505560
}];
506561

507-
CGSize size = [layoutManager usedRectForTextContainer:textContainer].size;
562+
CGRect usedBounds = [layoutManager usedRectForTextContainer:textContainer];
563+
CGSize size = usedBounds.size;
508564

509565
if (textDidWrap) {
510566
size.width = textContainer.size.width;

0 commit comments

Comments
 (0)