From 96a31c24c01e38c33448e5b899081b1b0e3a1fc8 Mon Sep 17 00:00:00 2001 From: zhongwuzw Date: Tue, 12 Sep 2023 18:25:40 +0800 Subject: [PATCH 01/10] =?UTF-8?q?=E3=80=90iOS=E3=80=91Fixes=20ellipsis=20c?= =?UTF-8?q?arries=20background=20from=20trimmed=20text?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Libraries/Text/Text/RCTTextView.mm | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/react-native/Libraries/Text/Text/RCTTextView.mm b/packages/react-native/Libraries/Text/Text/RCTTextView.mm index 3f64e313ab58..bb226651e8a7 100644 --- a/packages/react-native/Libraries/Text/Text/RCTTextView.mm +++ b/packages/react-native/Libraries/Text/Text/RCTTextView.mm @@ -112,11 +112,35 @@ - (void)drawRect:(CGRect)rect #endif NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; + NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; + NSRange truncatedRange = [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:glyphRange.location]; + + NSMutableDictionary *backgroundColorAttributes = [NSMutableDictionary dictionary]; + + [_textStorage enumerateAttribute:NSBackgroundColorAttributeName + inRange:characterRange + options:0 + usingBlock:^(id _Nullable value, NSRange range, BOOL *_Nonnull stop) { + // Remove background color if glyphs is truncated + if (truncatedRange.location != NSNotFound && range.location >= truncatedRange.location) { + [_textStorage removeAttribute:NSBackgroundColorAttributeName range:range]; + NSValue *rangeValue = [NSValue valueWithRange:range]; + backgroundColorAttributes[rangeValue] = value; + } + }]; + [layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:_contentFrame.origin]; [layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:_contentFrame.origin]; + // Re-Added removed background color if glyphs is truncated after we draw background glyphs + [backgroundColorAttributes + enumerateKeysAndObjectsUsingBlock:^(NSValue *_Nonnull key, UIColor *_Nonnull obj, BOOL *_Nonnull stop) { + NSRange range = key.rangeValue; + [_textStorage addAttribute:NSBackgroundColorAttributeName value:obj range:range]; + }]; + __block UIBezierPath *highlightPath = nil; - NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; + [_textStorage enumerateAttribute:RCTTextAttributesIsHighlightedAttributeName inRange:characterRange From 4817a95b0c86aeddae9128d48128af41bbd9b51e Mon Sep 17 00:00:00 2001 From: zhongwuzw Date: Tue, 17 Oct 2023 10:03:51 +0800 Subject: [PATCH 02/10] =?UTF-8?q?Revert=20"=E3=80=90iOS=E3=80=91Fixes=20el?= =?UTF-8?q?lipsis=20carries=20background=20from=20trimmed=20text"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 96a31c24c01e38c33448e5b899081b1b0e3a1fc8. --- .../Libraries/Text/Text/RCTTextView.mm | 26 +------------------ 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/packages/react-native/Libraries/Text/Text/RCTTextView.mm b/packages/react-native/Libraries/Text/Text/RCTTextView.mm index bb226651e8a7..3f64e313ab58 100644 --- a/packages/react-native/Libraries/Text/Text/RCTTextView.mm +++ b/packages/react-native/Libraries/Text/Text/RCTTextView.mm @@ -112,35 +112,11 @@ - (void)drawRect:(CGRect)rect #endif NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; - NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; - NSRange truncatedRange = [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:glyphRange.location]; - - NSMutableDictionary *backgroundColorAttributes = [NSMutableDictionary dictionary]; - - [_textStorage enumerateAttribute:NSBackgroundColorAttributeName - inRange:characterRange - options:0 - usingBlock:^(id _Nullable value, NSRange range, BOOL *_Nonnull stop) { - // Remove background color if glyphs is truncated - if (truncatedRange.location != NSNotFound && range.location >= truncatedRange.location) { - [_textStorage removeAttribute:NSBackgroundColorAttributeName range:range]; - NSValue *rangeValue = [NSValue valueWithRange:range]; - backgroundColorAttributes[rangeValue] = value; - } - }]; - [layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:_contentFrame.origin]; [layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:_contentFrame.origin]; - // Re-Added removed background color if glyphs is truncated after we draw background glyphs - [backgroundColorAttributes - enumerateKeysAndObjectsUsingBlock:^(NSValue *_Nonnull key, UIColor *_Nonnull obj, BOOL *_Nonnull stop) { - NSRange range = key.rangeValue; - [_textStorage addAttribute:NSBackgroundColorAttributeName value:obj range:range]; - }]; - __block UIBezierPath *highlightPath = nil; - + NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; [_textStorage enumerateAttribute:RCTTextAttributesIsHighlightedAttributeName inRange:characterRange From aadbf6f674b6561d1f291c29bda1a485aeb6d617 Mon Sep 17 00:00:00 2001 From: zhongwuzw Date: Tue, 17 Oct 2023 10:15:52 +0800 Subject: [PATCH 03/10] Support old and Fabric arch --- .../Libraries/Text/Text/RCTTextShadowView.mm | 17 +++++++++++++++++ .../components/text/ParagraphLayoutManager.cpp | 4 +++- .../textlayoutmanager/RCTTextLayoutManager.mm | 16 ++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm b/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm index 5afa863ad060..6f507e8533d2 100644 --- a/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm +++ b/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm @@ -237,6 +237,23 @@ - (NSTextStorage *)textStorageAndLayoutManagerThatFitsSize:(CGSize)size exclusiv maximumFontSize:self.textAttributes.effectiveFont.pointSize]; } + [layoutManager ensureLayoutForTextContainer:textContainer]; + NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; + NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; + + [textStorage enumerateAttribute:NSBackgroundColorAttributeName + inRange:characterRange + options:0 + usingBlock:^(id _Nullable value, NSRange range, BOOL *_Nonnull stop) { + NSRange truncatedRange = + [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:range.location]; + + // Remove background color if glyphs is truncated + if (truncatedRange.location != NSNotFound && range.location >= truncatedRange.location) { + [textStorage removeAttribute:NSBackgroundColorAttributeName range:range]; + } + }]; + if (!exclusiveOwnership) { [_cachedTextStorages setObject:textStorage forKey:key]; } diff --git a/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphLayoutManager.cpp b/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphLayoutManager.cpp index ea615b500ab4..5a8266ac7950 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphLayoutManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphLayoutManager.cpp @@ -32,12 +32,14 @@ TextMeasurement ParagraphLayoutManager::measure( return cachedTextMeasurement_; } else { + hostTextStorage_ = textLayoutManager_->getHostTextStorage( + attributedString, paragraphAttributes, layoutConstraints); return textLayoutManager_->measure( AttributedStringBox(attributedString), paragraphAttributes, layoutContext, layoutConstraints, - nullptr); + hostTextStorage_); } } 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 368c33410558..6ab1f6754109 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 @@ -320,6 +320,22 @@ - (TextMeasurement)_measureTextStorage:(NSTextStorage *)textStorage attachments.push_back(TextMeasurement::Attachment{rect, false}); }]; + NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; + NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; + + [textStorage enumerateAttribute:NSBackgroundColorAttributeName + inRange:characterRange + options:0 + usingBlock:^(id _Nullable value, NSRange range, BOOL *_Nonnull stop) { + NSRange truncatedRange = + [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:range.location]; + + // Remove background color if glyphs is truncated + if (truncatedRange.location != NSNotFound && range.location >= truncatedRange.location) { + [textStorage removeAttribute:NSBackgroundColorAttributeName range:range]; + } + }]; + return TextMeasurement{{size.width, size.height}, attachments}; } From 92f860a0eea20b19a6137ec57de984c2ad715407 Mon Sep 17 00:00:00 2001 From: zhongwuzw Date: Fri, 10 May 2024 17:12:01 +0800 Subject: [PATCH 04/10] Fix for new arch --- .../textlayoutmanager/RCTTextLayoutManager.mm | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 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 cff48832f993..4fd276a9f359 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 @@ -82,6 +82,22 @@ - (void)drawAttributedString:(AttributedString)attributedString #endif NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; + + NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; + + [textStorage enumerateAttribute:NSBackgroundColorAttributeName + inRange:characterRange + options:0 + usingBlock:^(id _Nullable value, NSRange range, BOOL *_Nonnull stop) { + NSRange truncatedRange = + [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:range.location]; + + // Remove background color if glyphs is truncated + if (truncatedRange.location != NSNotFound && range.location >= truncatedRange.location) { + [textStorage removeAttribute:NSBackgroundColorAttributeName range:range]; + } + }]; + [layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:frame.origin]; [layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:frame.origin]; @@ -316,22 +332,6 @@ - (TextMeasurement)_measureTextStorage:(NSTextStorage *)textStorage attachments.push_back(TextMeasurement::Attachment{rect, false}); }]; - NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; - NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; - - [textStorage enumerateAttribute:NSBackgroundColorAttributeName - inRange:characterRange - options:0 - usingBlock:^(id _Nullable value, NSRange range, BOOL *_Nonnull stop) { - NSRange truncatedRange = - [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:range.location]; - - // Remove background color if glyphs is truncated - if (truncatedRange.location != NSNotFound && range.location >= truncatedRange.location) { - [textStorage removeAttribute:NSBackgroundColorAttributeName range:range]; - } - }]; - return TextMeasurement{{size.width, size.height}, attachments}; } From c3afd09dce15a9b5d8b50cbc0b6a6b469ce2497d Mon Sep 17 00:00:00 2001 From: zhongwuzw Date: Tue, 9 Jul 2024 18:39:25 +0800 Subject: [PATCH 05/10] Add maximunNumberOfLines gate --- .../Libraries/Text/Text/RCTTextShadowView.mm | 34 ++++++++++--------- .../textlayoutmanager/RCTTextLayoutManager.mm | 28 ++++++++------- 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm b/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm index 0a2896aa5c66..4fa4ceec74cb 100644 --- a/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm +++ b/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm @@ -237,22 +237,24 @@ - (NSTextStorage *)textStorageAndLayoutManagerThatFitsSize:(CGSize)size exclusiv maximumFontSize:self.textAttributes.effectiveFont.pointSize]; } - [layoutManager ensureLayoutForTextContainer:textContainer]; - NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; - NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; - - [textStorage enumerateAttribute:NSBackgroundColorAttributeName - inRange:characterRange - options:0 - usingBlock:^(id _Nullable value, NSRange range, BOOL *_Nonnull stop) { - NSRange truncatedRange = - [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:range.location]; - - // Remove background color if glyphs is truncated - if (truncatedRange.location != NSNotFound && range.location >= truncatedRange.location) { - [textStorage removeAttribute:NSBackgroundColorAttributeName range:range]; - } - }]; + if (_maximumNumberOfLines > 0) { + [layoutManager ensureLayoutForTextContainer:textContainer]; + NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; + NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; + + [textStorage enumerateAttribute:NSBackgroundColorAttributeName + inRange:characterRange + options:0 + usingBlock:^(id _Nullable value, NSRange range, BOOL *_Nonnull stop) { + NSRange truncatedRange = + [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:range.location]; + + // Remove background color if glyphs is truncated + if (truncatedRange.location != NSNotFound && range.location >= truncatedRange.location) { + [textStorage removeAttribute:NSBackgroundColorAttributeName range:range]; + } + }]; + } if (!exclusiveOwnership) { [_cachedTextStorages setObject:textStorage forKey:key]; 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 1160eb78c35c..6a4943fd555b 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,20 +83,22 @@ - (void)drawAttributedString:(AttributedString)attributedString NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; - NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; - - [textStorage enumerateAttribute:NSBackgroundColorAttributeName - inRange:characterRange - options:0 - usingBlock:^(id _Nullable value, NSRange range, BOOL *_Nonnull stop) { - NSRange truncatedRange = - [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:range.location]; + if (paragraphAttributes.maximumNumberOfLines > 0) { + NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; - // Remove background color if glyphs is truncated - if (truncatedRange.location != NSNotFound && range.location >= truncatedRange.location) { - [textStorage removeAttribute:NSBackgroundColorAttributeName range:range]; - } - }]; + [textStorage enumerateAttribute:NSBackgroundColorAttributeName + inRange:characterRange + options:0 + usingBlock:^(id _Nullable value, NSRange range, BOOL *_Nonnull stop) { + NSRange truncatedRange = + [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:range.location]; + + // Remove background color if glyphs is truncated + if (truncatedRange.location != NSNotFound && range.location >= truncatedRange.location) { + [textStorage removeAttribute:NSBackgroundColorAttributeName range:range]; + } + }]; + } [layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:frame.origin]; [layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:frame.origin]; From 20ccff63c5e2576229a698aeaf1030cbb8a58ad3 Mon Sep 17 00:00:00 2001 From: zhongwuzw Date: Wed, 10 Jul 2024 15:55:35 +0800 Subject: [PATCH 06/10] Keep the same style of the character that precedes it --- .../Libraries/Text/Text/RCTTextShadowView.mm | 72 ++++++++++++++----- .../textlayoutmanager/RCTTextLayoutManager.mm | 70 +++++++++++++----- 2 files changed, 108 insertions(+), 34 deletions(-) diff --git a/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm b/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm index 4fa4ceec74cb..b2eab24d8091 100644 --- a/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm +++ b/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm @@ -237,24 +237,7 @@ - (NSTextStorage *)textStorageAndLayoutManagerThatFitsSize:(CGSize)size exclusiv maximumFontSize:self.textAttributes.effectiveFont.pointSize]; } - if (_maximumNumberOfLines > 0) { - [layoutManager ensureLayoutForTextContainer:textContainer]; - NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; - NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; - - [textStorage enumerateAttribute:NSBackgroundColorAttributeName - inRange:characterRange - options:0 - usingBlock:^(id _Nullable value, NSRange range, BOOL *_Nonnull stop) { - NSRange truncatedRange = - [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:range.location]; - - // Remove background color if glyphs is truncated - if (truncatedRange.location != NSNotFound && range.location >= truncatedRange.location) { - [textStorage removeAttribute:NSBackgroundColorAttributeName range:range]; - } - }]; - } + [self processTruncatedAttributedText:textStorage textContainer:textContainer layoutManager:layoutManager]; if (!exclusiveOwnership) { [_cachedTextStorages setObject:textStorage forKey:key]; @@ -263,6 +246,59 @@ - (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 textContainer, + NSRange glyphRange, + BOOL *_Nonnull stop) { + if (line == textContainer.maximumNumberOfLines - 1) { + NSRange truncatedRange = [layoutManager + truncatedGlyphRangeInLineFragmentForGlyphAtIndex:glyphRange.location]; + + if (truncatedRange.location != NSNotFound) { + // Remove all attributes for truncated range + [textStorage + enumerateAttributesInRange:truncatedRange + options:0 + usingBlock:^( + NSDictionary *_Nonnull attrs, + NSRange range, + BOOL *_Nonnull stop) { + for (NSAttributedStringKey key in attrs) { + [textStorage removeAttribute:key range:range]; + } + }]; + if (truncatedRange.location - 1 >= 0) { + // Keep the same style of the character that precedes it + [textStorage + enumerateAttributesInRange:NSMakeRange(truncatedRange.location - 1, 1) + options:0 + usingBlock:^( + NSDictionary + *_Nonnull attrs, + NSRange range, + BOOL *_Nonnull stop) { + [textStorage addAttributes:attrs range:truncatedRange]; + }]; + } + } + } + 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 6a4943fd555b..dab6d9e3f0ec 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,22 +83,7 @@ - (void)drawAttributedString:(AttributedString)attributedString NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; - if (paragraphAttributes.maximumNumberOfLines > 0) { - NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; - - [textStorage enumerateAttribute:NSBackgroundColorAttributeName - inRange:characterRange - options:0 - usingBlock:^(id _Nullable value, NSRange range, BOOL *_Nonnull stop) { - NSRange truncatedRange = - [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:range.location]; - - // Remove background color if glyphs is truncated - if (truncatedRange.location != NSNotFound && range.location >= truncatedRange.location) { - [textStorage removeAttribute:NSBackgroundColorAttributeName range:range]; - } - }]; - } + [self processTruncatedAttributedText:textStorage textContainer:textContainer layoutManager:layoutManager]; [layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:frame.origin]; [layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:frame.origin]; @@ -140,6 +125,59 @@ - (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 textContainer, + NSRange glyphRange, + BOOL *_Nonnull stop) { + if (line == textContainer.maximumNumberOfLines - 1) { + NSRange truncatedRange = [layoutManager + truncatedGlyphRangeInLineFragmentForGlyphAtIndex:glyphRange.location]; + + if (truncatedRange.location != NSNotFound) { + // Remove all attributes for truncated range + [textStorage + enumerateAttributesInRange:truncatedRange + options:0 + usingBlock:^( + NSDictionary *_Nonnull attrs, + NSRange range, + BOOL *_Nonnull stop) { + for (NSAttributedStringKey key in attrs) { + [textStorage removeAttribute:key range:range]; + } + }]; + if (truncatedRange.location - 1 >= 0) { + // Keep the same style of the character that precedes it + [textStorage + enumerateAttributesInRange:NSMakeRange(truncatedRange.location - 1, 1) + options:0 + usingBlock:^( + NSDictionary + *_Nonnull attrs, + NSRange range, + BOOL *_Nonnull stop) { + [textStorage addAttributes:attrs range:truncatedRange]; + }]; + } + } + } + line++; + }]; + } +} + - (LinesMeasurements)getLinesForAttributedString:(facebook::react::AttributedString)attributedString paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes size:(CGSize)size From 80cd8e0ce6cab9733bf9315b85c186b5ffaffddf Mon Sep 17 00:00:00 2001 From: zhongwuzw Date: Wed, 10 Jul 2024 16:18:21 +0800 Subject: [PATCH 07/10] Simply remove attributes operation --- .../Libraries/Text/Text/RCTTextShadowView.mm | 12 +----------- .../textlayoutmanager/RCTTextLayoutManager.mm | 12 +----------- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm b/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm index b2eab24d8091..f8ea75802708 100644 --- a/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm +++ b/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm @@ -268,17 +268,7 @@ - (void)processTruncatedAttributedText:(NSTextStorage *)textStorage if (truncatedRange.location != NSNotFound) { // Remove all attributes for truncated range - [textStorage - enumerateAttributesInRange:truncatedRange - options:0 - usingBlock:^( - NSDictionary *_Nonnull attrs, - NSRange range, - BOOL *_Nonnull stop) { - for (NSAttributedStringKey key in attrs) { - [textStorage removeAttribute:key range:range]; - } - }]; + [textStorage setAttributes:nil range:truncatedRange]; if (truncatedRange.location - 1 >= 0) { // Keep the same style of the character that precedes it [textStorage 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 dab6d9e3f0ec..00c17c8d7dd0 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 @@ -147,17 +147,7 @@ - (void)processTruncatedAttributedText:(NSTextStorage *)textStorage if (truncatedRange.location != NSNotFound) { // Remove all attributes for truncated range - [textStorage - enumerateAttributesInRange:truncatedRange - options:0 - usingBlock:^( - NSDictionary *_Nonnull attrs, - NSRange range, - BOOL *_Nonnull stop) { - for (NSAttributedStringKey key in attrs) { - [textStorage removeAttribute:key range:range]; - } - }]; + [textStorage setAttributes:nil range:truncatedRange]; if (truncatedRange.location - 1 >= 0) { // Keep the same style of the character that precedes it [textStorage From 19e3ef3ae3ae48c3bc96fdb937e5f84a8ae717d9 Mon Sep 17 00:00:00 2001 From: zhongwuzw Date: Wed, 10 Jul 2024 17:02:06 +0800 Subject: [PATCH 08/10] Only remove color attributes --- .../Libraries/Text/Text/RCTTextShadowView.mm | 26 +++++++++---------- .../textlayoutmanager/RCTTextLayoutManager.mm | 26 +++++++++---------- 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm b/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm index f8ea75802708..1a3871e5dd76 100644 --- a/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm +++ b/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm @@ -267,20 +267,18 @@ - (void)processTruncatedAttributedText:(NSTextStorage *)textStorage truncatedGlyphRangeInLineFragmentForGlyphAtIndex:glyphRange.location]; if (truncatedRange.location != NSNotFound) { - // Remove all attributes for truncated range - [textStorage setAttributes:nil range:truncatedRange]; - if (truncatedRange.location - 1 >= 0) { - // Keep the same style of the character that precedes it - [textStorage - enumerateAttributesInRange:NSMakeRange(truncatedRange.location - 1, 1) - options:0 - usingBlock:^( - NSDictionary - *_Nonnull attrs, - NSRange range, - BOOL *_Nonnull stop) { - [textStorage addAttributes:attrs range:truncatedRange]; - }]; + // Remove color attributes for truncated range + for (NSAttributedStringKey key in + @[ NSForegroundColorAttributeName, NSBackgroundColorAttributeName ]) { + [textStorage removeAttribute:key range:truncatedRange]; + if (truncatedRange.location >= 1) { + id attribute = [textStorage attribute:key + atIndex:truncatedRange.location - 1 + effectiveRange:nil]; + if (attribute) { + [textStorage addAttribute:key value:attribute range:truncatedRange]; + } + } } } } 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 00c17c8d7dd0..eff8600a4f7d 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 @@ -146,20 +146,18 @@ - (void)processTruncatedAttributedText:(NSTextStorage *)textStorage truncatedGlyphRangeInLineFragmentForGlyphAtIndex:glyphRange.location]; if (truncatedRange.location != NSNotFound) { - // Remove all attributes for truncated range - [textStorage setAttributes:nil range:truncatedRange]; - if (truncatedRange.location - 1 >= 0) { - // Keep the same style of the character that precedes it - [textStorage - enumerateAttributesInRange:NSMakeRange(truncatedRange.location - 1, 1) - options:0 - usingBlock:^( - NSDictionary - *_Nonnull attrs, - NSRange range, - BOOL *_Nonnull stop) { - [textStorage addAttributes:attrs range:truncatedRange]; - }]; + // Remove color attributes for truncated range + for (NSAttributedStringKey key in + @[ NSForegroundColorAttributeName, NSBackgroundColorAttributeName ]) { + [textStorage removeAttribute:key range:truncatedRange]; + if (truncatedRange.location >= 1) { + id attribute = [textStorage attribute:key + atIndex:truncatedRange.location - 1 + effectiveRange:nil]; + if (attribute) { + [textStorage addAttribute:key value:attribute range:truncatedRange]; + } + } } } } From 41a11320d46d017e2722f41eca03d496a80d201c Mon Sep 17 00:00:00 2001 From: zhongwuzw Date: Wed, 10 Jul 2024 17:04:57 +0800 Subject: [PATCH 09/10] optimize code --- .../Libraries/Text/Text/RCTTextShadowView.mm | 14 ++++++-------- .../textlayoutmanager/RCTTextLayoutManager.mm | 14 ++++++-------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm b/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm index 1a3871e5dd76..f169c644f0b9 100644 --- a/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm +++ b/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm @@ -266,18 +266,16 @@ - (void)processTruncatedAttributedText:(NSTextStorage *)textStorage NSRange truncatedRange = [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:glyphRange.location]; - if (truncatedRange.location != NSNotFound) { + if (truncatedRange.location != NSNotFound && truncatedRange.location >= 1) { // Remove color attributes for truncated range for (NSAttributedStringKey key in @[ NSForegroundColorAttributeName, NSBackgroundColorAttributeName ]) { [textStorage removeAttribute:key range:truncatedRange]; - if (truncatedRange.location >= 1) { - id attribute = [textStorage attribute:key - atIndex:truncatedRange.location - 1 - effectiveRange:nil]; - if (attribute) { - [textStorage addAttribute:key value:attribute range:truncatedRange]; - } + id attribute = [textStorage attribute:key + atIndex:truncatedRange.location - 1 + effectiveRange:nil]; + if (attribute) { + [textStorage addAttribute:key value:attribute range:truncatedRange]; } } } 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 eff8600a4f7d..6456ee391284 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 @@ -145,18 +145,16 @@ - (void)processTruncatedAttributedText:(NSTextStorage *)textStorage NSRange truncatedRange = [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:glyphRange.location]; - if (truncatedRange.location != NSNotFound) { + if (truncatedRange.location != NSNotFound && truncatedRange.location >= 1) { // Remove color attributes for truncated range for (NSAttributedStringKey key in @[ NSForegroundColorAttributeName, NSBackgroundColorAttributeName ]) { [textStorage removeAttribute:key range:truncatedRange]; - if (truncatedRange.location >= 1) { - id attribute = [textStorage attribute:key - atIndex:truncatedRange.location - 1 - effectiveRange:nil]; - if (attribute) { - [textStorage addAttribute:key value:attribute range:truncatedRange]; - } + id attribute = [textStorage attribute:key + atIndex:truncatedRange.location - 1 + effectiveRange:nil]; + if (attribute) { + [textStorage addAttribute:key value:attribute range:truncatedRange]; } } } From 3c536f7e7bfffda406c7ef9ed31499896c1506d6 Mon Sep 17 00:00:00 2001 From: zhongwuzw Date: Wed, 10 Jul 2024 21:34:43 +0800 Subject: [PATCH 10/10] fix shallow variable --- .../react-native/Libraries/Text/Text/RCTTextShadowView.mm | 6 +++--- .../renderer/textlayoutmanager/RCTTextLayoutManager.mm | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm b/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm index f169c644f0b9..55cb8d1e822a 100644 --- a/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm +++ b/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm @@ -259,12 +259,12 @@ - (void)processTruncatedAttributedText:(NSTextStorage *)textStorage usingBlock:^( CGRect rect, CGRect usedRect, - NSTextContainer *_Nonnull textContainer, - NSRange glyphRange, + NSTextContainer *_Nonnull _, + NSRange lineGlyphRange, BOOL *_Nonnull stop) { if (line == textContainer.maximumNumberOfLines - 1) { NSRange truncatedRange = [layoutManager - truncatedGlyphRangeInLineFragmentForGlyphAtIndex:glyphRange.location]; + truncatedGlyphRangeInLineFragmentForGlyphAtIndex:lineGlyphRange.location]; if (truncatedRange.location != NSNotFound && truncatedRange.location >= 1) { // Remove color attributes for truncated range 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 6456ee391284..9497d11d8130 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 @@ -138,12 +138,12 @@ - (void)processTruncatedAttributedText:(NSTextStorage *)textStorage usingBlock:^( CGRect rect, CGRect usedRect, - NSTextContainer *_Nonnull textContainer, - NSRange glyphRange, + NSTextContainer *_Nonnull _, + NSRange lineGlyphRange, BOOL *_Nonnull stop) { if (line == textContainer.maximumNumberOfLines - 1) { NSRange truncatedRange = [layoutManager - truncatedGlyphRangeInLineFragmentForGlyphAtIndex:glyphRange.location]; + truncatedGlyphRangeInLineFragmentForGlyphAtIndex:lineGlyphRange.location]; if (truncatedRange.location != NSNotFound && truncatedRange.location >= 1) { // Remove color attributes for truncated range