[ASTextKit] Remove internal side effects related to constrainedSize.

This commit is contained in:
Michael Schneider
2016-11-20 21:13:37 -08:00
committed by Scott Goodson
parent c3cbd3b583
commit beb98b448e
8 changed files with 119 additions and 240 deletions

View File

@@ -50,6 +50,77 @@ struct ASTextNodeDrawParameter {
UIColor *backgroundColor; UIColor *backgroundColor;
}; };
#pragma mark - ASTextKitRenderer
// Not used at the moment but handy to have
/*ASDISPLAYNODE_INLINE NSUInteger ASHashFromCGRect(CGRect rect)
{
return ((*(NSUInteger *)&rect.origin.x << 10 ^ *(NSUInteger *)&rect.origin.y) + (*(NSUInteger *)&rect.size.width << 10 ^ *(NSUInteger *)&rect.size.height));
}*/
ASDISPLAYNODE_INLINE NSUInteger ASHashFromCGSize(CGSize size)
{
return ((*(NSUInteger *)&size.width << 10 ^ *(NSUInteger *)&size.height));
}
@interface ASTextNodeRendererKey : NSObject
@property (assign, nonatomic) ASTextKitAttributes attributes;
@property (assign, nonatomic) CGSize constrainedSize;
@end
@implementation ASTextNodeRendererKey
- (NSUInteger)hash
{
return _attributes.hash() ^ ASHashFromCGSize(_constrainedSize);
}
- (BOOL)isEqual:(ASTextNodeRendererKey *)object
{
if (self == object) {
return YES;
}
return _attributes.hash() == object.attributes.hash()
&& CGSizeEqualToSize(_constrainedSize, object.constrainedSize);
}
@end
static NSCache *sharedRendererCache()
{
static dispatch_once_t onceToken;
static NSCache *__rendererCache = nil;
dispatch_once(&onceToken, ^{
__rendererCache = [[NSCache alloc] init];
__rendererCache.countLimit = 500; // 500 renders cache
});
return __rendererCache;
}
/**
The concept here is that neither the node nor layout should ever have a strong reference to the renderer object.
This is to reduce memory load when loading thousands and thousands of text nodes into memory at once. Instead
we maintain a LRU renderer cache that is queried via a unique key based on text kit attributes and constrained size.
*/
static ASTextKitRenderer *rendererForAttributes(ASTextKitAttributes attributes, CGSize constrainedSize)
{
NSCache *cache = sharedRendererCache();
ASTextNodeRendererKey *key = [[ASTextNodeRendererKey alloc] init];
key.attributes = attributes;
key.constrainedSize = constrainedSize;
ASTextKitRenderer *renderer = [cache objectForKey:key];
if (renderer == nil) {
renderer = [[ASTextKitRenderer alloc] initWithTextKitAttributes:attributes constrainedSize:constrainedSize];
[cache setObject:renderer forKey:key];
}
return renderer;
}
@interface ASTextNode () <UIGestureRecognizerDelegate> @interface ASTextNode () <UIGestureRecognizerDelegate>
@end @end
@@ -73,10 +144,6 @@ struct ASTextNodeDrawParameter {
NSRange _highlightRange; NSRange _highlightRange;
ASHighlightOverlayLayer *_activeHighlightLayer; ASHighlightOverlayLayer *_activeHighlightLayer;
CGSize _constrainedSize;
ASTextKitRenderer *_renderer;
ASTextNodeDrawParameter _drawParameter; ASTextNodeDrawParameter _drawParameter;
UILongPressGestureRecognizer *_longPressGestureRecognizer; UILongPressGestureRecognizer *_longPressGestureRecognizer;
@@ -123,8 +190,6 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
self.isAccessibilityElement = YES; self.isAccessibilityElement = YES;
self.accessibilityTraits = UIAccessibilityTraitStaticText; self.accessibilityTraits = UIAccessibilityTraitStaticText;
_constrainedSize = CGSizeMake(-INFINITY, -INFINITY);
// Placeholders // Placeholders
// Disabled by default in ASDisplayNode, but add a few options for those who toggle // Disabled by default in ASDisplayNode, but add a few options for those who toggle
// on the special placeholder behavior of ASTextNode. // on the special placeholder behavior of ASTextNode.
@@ -139,8 +204,6 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
{ {
CGColorRelease(_shadowColor); CGColorRelease(_shadowColor);
[self _invalidateRenderer];
if (_longPressGestureRecognizer) { if (_longPressGestureRecognizer) {
_longPressGestureRecognizer.delegate = nil; _longPressGestureRecognizer.delegate = nil;
[_longPressGestureRecognizer removeTarget:nil action:NULL]; [_longPressGestureRecognizer removeTarget:nil action:NULL];
@@ -181,27 +244,12 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
#pragma mark - ASDisplayNode #pragma mark - ASDisplayNode
// FIXME: Re-evaluate if it is still the right decision to clear the renderer at this stage.
// This code was written before TextKit and when 512MB devices were still the overwhelming majority.
- (void)displayDidFinish
{
[super displayDidFinish];
// We invalidate our renderer here to clear the very high memory cost of
// keeping this around. _invalidateRenderer will dealloc this onto a bg
// thread resulting in less stutters on the main thread than if it were
// to be deallocated in dealloc. This is also helpful in opportunistically
// reducing memory consumption and reducing the overall footprint of the app.
[self _invalidateRenderer];
}
- (void)clearContents - (void)clearContents
{ {
// We discard the backing store and renderer to prevent the very large // We discard the backing store and renderer to prevent the very large
// memory overhead of maintaining these for all text nodes. They can be // memory overhead of maintaining these for all text nodes. They can be
// regenerated when layout is necessary. // regenerated when layout is necessary.
[super clearContents]; // ASDisplayNode will set layer.contents = nil [super clearContents]; // ASDisplayNode will set layer.contents = nil
[self _invalidateRenderer];
} }
- (void)didLoad - (void)didLoad
@@ -218,45 +266,23 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
} }
} }
- (void)setFrame:(CGRect)frame
{
[super setFrame:frame];
[self _invalidateRendererIfNeededForBoundsSize:frame.size];
}
- (void)setBounds:(CGRect)bounds
{
[super setBounds:bounds];
[self _invalidateRendererIfNeededForBoundsSize:bounds.size];
}
#pragma mark - Renderer Management #pragma mark - Renderer Management
- (ASTextKitRenderer *)_renderer - (ASTextKitRenderer *)_renderer
{ {
return [self _rendererWithBounds:self.threadSafeBounds]; CGSize constrainedSize = self.threadSafeBounds.size;
return [self _rendererWithBoundsSlow:{.size = constrainedSize}];
} }
- (ASTextKitRenderer *)_rendererWithBounds:(CGRect)bounds - (ASTextKitRenderer *)_rendererWithBoundsSlow:(CGRect)bounds
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
bounds.size.width -= (_textContainerInset.left + _textContainerInset.right);
if (_renderer == nil) { bounds.size.height -= (_textContainerInset.top + _textContainerInset.bottom);
CGSize constrainedSize; return rendererForAttributes([self _rendererAttributes], bounds.size);
if (_constrainedSize.width != -INFINITY) {
constrainedSize = _constrainedSize;
} else {
constrainedSize = bounds.size;
constrainedSize.width -= (_textContainerInset.left + _textContainerInset.right);
constrainedSize.height -= (_textContainerInset.top + _textContainerInset.bottom);
}
_renderer = [[ASTextKitRenderer alloc] initWithTextKitAttributes:[self _rendererAttributes]
constrainedSize:constrainedSize];
}
return _renderer;
} }
- (ASTextKitAttributes)_rendererAttributes - (ASTextKitAttributes)_rendererAttributes
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
@@ -276,38 +302,6 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
}; };
} }
- (void)_invalidateRendererIfNeeded
{
[self _invalidateRendererIfNeededForBoundsSize:self.threadSafeBounds.size];
}
- (void)_invalidateRendererIfNeededForBoundsSize:(CGSize)boundsSize
{
if ([self _needInvalidateRendererForBoundsSize:boundsSize]) {
// Our bounds have changed to a size that is not identical to our constraining size,
// so our previous layout information is invalid, and TextKit may draw at the
// incorrect origin.
{
ASDN::MutexLocker l(__instanceLock__);
_constrainedSize = CGSizeMake(-INFINITY, -INFINITY);
}
[self _invalidateRenderer];
}
}
- (void)_invalidateRenderer
{
ASDN::MutexLocker l(__instanceLock__);
if (_renderer) {
// Destruction of the layout managers/containers/text storage is quite
// expensive, and can take some time, so we dispatch onto a bg queue to
// actually dealloc.
ASPerformBackgroundDeallocation(_renderer);
_renderer = nil;
}
}
#pragma mark - Layout and Sizing #pragma mark - Layout and Sizing
- (void)setTextContainerInset:(UIEdgeInsets)textContainerInset - (void)setTextContainerInset:(UIEdgeInsets)textContainerInset
@@ -327,60 +321,6 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
return _textContainerInset; return _textContainerInset;
} }
- (BOOL)_needInvalidateRendererForBoundsSize:(CGSize)boundsSize
{
ASDN::MutexLocker l(__instanceLock__);
if (_renderer == nil) {
return YES;
}
// If the size is not the same as the constraint we provided to the renderer, start out assuming we need
// a new one. However, there are common cases where the constrained size doesn't need to be the same as calculated.
CGSize rendererConstrainedSize = _renderer.constrainedSize;
//inset bounds
boundsSize.width -= _textContainerInset.left + _textContainerInset.right;
boundsSize.height -= _textContainerInset.top + _textContainerInset.bottom;
if (CGSizeEqualToSize(boundsSize, rendererConstrainedSize)) {
return NO;
} else {
// It is very common to have a constrainedSize with a concrete, specific width but +Inf height.
// In this case, as long as the text node has bounds as large as the full calculatedLayout suggests,
// it means that the text has all the room it needs (as it was not vertically bounded). So, we will not
// experience truncation and don't need to recreate the renderer with the size it already calculated,
// as this would essentially serve to set its constrainedSize to be its calculatedSize (unnecessary).
ASLayout *layout = self.calculatedLayout;
if (layout != nil && CGSizeEqualToSize(boundsSize, layout.size)) {
return (boundsSize.width != rendererConstrainedSize.width);
} else {
return YES;
}
}
}
- (void)calculatedLayoutDidChange
{
[super calculatedLayoutDidChange];
ASLayout *layout = self.calculatedLayout;
if (layout != nil) {
ASDN::MutexLocker l(__instanceLock__);
CGSize layoutSize = layout.size;
// Apply textContainerInset
layoutSize.width -= (_textContainerInset.left + _textContainerInset.right);
layoutSize.height -= (_textContainerInset.top + _textContainerInset.bottom);
if (CGSizeEqualToSize(_constrainedSize, layoutSize) == NO) {
_constrainedSize = layoutSize;
[self _invalidateRenderer];
}
}
}
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
@@ -391,26 +331,17 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
// Cache the original constrained size for final size calculateion // Cache the original constrained size for final size calculateion
CGSize originalConstrainedSize = constrainedSize; CGSize originalConstrainedSize = constrainedSize;
// Adjust constrainedSize for textContainerInset before assigning it
constrainedSize.width -= (_textContainerInset.left + _textContainerInset.right);
constrainedSize.height -= (_textContainerInset.top + _textContainerInset.bottom);
_constrainedSize = constrainedSize;
if (_renderer != nil && CGSizeEqualToSize(constrainedSize, _renderer.constrainedSize) == NO) {
[self _invalidateRenderer];
}
[self setNeedsDisplay]; [self setNeedsDisplay];
CGSize size = [self _renderer].size; ASTextKitRenderer *renderer = [self _rendererWithBoundsSlow:{.size = constrainedSize}];
CGSize size = renderer.size;
if (_attributedText.length > 0) { if (_attributedText.length > 0) {
self.style.ascender = [[self class] ascenderWithAttributedString:_attributedText]; self.style.ascender = [[self class] ascenderWithAttributedString:_attributedText];
self.style.descender = [[_attributedText attribute:NSFontAttributeName atIndex:_attributedText.length - 1 effectiveRange:NULL] descender]; self.style.descender = [[_attributedText attribute:NSFontAttributeName atIndex:_attributedText.length - 1 effectiveRange:NULL] descender];
if (_renderer.currentScaleFactor > 0 && _renderer.currentScaleFactor < 1.0) { if (renderer.currentScaleFactor > 0 && renderer.currentScaleFactor < 1.0) {
// while not perfect, this is a good estimate of what the ascender of the scaled font will be. // while not perfect, this is a good estimate of what the ascender of the scaled font will be.
self.style.ascender *= _renderer.currentScaleFactor; self.style.ascender *= renderer.currentScaleFactor;
self.style.descender *= _renderer.currentScaleFactor; self.style.descender *= renderer.currentScaleFactor;
} }
} }
@@ -461,9 +392,6 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
// Without this, the size calculation of the text with truncation applied will // Without this, the size calculation of the text with truncation applied will
// not take into account the attributes of attributedText in the last line // not take into account the attributes of attributedText in the last line
[self _updateComposedTruncationText]; [self _updateComposedTruncationText];
// We need an entirely new renderer
[self _invalidateRenderer];
} }
NSUInteger length = attributedText.length; NSUInteger length = attributedText.length;
@@ -495,7 +423,6 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
} }
_exclusionPaths = [exclusionPaths copy]; _exclusionPaths = [exclusionPaths copy];
[self _invalidateRenderer];
[self setNeedsLayout]; [self setNeedsLayout];
[self setNeedsDisplay]; [self setNeedsDisplay];
} }
@@ -536,7 +463,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
CGContextTranslateCTM(context, _textContainerInset.left, _textContainerInset.top); CGContextTranslateCTM(context, _textContainerInset.left, _textContainerInset.top);
ASTextKitRenderer *renderer = [self _rendererWithBounds:drawParameterBounds]; ASTextKitRenderer *renderer = [self _rendererWithBoundsSlow:drawParameterBounds];
// Fill background // Fill background
if (backgroundColor != nil) { if (backgroundColor != nil) {
@@ -790,11 +717,12 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
if (highlightTargetLayer != nil) { if (highlightTargetLayer != nil) {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
ASTextKitRenderer *renderer = [self _renderer];
NSArray *highlightRects = [[self _renderer] rectsForTextRange:highlightRange measureOption:ASTextKitRendererMeasureOptionBlock]; NSArray *highlightRects = [renderer rectsForTextRange:highlightRange measureOption:ASTextKitRendererMeasureOptionBlock];
NSMutableArray *converted = [NSMutableArray arrayWithCapacity:highlightRects.count]; NSMutableArray *converted = [NSMutableArray arrayWithCapacity:highlightRects.count];
for (NSValue *rectValue in highlightRects) { for (NSValue *rectValue in highlightRects) {
UIEdgeInsets shadowPadding = _renderer.shadower.shadowPadding; UIEdgeInsets shadowPadding = renderer.shadower.shadowPadding;
CGRect rendererRect = ASTextNodeAdjustRenderRectForShadowPadding(rectValue.CGRectValue, shadowPadding); CGRect rendererRect = ASTextNodeAdjustRenderRectForShadowPadding(rectValue.CGRectValue, shadowPadding);
// The rects returned from renderer don't have `textContainerInset`, // The rects returned from renderer don't have `textContainerInset`,
@@ -1119,7 +1047,6 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI
CGColorRelease(_shadowColor); CGColorRelease(_shadowColor);
_shadowColor = CGColorRetain(shadowColor); _shadowColor = CGColorRetain(shadowColor);
_cachedShadowUIColor = [UIColor colorWithCGColor:shadowColor]; _cachedShadowUIColor = [UIColor colorWithCGColor:shadowColor];
[self _invalidateRenderer];
[self setNeedsDisplay]; [self setNeedsDisplay];
} }
} }
@@ -1137,7 +1064,6 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI
if (!CGSizeEqualToSize(_shadowOffset, shadowOffset)) { if (!CGSizeEqualToSize(_shadowOffset, shadowOffset)) {
_shadowOffset = shadowOffset; _shadowOffset = shadowOffset;
[self _invalidateRenderer];
[self setNeedsDisplay]; [self setNeedsDisplay];
} }
} }
@@ -1155,7 +1081,6 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI
if (_shadowOpacity != shadowOpacity) { if (_shadowOpacity != shadowOpacity) {
_shadowOpacity = shadowOpacity; _shadowOpacity = shadowOpacity;
[self _invalidateRenderer];
[self setNeedsDisplay]; [self setNeedsDisplay];
} }
} }
@@ -1173,7 +1098,6 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI
if (_shadowRadius != shadowRadius) { if (_shadowRadius != shadowRadius) {
_shadowRadius = shadowRadius; _shadowRadius = shadowRadius;
[self _invalidateRenderer];
[self setNeedsDisplay]; [self setNeedsDisplay];
} }
} }
@@ -1232,7 +1156,6 @@ static NSAttributedString *DefaultTruncationAttributedString()
if (_truncationMode != truncationMode) { if (_truncationMode != truncationMode) {
_truncationMode = truncationMode; _truncationMode = truncationMode;
[self _invalidateRenderer];
[self setNeedsDisplay]; [self setNeedsDisplay];
} }
} }
@@ -1251,7 +1174,6 @@ static NSAttributedString *DefaultTruncationAttributedString()
if ([_pointSizeScaleFactors isEqualToArray:pointSizeScaleFactors] == NO) { if ([_pointSizeScaleFactors isEqualToArray:pointSizeScaleFactors] == NO) {
_pointSizeScaleFactors = pointSizeScaleFactors; _pointSizeScaleFactors = pointSizeScaleFactors;
[self _invalidateRenderer];
[self setNeedsDisplay]; [self setNeedsDisplay];
}} }}
@@ -1261,7 +1183,6 @@ static NSAttributedString *DefaultTruncationAttributedString()
if (_maximumNumberOfLines != maximumNumberOfLines) { if (_maximumNumberOfLines != maximumNumberOfLines) {
_maximumNumberOfLines = maximumNumberOfLines; _maximumNumberOfLines = maximumNumberOfLines;
[self _invalidateRenderer];
[self setNeedsDisplay]; [self setNeedsDisplay];
} }
} }
@@ -1285,7 +1206,6 @@ static NSAttributedString *DefaultTruncationAttributedString()
- (void)_invalidateTruncationText - (void)_invalidateTruncationText
{ {
[self _updateComposedTruncationText]; [self _updateComposedTruncationText];
[self _invalidateRenderer];
[self setNeedsDisplay]; [self setNeedsDisplay];
} }

