diff --git a/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm b/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm index 363170e617ed5c..6c815d267eefcd 100644 --- a/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm +++ b/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm @@ -237,6 +237,8 @@ - (NSTextStorage *)textStorageAndLayoutManagerThatFitsSize:(CGSize)size exclusiv maximumFontSize:self.textAttributes.effectiveFont.pointSize]; } + [self processTruncatedAttributedText:textStorage textContainer:textContainer layoutManager:layoutManager]; + if (!exclusiveOwnership) { [_cachedTextStorages setObject:textStorage forKey:key]; } @@ -244,6 +246,50 @@ - (NSTextStorage *)textStorageAndLayoutManagerThatFitsSize:(CGSize)size exclusiv return textStorage; } +- (void)processTruncatedAttributedText:(NSTextStorage *)textStorage + textContainer:(NSTextContainer *)textContainer + layoutManager:(NSLayoutManager *)layoutManager +{ + if (_maximumNumberOfLines > 0) { + [layoutManager ensureLayoutForTextContainer:textContainer]; + NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; + __block int line = 0; + [layoutManager + enumerateLineFragmentsForGlyphRange:glyphRange + usingBlock:^( + CGRect rect, + CGRect usedRect, + NSTextContainer *_Nonnull _, + NSRange lineGlyphRange, + BOOL *_Nonnull stop) { + if (line == textContainer.maximumNumberOfLines - 1) { + NSRange truncatedRange = [layoutManager + truncatedGlyphRangeInLineFragmentForGlyphAtIndex:lineGlyphRange.location]; + + if (truncatedRange.location != NSNotFound) { + NSRange characterRange = + [layoutManager characterRangeForGlyphRange:truncatedRange + actualGlyphRange:nil]; + if (characterRange.location > 0 && characterRange.length > 0) { + // Remove color attributes for truncated range + for (NSAttributedStringKey key in + @[ NSForegroundColorAttributeName, NSBackgroundColorAttributeName ]) { + [textStorage removeAttribute:key range:characterRange]; + id attribute = [textStorage attribute:key + atIndex:characterRange.location - 1 + effectiveRange:nil]; + if (attribute) { + [textStorage addAttribute:key value:attribute range:characterRange]; + } + } + } + } + } + line++; + }]; + } +} + - (void)layoutWithMetrics:(RCTLayoutMetrics)layoutMetrics layoutContext:(RCTLayoutContext)layoutContext { // If the view got new `contentFrame`, we have to redraw it because 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 b62dc9b536b24c..0cc55820b7da4e 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 @@ -83,6 +83,9 @@ - (void)drawAttributedString:(AttributedString)attributedString #endif NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; + + [self processTruncatedAttributedText:textStorage textContainer:textContainer layoutManager:layoutManager]; + [layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:frame.origin]; [layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:frame.origin]; @@ -123,6 +126,49 @@ - (void)drawAttributedString:(AttributedString)attributedString } } +- (void)processTruncatedAttributedText:(NSTextStorage *)textStorage + textContainer:(NSTextContainer *)textContainer + layoutManager:(NSLayoutManager *)layoutManager +{ + if (textContainer.maximumNumberOfLines > 0) { + [layoutManager ensureLayoutForTextContainer:textContainer]; + NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; + __block int line = 0; + [layoutManager + enumerateLineFragmentsForGlyphRange:glyphRange + usingBlock:^( + CGRect rect, + CGRect usedRect, + NSTextContainer *_Nonnull _, + NSRange lineGlyphRange, + BOOL *_Nonnull stop) { + if (line == textContainer.maximumNumberOfLines - 1) { + NSRange truncatedRange = [layoutManager + truncatedGlyphRangeInLineFragmentForGlyphAtIndex:lineGlyphRange.location]; + if (truncatedRange.location != NSNotFound) { + NSRange characterRange = + [layoutManager characterRangeForGlyphRange:truncatedRange + actualGlyphRange:nil]; + if (characterRange.location > 0 && characterRange.length > 0) { + // Remove color attributes for truncated range + for (NSAttributedStringKey key in + @[ NSForegroundColorAttributeName, NSBackgroundColorAttributeName ]) { + [textStorage removeAttribute:key range:characterRange]; + id attribute = [textStorage attribute:key + atIndex:characterRange.location - 1 + effectiveRange:nil]; + if (attribute) { + [textStorage addAttribute:key value:attribute range:characterRange]; + } + } + } + } + } + line++; + }]; + } +} + - (LinesMeasurements)getLinesForAttributedString:(facebook::react::AttributedString)attributedString paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes size:(CGSize)size