Skip to content

Commit 5bb7c71

Browse files
sbuggayfacebook-github-bot
authored andcommitted
Fix text clipping (#57408)
Summary: Gate the iOS compressed text adjustment behind a runtime gate, 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] Reviewed By: javache Differential Revision: D110430304
1 parent 1585593 commit 5bb7c71

3 files changed

Lines changed: 100 additions & 6 deletions

File tree

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

Lines changed: 39 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,13 +25,23 @@
2425

2526
using namespace facebook::react;
2627

28+
@interface RCTTextLayoutManager (RCTParagraphComponentViewPrivate)
29+
30+
- (CGRect)drawingFrameForAttributedString:(facebook::react::AttributedString)attributedString
31+
paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes
32+
frame:(CGRect)frame
33+
containerFrame:(CGRect *)containerFrame;
34+
35+
@end
36+
2737
// ParagraphTextView is an auxiliary view we set as contentView so the drawing
2838
// can happen on top of the layers manipulated by RCTViewComponentView (the parent view)
2939
@interface RCTParagraphTextView : UIView
3040

3141
@property (nonatomic) ParagraphShadowNode::ConcreteState::Shared state;
3242
@property (nonatomic) ParagraphAttributes paragraphAttributes;
3343
@property (nonatomic) LayoutMetrics layoutMetrics;
44+
@property (nonatomic) CGRect drawingFrame;
3445

3546
@end
3647

@@ -50,6 +61,7 @@ @implementation RCTParagraphComponentView {
5061
RCTParagraphComponentAccessibilityProvider *_accessibilityProvider;
5162
UILongPressGestureRecognizer *_longPressGestureRecognizer;
5263
RCTParagraphTextView *_textView;
64+
CGRect _textLayoutFrame;
5365
}
5466

5567
- (instancetype)initWithFrame:(CGRect)frame
@@ -60,6 +72,7 @@ - (instancetype)initWithFrame:(CGRect)frame
6072
self.opaque = NO;
6173
_textView = [RCTParagraphTextView new];
6274
_textView.backgroundColor = UIColor.clearColor;
75+
_textView.drawingFrame = self.bounds;
6376
self.contentView = _textView;
6477
}
6578

@@ -134,6 +147,7 @@ - (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics
134147
// re-applying individual sub-values which weren't changed.
135148
[super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:_layoutMetrics];
136149
_textView.layoutMetrics = _layoutMetrics;
150+
_textLayoutFrame = RCTCGRectFromRect(_layoutMetrics.getContentFrame());
137151
[_textView setNeedsDisplay];
138152
[self setNeedsLayout];
139153
}
@@ -149,7 +163,28 @@ - (void)layoutSubviews
149163
{
150164
[super layoutSubviews];
151165

152-
_textView.frame = self.bounds;
166+
CGRect textViewFrame = self.bounds;
167+
CGRect drawingFrame = RCTCGRectFromRect(_layoutMetrics.getContentFrame());
168+
169+
if (ReactNativeFeatureFlags::enableIOSCompressedTextFrameAdjustment() && _textView.state &&
170+
drawingFrame.size.height > 0) {
171+
const auto &stateData = _textView.state->getData();
172+
auto textLayoutManager = stateData.layoutManager.lock();
173+
if (textLayoutManager) {
174+
RCTTextLayoutManager *nativeTextLayoutManager =
175+
(RCTTextLayoutManager *)unwrapManagedObject(textLayoutManager->getNativeTextLayoutManager());
176+
CGRect drawingContainerFrame = drawingFrame;
177+
drawingFrame = [nativeTextLayoutManager drawingFrameForAttributedString:stateData.attributedString
178+
paragraphAttributes:_paragraphAttributes
179+
frame:drawingFrame
180+
containerFrame:&drawingContainerFrame];
181+
textViewFrame = CGRectUnion(textViewFrame, drawingContainerFrame);
182+
}
183+
}
184+
185+
_textLayoutFrame = drawingFrame;
186+
_textView.frame = textViewFrame;
187+
_textView.drawingFrame = CGRectOffset(drawingFrame, -textViewFrame.origin.x, -textViewFrame.origin.y);
153188
}
154189

