Files
react-native/ReactCommon/fabric/textlayoutmanager/platform/ios/RCTTextLayoutManager.mm
Valentin Shergin 17cb6a8aeb Fabric: Caching for NSAttributedStrings in RCTTextLayoutManager
Summary:
Creation NSAttributedString from attributedString is not so cheap process, so with this simple cache we hopefully can save a couple milliseconds.
The same string is used at least two times: first time for measuring and second for drawing.

Reviewed By: mdvacca

Differential Revision: D14296514

fbshipit-source-id: 6313aa2c6e9f63d873868131750f61c02d64d2de
2019-03-03 12:07:12 -08:00

155 lines
6.3 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"
#import <react/utils/SimpleThreadSafeCache.h>
using namespace facebook::react;
@implementation RCTTextLayoutManager {
SimpleThreadSafeCache<AttributedString, std::shared_ptr<const void>, 256> _cache;
}
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:[self _nsAttributedStringFromAttributedString: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:[self _nsAttributedStringFromAttributedString: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:[self _nsAttributedStringFromAttributedString: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;
}
- (NSAttributedString *)_nsAttributedStringFromAttributedString:(AttributedString)attributedString
{
auto sharedNSAttributedString = _cache.get(attributedString, [](const AttributedString attributedString) {
return std::shared_ptr<void>(
(__bridge_retained void *)RCTNSAttributedStringFromAttributedString(attributedString), CFRelease);
});
return (__bridge NSAttributedString *)sharedNSAttributedString.get();
}
@end