View File

@@ -110,7 +110,8 @@ struct ASTextKitAttributes {
&& maximumNumberOfLines == other.maximumNumberOfLines && maximumNumberOfLines == other.maximumNumberOfLines
&& shadowOpacity == other.shadowOpacity && shadowOpacity == other.shadowOpacity
&& shadowRadius == other.shadowRadius && shadowRadius == other.shadowRadius
&& [pointSizeScaleFactors isEqualToArray:other.pointSizeScaleFactors] && (pointSizeScaleFactors == other.pointSizeScaleFactors
|| [pointSizeScaleFactors isEqualToArray:other.pointSizeScaleFactors])
&& CGSizeEqualToSize(shadowOffset, other.shadowOffset) && CGSizeEqualToSize(shadowOffset, other.shadowOffset)
&& ASObjectIsEqual(exclusionPaths, other.exclusionPaths) && ASObjectIsEqual(exclusionPaths, other.exclusionPaths)
&& ASObjectIsEqual(avoidTailTruncationSet, other.avoidTailTruncationSet) && ASObjectIsEqual(avoidTailTruncationSet, other.avoidTailTruncationSet)

View File

@@ -29,8 +29,6 @@
exclusionPaths:(NSArray *)exclusionPaths exclusionPaths:(NSArray *)exclusionPaths
constrainedSize:(CGSize)constrainedSize; constrainedSize:(CGSize)constrainedSize;
@property (nonatomic, assign, readwrite) CGSize constrainedSize;
/** /**
All operations on TextKit values MUST occur within this locked context. Simultaneous access (even non-mutative) to All operations on TextKit values MUST occur within this locked context. Simultaneous access (even non-mutative) to
TextKit components may cause crashes. TextKit components may cause crashes.

View File

@@ -54,18 +54,6 @@
return self; return self;
} }
- (CGSize)constrainedSize
{
ASDN::MutexSharedLocker l(__instanceLock__);
return _textContainer.size;
}
- (void)setConstrainedSize:(CGSize)constrainedSize
{
ASDN::MutexSharedLocker l(__instanceLock__);
_textContainer.size = constrainedSize;
}
- (void)performBlockWithLockedTextKitComponents:(void (^)(NSLayoutManager *, - (void)performBlockWithLockedTextKitComponents:(void (^)(NSLayoutManager *,
NSTextStorage *, NSTextStorage *,
NSTextContainer *))block NSTextContainer *))block

View File

@@ -37,7 +37,6 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
@implementation ASTextKitRenderer { @implementation ASTextKitRenderer {
CGSize _calculatedSize; CGSize _calculatedSize;
BOOL _sizeIsCalculated;
} }
@synthesize attributes = _attributes, context = _context, shadower = _shadower, truncater = _truncater, fontSizeAdjuster = _fontSizeAdjuster; @synthesize attributes = _attributes, context = _context, shadower = _shadower, truncater = _truncater, fontSizeAdjuster = _fontSizeAdjuster;
@@ -49,62 +48,38 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
if (self = [super init]) { if (self = [super init]) {
_constrainedSize = constrainedSize; _constrainedSize = constrainedSize;
_attributes = attributes; _attributes = attributes;
_sizeIsCalculated = NO;
_currentScaleFactor = 1; _currentScaleFactor = 1;
}
return self;
}
- (ASTextKitShadower *)shadower // As the renderer should be thread safe, create all subcomponents in the initialization method
{
if (!_shadower) {
ASTextKitAttributes attributes = _attributes;
_shadower = [ASTextKitShadower shadowerWithShadowOffset:attributes.shadowOffset _shadower = [ASTextKitShadower shadowerWithShadowOffset:attributes.shadowOffset
shadowColor:attributes.shadowColor shadowColor:attributes.shadowColor
shadowOpacity:attributes.shadowOpacity shadowOpacity:attributes.shadowOpacity
shadowRadius:attributes.shadowRadius]; shadowRadius:attributes.shadowRadius];
}
return _shadower;
}
- (ASTextKitTailTruncater *)truncater
{
if (!_truncater) {
ASTextKitAttributes attributes = _attributes;
NSCharacterSet *avoidTailTruncationSet = attributes.avoidTailTruncationSet ? : _defaultAvoidTruncationCharacterSet();
_truncater = [[ASTextKitTailTruncater alloc] initWithContext:[self context]
truncationAttributedString:attributes.truncationAttributedString
avoidTailTruncationSet:avoidTailTruncationSet];
}
return _truncater;
}
- (ASTextKitFontSizeAdjuster *)fontSizeAdjuster
{
if (!_fontSizeAdjuster) {
ASTextKitAttributes attributes = _attributes;
// We must inset the constrained size by the size of the shadower. // We must inset the constrained size by the size of the shadower.
CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize]; CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize];
_fontSizeAdjuster = [[ASTextKitFontSizeAdjuster alloc] initWithContext:[self context]
constrainedSize:shadowConstrainedSize
textKitAttributes:attributes];
}
return _fontSizeAdjuster;
}
- (ASTextKitContext *)context
{
if (!_context) {
ASTextKitAttributes attributes = _attributes;
// We must inset the constrained size by the size of the shadower.
CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize];
_context = [[ASTextKitContext alloc] initWithAttributedString:attributes.attributedString _context = [[ASTextKitContext alloc] initWithAttributedString:attributes.attributedString
lineBreakMode:attributes.lineBreakMode lineBreakMode:attributes.lineBreakMode
maximumNumberOfLines:attributes.maximumNumberOfLines maximumNumberOfLines:attributes.maximumNumberOfLines
exclusionPaths:attributes.exclusionPaths exclusionPaths:attributes.exclusionPaths
constrainedSize:shadowConstrainedSize]; constrainedSize:shadowConstrainedSize];
NSCharacterSet *avoidTailTruncationSet = attributes.avoidTailTruncationSet ?: _defaultAvoidTruncationCharacterSet();
_truncater = [[ASTextKitTailTruncater alloc] initWithContext:[self context]
truncationAttributedString:attributes.truncationAttributedString
avoidTailTruncationSet:avoidTailTruncationSet];
ASTextKitAttributes attributes = _attributes;
// We must inset the constrained size by the size of the shadower.
_fontSizeAdjuster = [[ASTextKitFontSizeAdjuster alloc] initWithContext:[self context]
constrainedSize:shadowConstrainedSize
textKitAttributes:attributes];
// Calcualate size immediately
[self _calculateSize];
} }
return _context; return self;
} }
- (NSStringDrawingContext *)stringDrawingContext - (NSStringDrawingContext *)stringDrawingContext
@@ -127,10 +102,6 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
- (CGSize)size - (CGSize)size
{ {
if (!_sizeIsCalculated) {
[self _calculateSize];
_sizeIsCalculated = YES;
}
return _calculatedSize; return _calculatedSize;
} }
@@ -223,12 +194,6 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
// We add an assertion so we can track the rare conditions where a graphics context is not present // We add an assertion so we can track the rare conditions where a graphics context is not present
ASDisplayNodeAssertNotNil(context, @"This is no good without a context."); ASDisplayNodeAssertNotNil(context, @"This is no good without a context.");
// This renderer may not be the one that did the sizing. If that is the case its truncation and currentScaleFactor may not have been evaluated.
// If there's any possibility we need to truncate or scale (i.e. width is not infinite), perform the size calculation.
if (_sizeIsCalculated == NO && isinf(_constrainedSize.width) == NO) {
[self _calculateSize];
}
bounds = CGRectIntersection(bounds, { .size = _constrainedSize }); bounds = CGRectIntersection(bounds, { .size = _constrainedSize });
CGRect shadowInsetBounds = [[self shadower] insetRectWithConstrainedRect:bounds]; CGRect shadowInsetBounds = [[self shadower] insetRectWithConstrainedRect:bounds];
@@ -298,9 +263,7 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
- (std::vector<NSRange>)visibleRanges - (std::vector<NSRange>)visibleRanges
{ {
ASTextKitTailTruncater *truncater = [self truncater]; return _truncater.visibleRanges;
[truncater truncate];
return truncater.visibleRanges;
} }
@end @end

View File

@@ -18,6 +18,13 @@
@implementation ASTextNodeSnapshotTests @implementation ASTextNodeSnapshotTests
- (void)setUp
{
[super setUp];
self.recordMode = NO;
}
- (void)testTextContainerInset - (void)testTextContainerInset
{ {
// trivial test case to ensure ASSnapshotTestCase works // trivial test case to ensure ASSnapshotTestCase works

View File

@@ -171,7 +171,9 @@
ASTextNodeTestDelegate *delegate = [ASTextNodeTestDelegate new]; ASTextNodeTestDelegate *delegate = [ASTextNodeTestDelegate new];
_textNode.delegate = delegate; _textNode.delegate = delegate;
[_textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 100))]; ASLayout *layout = [_textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 100))];
_textNode.frame = CGRectMake(0, 0, layout.size.width, layout.size.height);
NSRange returnedLinkRange; NSRange returnedLinkRange;
NSString *returnedAttributeName; NSString *returnedAttributeName;
NSString *returnedLinkAttributeValue = [_textNode linkAttributeValueAtPoint:CGPointMake(3, 3) attributeName:&returnedAttributeName range:&returnedLinkRange]; NSString *returnedLinkAttributeValue = [_textNode linkAttributeValueAtPoint:CGPointMake(3, 3) attributeName:&returnedAttributeName range:&returnedLinkRange];

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB