Skip to content
This repository was archived by the owner on Feb 2, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
284 changes: 191 additions & 93 deletions AsyncDisplayKit.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion AsyncDisplayKit/ASEditableTextNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

#import "ASDisplayNode+Subclasses.h"
#import "ASEqualityHelpers.h"
#import "ASTextNodeTextKitHelpers.h"
#import "ASTextKitHelpers.h"
#import "ASTextNodeWordKerner.h"
#import "ASThread.h"

Expand Down
26 changes: 26 additions & 0 deletions AsyncDisplayKit/ASEqualityHashHelpers.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/

#import "ASEqualityHashHelpers.h"

#import <functional>
#import <objc/runtime.h>
#import <stdio.h>
#import <string>

NSUInteger ASIntegerArrayHash(const NSUInteger *subhashes, NSUInteger count)
{
uint64_t result = subhashes[0];
for (int ii = 1; ii < count; ++ii) {
result = ASHashCombine(result, subhashes[ii]);
}
return ASHash64ToNative(result);
}

2 changes: 1 addition & 1 deletion AsyncDisplayKit/ASTextNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) {
@abstract The maximum number of lines to render of the text before truncation.
@default 0 (No limit)
*/
@property (nonatomic, assign) NSUInteger maximumLineCount;
@property (nonatomic, assign) NSUInteger maximumNumberOfLines;

/**
@abstract The number of lines in the text. Text must have been sized first.
Expand Down
143 changes: 56 additions & 87 deletions AsyncDisplayKit/ASTextNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
#import <AsyncDisplayKit/ASHighlightOverlayLayer.h>
#import <AsyncDisplayKit/ASTextNodeCoreTextAdditions.h>
#import <AsyncDisplayKit/ASTextNodeTextKitHelpers.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>

#import "ASTextKitCoreTextAdditions.h"
#import "ASTextKitHelpers.h"
#import "ASTextKitRenderer.h"
#import "ASTextKitRenderer+Positioning.h"
#import "ASTextKitShadower.h"

#import "ASInternalHelpers.h"
#import "ASTextNodeRenderer.h"
#import "ASTextNodeShadower.h"
#import "ASEqualityHelpers.h"

static const NSTimeInterval ASTextNodeHighlightFadeOutDuration = 0.15;
Expand All @@ -30,14 +32,11 @@

@interface ASTextNodeDrawParameters : NSObject

- (instancetype)initWithRenderer:(ASTextNodeRenderer *)renderer
shadower:(ASTextNodeShadower *)shadower
- (instancetype)initWithRenderer:(ASTextKitRenderer *)renderer
textOrigin:(CGPoint)textOrigin
backgroundColor:(CGColorRef)backgroundColor;

@property (nonatomic, strong, readonly) ASTextNodeRenderer *renderer;

@property (nonatomic, strong, readonly) ASTextNodeShadower *shadower;
@property (nonatomic, strong, readonly) ASTextKitRenderer *renderer;

@property (nonatomic, assign, readonly) CGPoint textOrigin;

Expand All @@ -47,14 +46,12 @@ - (instancetype)initWithRenderer:(ASTextNodeRenderer *)renderer

@implementation ASTextNodeDrawParameters

- (instancetype)initWithRenderer:(ASTextNodeRenderer *)renderer
shadower:(ASTextNodeShadower *)shadower
- (instancetype)initWithRenderer:(ASTextKitRenderer *)renderer
textOrigin:(CGPoint)textOrigin
backgroundColor:(CGColorRef)backgroundColor
{
if (self = [super init]) {
_renderer = renderer;
_shadower = shadower;
_textOrigin = textOrigin;
_backgroundColor = CGColorRetain(backgroundColor);
}
Expand Down Expand Up @@ -91,8 +88,7 @@ @implementation ASTextNode {

CGSize _constrainedSize;

ASTextNodeRenderer *_renderer;
ASTextNodeShadower *_shadower;
ASTextKitRenderer *_renderer;

UILongPressGestureRecognizer *_longPressGestureRecognizer;
}
Expand All @@ -117,7 +113,7 @@ - (instancetype)init
self.needsDisplayOnBoundsChange = YES;

_truncationMode = NSLineBreakByWordWrapping;
_truncationAttributedString = DefaultTruncationAttributedString();
_composedTruncationString = DefaultTruncationAttributedString();

// The common case is for a text node to be non-opaque and blended over some background.
self.opaque = NO;
Expand Down Expand Up @@ -181,32 +177,13 @@ - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
{
ASDisplayNodeAssert(constrainedSize.width >= 0, @"Constrained width for text (%f) is too narrow", constrainedSize.width);
ASDisplayNodeAssert(constrainedSize.height >= 0, @"Constrained height for text (%f) is too short", constrainedSize.height);
// The supplied constrainedSize should include room for shadowPadding.
// Inset the constrainedSize by the shadow padding to get the size available for text.
UIEdgeInsets shadowPadding = [[self _shadower] shadowPadding];
// Invert the negative values of shadow padding to get a positive inset
UIEdgeInsets shadowPaddingOutset = ASDNEdgeInsetsInvert(shadowPadding);

// Inset the padded constrainedSize to get the remaining size available for text
CGRect constrainedRect = CGRect{CGPointZero, constrainedSize};
CGSize constrainedSizeForText = UIEdgeInsetsInsetRect(constrainedRect, shadowPaddingOutset).size;
ASDisplayNodeAssert(constrainedSizeForText.width >= 0, @"Constrained width for text (%f) after subtracting shadow padding (%@) is too narrow", constrainedSizeForText.width, NSStringFromUIEdgeInsets(shadowPadding));
ASDisplayNodeAssert(constrainedSizeForText.height >= 0, @"Constrained height for text (%f) after subtracting shadow padding (%@) is too short", constrainedSizeForText.height, NSStringFromUIEdgeInsets(shadowPadding));

_constrainedSize = constrainedSizeForText;

_constrainedSize = constrainedSize;
[self _invalidateRenderer];
ASDisplayNodeRespectThreadAffinityOfNode(self, ^{
[self setNeedsDisplay];
});
CGSize rendererSize = [[self _renderer] size];

// Add shadow padding back
CGSize renderSizePlusShadowPadding = UIEdgeInsetsInsetRect(CGRect{CGPointZero, rendererSize}, shadowPadding).size;
ASDisplayNodeAssert(renderSizePlusShadowPadding.width >= 0, @"Calculated width for text with shadow padding (%f) is too narrow", constrainedSizeForText.width);
ASDisplayNodeAssert(renderSizePlusShadowPadding.height >= 0, @"Calculated height for text with shadow padding (%f) is too short", constrainedSizeForText.height);
renderSizePlusShadowPadding = ceilSizeValue(renderSizePlusShadowPadding);
return CGSizeMake(MIN(renderSizePlusShadowPadding.width, constrainedSize.width),
MIN(renderSizePlusShadowPadding.height, constrainedSize.height));
return [[self _renderer] size];
}

- (void)displayDidFinish
Expand Down Expand Up @@ -269,53 +246,43 @@ - (void)setBounds:(CGRect)bounds

#pragma mark - Renderer Management

- (ASTextNodeRenderer *)_renderer
- (ASTextKitRenderer *)_renderer
{
ASDN::MutexLocker l(_rendererLock);
if (_renderer == nil) {
CGSize constrainedSize = _constrainedSize.width != -INFINITY ? _constrainedSize : self.bounds.size;
_renderer = [[ASTextNodeRenderer alloc] initWithAttributedString:_attributedString
truncationString:_composedTruncationString
truncationMode:_truncationMode
maximumLineCount:_maximumLineCount
exclusionPaths:_exclusionPaths
constrainedSize:constrainedSize];
_renderer = [[ASTextKitRenderer alloc] initWithTextKitAttributes:[self _rendererAttributes]
constrainedSize:constrainedSize];
}
return _renderer;
}

- (ASTextKitAttributes)_rendererAttributes
{
return {
.attributedString = _attributedString,
.truncationAttributedString = _composedTruncationString,
.lineBreakMode = _truncationMode,
.maximumNumberOfLines = _maximumNumberOfLines,
.exclusionPaths = _exclusionPaths,
};
}

- (void)_invalidateRenderer
{
ASDN::MutexLocker l(_rendererLock);
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.
__block ASTextNodeRenderer *renderer = _renderer;
__block ASTextKitRenderer *renderer = _renderer;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
renderer = nil;
});
}
_renderer = nil;
}

#pragma mark - Shadow Drawer Management
- (ASTextNodeShadower *)_shadower
{
if (_shadower == nil) {
_shadower = [[ASTextNodeShadower alloc] initWithShadowOffset:_shadowOffset
shadowColor:_shadowColor
shadowOpacity:_shadowOpacity
shadowRadius:_shadowRadius];
}
return _shadower;
}

- (void)_invalidateShadower
{
_shadower = nil;
}

#pragma mark - Modifying User Text

- (void)setAttributedString:(NSAttributedString *)attributedString {
Expand Down Expand Up @@ -399,11 +366,11 @@ + (void)drawRect:(CGRect)bounds withParameters:(ASTextNodeDrawParameters *)param
}

// Draw shadow
[parameters.shadower setShadowInContext:context];
[[parameters.renderer shadower] setShadowInContext:context];

// Draw text
bounds.origin = parameters.textOrigin;
[parameters.renderer drawInRect:bounds inContext:context];
[parameters.renderer drawInContext:context bounds:bounds];

CGContextRestoreGState(context);
}
Expand All @@ -414,9 +381,8 @@ - (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer
UIEdgeInsets shadowPadding = [self shadowPadding];
CGPoint textOrigin = CGPointMake(self.bounds.origin.x - shadowPadding.left, self.bounds.origin.y - shadowPadding.top);
return [[ASTextNodeDrawParameters alloc] initWithRenderer:[self _renderer]
shadower:[self _shadower]
textOrigin:textOrigin
backgroundColor:self.backgroundColor.CGColor];
textOrigin:textOrigin
backgroundColor:self.backgroundColor.CGColor];
}

#pragma mark - Attributes
Expand All @@ -436,8 +402,8 @@ - (id)_linkAttributeValueAtPoint:(CGPoint)point
range:(out NSRange *)rangeOut
inAdditionalTruncationMessage:(out BOOL *)inAdditionalTruncationMessageOut
{
ASTextNodeRenderer *renderer = [self _renderer];
NSRange visibleRange = [renderer visibleRange];
ASTextKitRenderer *renderer = [self _renderer];
NSRange visibleRange = renderer.visibleRanges[0];
NSAttributedString *attributedString = _attributedString;

// Check in a 9-point region around the actual touch point so we make sure
Expand Down Expand Up @@ -635,10 +601,11 @@ - (void)_setHighlightRange:(NSRange)highlightRange forAttributeName:(NSString *)
}

if (highlightTargetLayer != nil) {
NSArray *highlightRects = [[self _renderer] rectsForTextRange:highlightRange measureOption:ASTextNodeRendererMeasureOptionBlock];
NSArray *highlightRects = [[self _renderer] rectsForTextRange:highlightRange measureOption:ASTextKitRendererMeasureOptionBlock];
NSMutableArray *converted = [NSMutableArray arrayWithCapacity:highlightRects.count];
for (NSValue *rectValue in highlightRects) {
CGRect rendererRect = [[self class] _adjustRendererRect:rectValue.CGRectValue forShadowPadding:_shadower.shadowPadding];
UIEdgeInsets shadowPadding = _renderer.shadower.shadowPadding;
CGRect rendererRect = [[self class] _adjustRendererRect:rectValue.CGRectValue forShadowPadding:shadowPadding];
CGRect highlightedRect = [self.layer convertRect:rendererRect toLayer:highlightTargetLayer];

// We set our overlay layer's frame to the bounds of the highlight target layer.
Expand Down Expand Up @@ -699,7 +666,7 @@ + (CGRect)_adjustRendererRect:(CGRect)rendererRect forShadowPadding:(UIEdgeInset
return rendererRect;
}

- (NSArray *)_rectsForTextRange:(NSRange)textRange measureOption:(ASTextNodeRendererMeasureOption)measureOption
- (NSArray *)_rectsForTextRange:(NSRange)textRange measureOption:(ASTextKitRendererMeasureOption)measureOption
{
NSArray *rects = [[self _renderer] rectsForTextRange:textRange measureOption:measureOption];
NSMutableArray *adjustedRects = [NSMutableArray array];
Expand All @@ -717,12 +684,12 @@ - (NSArray *)_rectsForTextRange:(NSRange)textRange measureOption:(ASTextNodeRend

- (NSArray *)rectsForTextRange:(NSRange)textRange
{
return [self _rectsForTextRange:textRange measureOption:ASTextNodeRendererMeasureOptionCapHeight];
return [self _rectsForTextRange:textRange measureOption:ASTextKitRendererMeasureOptionCapHeight];
}

- (NSArray *)highlightRectsForTextRange:(NSRange)textRange
{
return [self _rectsForTextRange:textRange measureOption:ASTextNodeRendererMeasureOptionBlock];
return [self _rectsForTextRange:textRange measureOption:ASTextKitRendererMeasureOptionBlock];
}

- (CGRect)trailingRect
Expand Down Expand Up @@ -755,11 +722,11 @@ - (UIImage *)placeholderImage
UIGraphicsBeginImageContext(size);
[self.placeholderColor setFill];

ASTextNodeRenderer *renderer = [self _renderer];
NSRange textRange = [renderer visibleRange];
ASTextKitRenderer *renderer = [self _renderer];
NSRange textRange = renderer.visibleRanges[0];

// cap height is both faster and creates less subpixel blending
NSArray *lineRects = [self _rectsForTextRange:textRange measureOption:ASTextNodeRendererMeasureOptionLineHeight];
NSArray *lineRects = [self _rectsForTextRange:textRange measureOption:ASTextKitRendererMeasureOptionLineHeight];

// fill each line with the placeholder color
for (NSValue *rectValue in lineRects) {
Expand Down Expand Up @@ -830,7 +797,8 @@ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
BOOL linkCrossesVisibleRange = (lastCharIndex > range.location) && (lastCharIndex < NSMaxRange(range) - 1);

if (inAdditionalTruncationMessage) {
NSRange truncationMessageRange = [self _additionalTruncationMessageRangeWithVisibleRange:[[self _renderer] visibleRange]];
NSRange visibleRange = [self _renderer].visibleRanges[0];
NSRange truncationMessageRange = [self _additionalTruncationMessageRangeWithVisibleRange:visibleRange];
[self _setHighlightRange:truncationMessageRange forAttributeName:ASTextNodeTruncationTokenAttributeName value:nil animated:YES];
} else if (range.length && !linkCrossesVisibleRange && linkAttributeValue != nil && linkAttributeName != nil) {
[self _setHighlightRange:range forAttributeName:linkAttributeName value:linkAttributeValue animated:YES];
Expand Down Expand Up @@ -905,7 +873,7 @@ - (void)setShadowColor:(CGColorRef)shadowColor
CGColorRetain(shadowColor);
}
_shadowColor = shadowColor;
[self _invalidateShadower];
[self _invalidateRenderer];
ASDisplayNodeRespectThreadAffinityOfNode(self, ^{
[self setNeedsDisplay];
});
Expand All @@ -921,7 +889,7 @@ - (void)setShadowOffset:(CGSize)shadowOffset
{
if (!CGSizeEqualToSize(_shadowOffset, shadowOffset)) {
_shadowOffset = shadowOffset;
[self _invalidateShadower];
[self _invalidateRenderer];
ASDisplayNodeRespectThreadAffinityOfNode(self, ^{
[self setNeedsDisplay];
});
Expand All @@ -937,7 +905,7 @@ - (void)setShadowOpacity:(CGFloat)shadowOpacity
{
if (_shadowOpacity != shadowOpacity) {
_shadowOpacity = shadowOpacity;
[self _invalidateShadower];
[self _invalidateRenderer];
ASDisplayNodeRespectThreadAffinityOfNode(self, ^{
[self setNeedsDisplay];
});
Expand All @@ -953,7 +921,7 @@ - (void)setShadowRadius:(CGFloat)shadowRadius
{
if (_shadowRadius != shadowRadius) {
_shadowRadius = shadowRadius;
[self _invalidateShadower];
[self _invalidateRenderer];
ASDisplayNodeRespectThreadAffinityOfNode(self, ^{
[self setNeedsDisplay];
});
Expand All @@ -962,7 +930,7 @@ - (void)setShadowRadius:(CGFloat)shadowRadius

- (UIEdgeInsets)shadowPadding
{
return [[self _shadower] shadowPadding];
return [self _renderer].shadower.shadowPadding;
}

#pragma mark - Truncation Message
Expand Down Expand Up @@ -1010,13 +978,14 @@ - (void)setTruncationMode:(NSLineBreakMode)truncationMode

- (BOOL)isTruncated
{
return [[self _renderer] truncationStringCharacterRange].location != NSNotFound;
NSRange visibleRange = [self _renderer].visibleRanges[0];
return visibleRange.length < _attributedString.length;
}

- (void)setMaximumLineCount:(NSUInteger)maximumLineCount
- (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines
{
if (_maximumLineCount != maximumLineCount) {
_maximumLineCount = maximumLineCount;
if (_maximumNumberOfLines != maximumNumberOfLines) {
_maximumNumberOfLines = maximumNumberOfLines;
[self _invalidateRenderer];
ASDisplayNodeRespectThreadAffinityOfNode(self, ^{
[self setNeedsDisplay];
Expand Down
6 changes: 0 additions & 6 deletions AsyncDisplayKit/AsyncDisplayKit.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,6 @@
#import <AsyncDisplayKit/ASRangeHandler.h>
#import <AsyncDisplayKit/ASRangeHandlerPreload.h>
#import <AsyncDisplayKit/ASRangeHandlerRender.h>
#import <AsyncDisplayKit/ASTextNodeCoreTextAdditions.h>
#import <AsyncDisplayKit/ASTextNodeRenderer.h>
#import <AsyncDisplayKit/ASTextNodeShadower.h>
#import <AsyncDisplayKit/ASTextNodeTextKitHelpers.h>
#import <AsyncDisplayKit/ASTextNodeTypes.h>
#import <AsyncDisplayKit/ASTextNodeWordKerner.h>
#import <AsyncDisplayKit/ASThread.h>
#import <AsyncDisplayKit/CGRect+ASConvenience.h>
#import <AsyncDisplayKit/NSMutableAttributedString+TextKitAdditions.h>
Expand Down
Loading