mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-07 22:40:55 +08:00
Summary: Previously, we stored a pointer to ShadowNode inside NSAttributedString's attributes to make possible retrieving an EventEmitter associated with some text fragment. That worked fine besides only one caveat: the internal implementation of NSAttributedString is quite strange and that causes a memory leak. Because of some reason, NSAttributedString does not release stored attributes after own destruction (maybe OS uses some kind of caching). So, now, instead of storing a strong pointer to ShadowNode inside NSAttributedString, we store a weak pointer to EventEmitter. Storing a weak pointer is okay because a desired lifetime of EventEmitter is guaranteed by LocalData stored inside a View. Storing a weak EventEmitter instead of weak ShadowNode will also help us with migration to ShadowView (we cannot store ShadowView weakly because it's a stack allocated object). Reviewed By: sahrens Differential Revision: D13196886 fbshipit-source-id: f8714e4b3709765629d6456edf0c635bf5f7c53b
144 lines
5.8 KiB
Plaintext
144 lines
5.8 KiB
Plaintext
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
#import "RCTTextLayoutManager.h"
|
|
|
|
#import "NSTextStorage+FontScaling.h"
|
|
#import "RCTAttributedTextUtils.h"
|
|
|
|
using namespace facebook::react;
|
|
|
|
@implementation RCTTextLayoutManager
|
|
|
|
static NSLineBreakMode RCTNSLineBreakModeFromWritingDirection(
|
|
EllipsizeMode ellipsizeMode) {
|
|
switch (ellipsizeMode) {
|
|
case EllipsizeMode::Clip:
|
|
return NSLineBreakByClipping;
|
|
case EllipsizeMode::Head:
|
|
return NSLineBreakByTruncatingHead;
|
|
case EllipsizeMode::Tail:
|
|
return NSLineBreakByTruncatingTail;
|
|
case EllipsizeMode::Middle:
|
|
return NSLineBreakByTruncatingMiddle;
|
|
}
|
|
}
|
|
|
|
- (facebook::react::Size)
|
|
measureWithAttributedString:(AttributedString)attributedString
|
|
paragraphAttributes:(ParagraphAttributes)paragraphAttributes
|
|
layoutConstraints:(LayoutConstraints)layoutConstraints {
|
|
CGSize maximumSize = CGSize{layoutConstraints.maximumSize.width,
|
|
layoutConstraints.maximumSize.height};
|
|
NSTextStorage *textStorage =
|
|
[self _textStorageAndLayoutManagerWithAttributesString:
|
|
RCTNSAttributedStringFromAttributedString(attributedString)
|
|
paragraphAttributes:paragraphAttributes
|
|
size:maximumSize];
|
|
|
|
NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject;
|
|
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
|
|
[layoutManager ensureLayoutForTextContainer:textContainer];
|
|
|
|
CGSize size = [layoutManager usedRectForTextContainer:textContainer].size;
|
|
|
|
size = (CGSize){MIN(size.width, maximumSize.width),
|
|
MIN(size.height, maximumSize.height)};
|
|
|
|
return facebook::react::Size{size.width, size.height};
|
|
}
|
|
|
|
- (void)drawAttributedString:(AttributedString)attributedString
|
|
paragraphAttributes:(ParagraphAttributes)paragraphAttributes
|
|
frame:(CGRect)frame {
|
|
NSTextStorage *textStorage =
|
|
[self _textStorageAndLayoutManagerWithAttributesString:
|
|
RCTNSAttributedStringFromAttributedString(attributedString)
|
|
paragraphAttributes:paragraphAttributes
|
|
size:frame.size];
|
|
NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject;
|
|
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
|
|
|
|
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
|
|
[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:frame.origin];
|
|
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:frame.origin];
|
|
}
|
|
|
|
- (NSTextStorage *)
|
|
_textStorageAndLayoutManagerWithAttributesString:
|
|
(NSAttributedString *)attributedString
|
|
paragraphAttributes:
|
|
(ParagraphAttributes)paragraphAttributes
|
|
size:(CGSize)size {
|
|
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:size];
|
|
|
|
textContainer.lineFragmentPadding = 0.0; // Note, the default value is 5.
|
|
textContainer.lineBreakMode = paragraphAttributes.maximumNumberOfLines > 0
|
|
? RCTNSLineBreakModeFromWritingDirection(
|
|
paragraphAttributes.ellipsizeMode)
|
|
: NSLineBreakByClipping;
|
|
textContainer.maximumNumberOfLines = paragraphAttributes.maximumNumberOfLines;
|
|
|
|
NSLayoutManager *layoutManager = [NSLayoutManager new];
|
|
[layoutManager addTextContainer:textContainer];
|
|
|
|
NSTextStorage *textStorage =
|
|
[[NSTextStorage alloc] initWithAttributedString:attributedString];
|
|
|
|
[textStorage addLayoutManager:layoutManager];
|
|
|
|
if (paragraphAttributes.adjustsFontSizeToFit) {
|
|
CGFloat minimumFontSize = !isnan(paragraphAttributes.minimumFontSize)
|
|
? paragraphAttributes.minimumFontSize
|
|
: 4.0;
|
|
CGFloat maximumFontSize = !isnan(paragraphAttributes.maximumFontSize)
|
|
? paragraphAttributes.maximumFontSize
|
|
: 96.0;
|
|
[textStorage scaleFontSizeToFitSize:size
|
|
minimumFontSize:minimumFontSize
|
|
maximumFontSize:maximumFontSize];
|
|
}
|
|
|
|
return textStorage;
|
|
}
|
|
|
|
- (SharedEventEmitter)
|
|
getEventEmitterWithAttributeString:(AttributedString)attributedString
|
|
paragraphAttributes:(ParagraphAttributes)paragraphAttributes
|
|
frame:(CGRect)frame
|
|
atPoint:(CGPoint)point {
|
|
NSTextStorage *textStorage =
|
|
[self _textStorageAndLayoutManagerWithAttributesString:
|
|
RCTNSAttributedStringFromAttributedString(attributedString)
|
|
paragraphAttributes:paragraphAttributes
|
|
size:frame.size];
|
|
NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject;
|
|
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
|
|
|
|
CGFloat fraction;
|
|
NSUInteger characterIndex =
|
|
[layoutManager characterIndexForPoint:point
|
|
inTextContainer:textContainer
|
|
fractionOfDistanceBetweenInsertionPoints:&fraction];
|
|
|
|
// If the point is not before (fraction == 0.0) the first character and not
|
|
// after (fraction == 1.0) the last character, then the attribute is valid.
|
|
if (textStorage.length > 0 && (fraction > 0 || characterIndex > 0) &&
|
|
(fraction < 1 || characterIndex < textStorage.length - 1)) {
|
|
RCTWeakEventEmitterWrapper *eventEmitterWrapper =
|
|
(RCTWeakEventEmitterWrapper *)[textStorage
|
|
attribute:RCTAttributedStringEventEmitterKey
|
|
atIndex:characterIndex
|
|
effectiveRange:NULL];
|
|
return eventEmitterWrapper.eventEmitter;
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
@end
|