155190
#pragma mark - Accessibility
@@ -194,7 +229,7 @@ - (NSArray *)accessibilityElements
194229
if (textLayoutManager) {
195230
RCTTextLayoutManager *nativeTextLayoutManager =
196231
(RCTTextLayoutManager *)unwrapManagedObject(textLayoutManager->getNativeTextLayoutManager());
197-
CGRect frame = RCTCGRectFromRect(_layoutMetrics.getContentFrame());
232+
CGRect frame = _textLayoutFrame;
198233
_accessibilityProvider =
199234
[[RCTParagraphComponentAccessibilityProvider alloc] initWithString:data.attributedString
200235
layoutManager:nativeTextLayoutManager
@@ -268,7 +303,7 @@ - (SharedTouchEventEmitter)touchEventEmitterAtPoint:(CGPoint)point
268303

269304
RCTTextLayoutManager *nativeTextLayoutManager =
270305
(RCTTextLayoutManager *)unwrapManagedObject(textLayoutManager->getNativeTextLayoutManager());
271-
CGRect frame = RCTCGRectFromRect(_layoutMetrics.getContentFrame());
306+
CGRect frame = _textLayoutFrame;
272307

273308
auto eventEmitter = [nativeTextLayoutManager getEventEmitterWithAttributeString:stateData.attributedString
274309
paragraphAttributes:_paragraphAttributes
@@ -404,7 +439,7 @@ - (void)drawRect:(CGRect)rect
404439
RCTTextLayoutManager *nativeTextLayoutManager =
405440
(RCTTextLayoutManager *)unwrapManagedObject(textLayoutManager->getNativeTextLayoutManager());
406441

407-
CGRect frame = RCTCGRectFromRect(_layoutMetrics.getContentFrame());
442+
CGRect frame = _drawingFrame;
408443

409444
[nativeTextLayoutManager drawAttributedString:stateData.attributedString
410445
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: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,64 @@ - (TextMeasurement)measureAttributedString:(AttributedString)attributedString
6868
layoutConstraints:layoutConstraints];
6969
}
7070

71+
- (CGRect)drawingFrameForAttributedString:(AttributedString)attributedString
72+
paragraphAttributes:(ParagraphAttributes)paragraphAttributes
73+
frame:(CGRect)frame
74+
containerFrame:(CGRect *)containerFrame
75+
{
76+
if (containerFrame != nullptr) {
77+
*containerFrame = frame;
78+
}
79+
80+
if (!ReactNativeFeatureFlags::enableIOSCompressedTextFrameAdjustment()) {
81+
return frame;
82+
}
83+
84+
NSTextStorage *textStorage = [self
85+
_textStorageAndLayoutManagerWithAttributesString:[self _nsAttributedStringFromAttributedString:attributedString]
86+
paragraphAttributes:paragraphAttributes
87+
size:frame.size];
88+
NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject;
89+
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
90+
[layoutManager ensureLayoutForTextContainer:textContainer];
91+
92+
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
93+
[self processTruncatedAttributedText:textStorage textContainer:textContainer layoutManager:layoutManager];
94+
95+
__block CGFloat maximumLineHeight = 0;
96+
[textStorage enumerateAttribute:NSParagraphStyleAttributeName
97+
inRange:NSMakeRange(0, textStorage.length)
98+
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
99+
usingBlock:^(NSParagraphStyle *paragraphStyle, __unused NSRange range, __unused BOOL *stop) {
100+
if (paragraphStyle != nil) {
101+
maximumLineHeight = MAX(paragraphStyle.maximumLineHeight, maximumLineHeight);
102+
}
103+
}];
104+
if (maximumLineHeight == 0 || glyphRange.length == 0) {
105+
return frame;
106+
}
107+
108+
CGRect glyphBounds = [layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer];
109+
CGFloat glyphHeight = CGRectGetMaxY(glyphBounds) - MIN(glyphBounds.origin.y, 0);
110+
CGFloat drawingHeight = MAX(frame.size.height, glyphHeight);
111+
CGFloat extraHeight = drawingHeight - frame.size.height;
112+
113+
CGRect localContainerFrame = frame;
114+
localContainerFrame.origin.y -= extraHeight / 2.0;
115+
localContainerFrame.size.height = drawingHeight;
116+
if (containerFrame != nullptr) {
117+
*containerFrame = localContainerFrame;
118+
}
119+
120+
CGRect drawingFrame = frame;
121+
drawingFrame.size.height = drawingHeight;
122+
drawingFrame.origin.y = localContainerFrame.origin.y;
123+
if (glyphBounds.origin.y < 0) {
124+
drawingFrame.origin.y += (drawingHeight - glyphBounds.size.height) / 2.0 - glyphBounds.origin.y;
125+
}
126+
return drawingFrame;
127+
}
128+
71129
- (void)drawAttributedString:(AttributedString)attributedString
72130
paragraphAttributes:(ParagraphAttributes)paragraphAttributes
73131
frame:(CGRect)frame
@@ -504,7 +562,8 @@ - (TextMeasurement)_measureTextStorage:(NSTextStorage *)textStorage
504562
}
505563
}];
506564

507-
CGSize size = [layoutManager usedRectForTextContainer:textContainer].size;
565+
CGRect usedBounds = [layoutManager usedRectForTextContainer:textContainer];
566+
CGSize size = usedBounds.size;
508567

509568
if (textDidWrap) {
510569
size.width = textContainer.size.width;

0 commit comments

Comments
 (0)