mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 22:25:57 +00:00
[ASTextKit] Remove internal side effects related to constrainedSize.
This commit is contained in:
committed by
Scott Goodson
parent
c3cbd3b583
commit
beb98b448e
@@ -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__);
|
||||||
@@ -390,27 +330,18 @@ 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];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
// As the renderer should be thread safe, create all subcomponents in the initialization method
|
||||||
}
|
|
||||||
|
|
||||||
- (ASTextKitShadower *)shadower
|
|
||||||
{
|
|
||||||
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.
|
|
||||||
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.
|
// We must inset the constrained size by the size of the shadower.
|
||||||
CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize];
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,12 +193,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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 |
Reference in New Issue
Block a user