diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index dfc8aad047..974b390886 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -13,6 +13,7 @@ Pod::Spec.new do |spec| 'AsyncDisplayKit/*.h', 'AsyncDisplayKit/Details/**/*.h', 'AsyncDisplayKit/Layout/*.h', + 'AsyncDisplayKit/TextKit/*.h', 'Base/*.h' ] diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index a1e91b0fd1..fe19c59286 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -137,7 +137,7 @@ 254C6B731BF94DF4003EC431 /* ASTextKitCoreTextAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BB1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h */; }; 254C6B741BF94DF4003EC431 /* ASTextNodeWordKerner.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754B91BEE458E00737CA5 /* ASTextNodeWordKerner.h */; }; 254C6B751BF94DF4003EC431 /* ASTextKitHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BA1BEE458E00737CA5 /* ASTextKitHelpers.h */; }; - 254C6B761BF94DF4003EC431 /* ASTextNodeTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BC1BEE458E00737CA5 /* ASTextNodeTypes.h */; }; + 254C6B761BF94DF4003EC431 /* ASTextNodeTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BC1BEE458E00737CA5 /* ASTextNodeTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; 254C6B771BF94DF4003EC431 /* ASTextKitAttributes.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754951BEE44CD00737CA5 /* ASTextKitAttributes.h */; }; 254C6B781BF94DF4003EC431 /* ASTextKitContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754961BEE44CD00737CA5 /* ASTextKitContext.h */; }; 254C6B791BF94DF4003EC431 /* ASTextKitEntityAttribute.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754981BEE44CD00737CA5 /* ASTextKitEntityAttribute.h */; }; diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index 8dbea6eb57..39441ed1c2 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -21,6 +21,7 @@ #import "ASTextKitRenderer.h" #import "ASTextKitRenderer+Positioning.h" #import "ASTextKitShadower.h" +#import "ASTextNodeWordKerner.h" #import "ASInternalHelpers.h" #import "ASEqualityHelpers.h" @@ -74,7 +75,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation @end -@interface ASTextNode () +@interface ASTextNode () @end @@ -100,6 +101,10 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation ASTextKitRenderer *_renderer; UILongPressGestureRecognizer *_longPressGestureRecognizer; + + ASDN::Mutex _wordKernerLock; + ASTextNodeWordKerner *_wordKerner; + } @dynamic placeholderEnabled; @@ -257,6 +262,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; .maximumNumberOfLines = _maximumNumberOfLines, .exclusionPaths = _exclusionPaths, .minimumScaleFactor = _minimumScaleFactor, + .layoutManagerDelegate = [self _wordKerner], }; } @@ -292,6 +298,15 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; } } +- (ASTextNodeWordKerner *)_wordKerner +{ + ASDN::MutexLocker l(_wordKernerLock); + if (_wordKerner == nil) { + _wordKerner = [[ASTextNodeWordKerner alloc] init]; + } + return _wordKerner; +} + #pragma mark - Layout and Sizing - (BOOL)_needInvalidateRendererForBoundsSize:(CGSize)boundsSize diff --git a/AsyncDisplayKit/TextKit/ASTextKitAttributes.h b/AsyncDisplayKit/TextKit/ASTextKitAttributes.h index 44835eee6d..d7bf44fef2 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitAttributes.h +++ b/AsyncDisplayKit/TextKit/ASTextKitAttributes.h @@ -89,6 +89,11 @@ struct ASTextKitAttributes { A pointer to a function that that returns a custom layout manager subclass. If nil, defaults to NSLayoutManager. */ NSLayoutManager *(*layoutManagerFactory)(void); + + /** + An optional delegate for the NSLayoutManager + */ + id layoutManagerDelegate; /** We provide an explicit copy function so we can use aggregate initializer syntax while providing copy semantics for diff --git a/AsyncDisplayKit/TextKit/ASTextKitContext.h b/AsyncDisplayKit/TextKit/ASTextKitContext.h index d9e6642f61..9fe6ab6af1 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitContext.h +++ b/AsyncDisplayKit/TextKit/ASTextKitContext.h @@ -28,7 +28,8 @@ maximumNumberOfLines:(NSUInteger)maximumNumberOfLines exclusionPaths:(NSArray *)exclusionPaths constrainedSize:(CGSize)constrainedSize - layoutManagerFactory:(NSLayoutManager*(*)(void))layoutManagerFactory; + layoutManagerFactory:(NSLayoutManager*(*)(void))layoutManagerFactory + layoutManagerDelegate:(id)layoutManagerDelegate; @property (nonatomic, assign, readwrite) CGSize constrainedSize; diff --git a/AsyncDisplayKit/TextKit/ASTextKitContext.mm b/AsyncDisplayKit/TextKit/ASTextKitContext.mm index a998bc2a2f..42d41af217 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitContext.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitContext.mm @@ -30,6 +30,7 @@ exclusionPaths:(NSArray *)exclusionPaths constrainedSize:(CGSize)constrainedSize layoutManagerFactory:(NSLayoutManager*(*)(void))layoutManagerFactory + layoutManagerDelegate:(id)layoutManagerDelegate { if (self = [super init]) { // Concurrently initialising TextKit components crashes (rdar://18448377) so we use a global lock. @@ -39,6 +40,7 @@ _textStorage = (attributedString ? [[NSTextStorage alloc] initWithAttributedString:attributedString] : [[NSTextStorage alloc] init]); _layoutManager = layoutManagerFactory ? layoutManagerFactory() : [[ASLayoutManager alloc] init]; _layoutManager.usesFontLeading = NO; + _layoutManager.delegate = layoutManagerDelegate; [_textStorage addLayoutManager:_layoutManager]; _textContainer = [[NSTextContainer alloc] initWithSize:constrainedSize]; // We want the text laid out up to the very edges of the container. diff --git a/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm b/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm index 5c0c144452..aa6b94d525 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm @@ -101,7 +101,8 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() maximumNumberOfLines:attributes.maximumNumberOfLines exclusionPaths:attributes.exclusionPaths constrainedSize:shadowConstrainedSize - layoutManagerFactory:attributes.layoutManagerFactory]; + layoutManagerFactory:attributes.layoutManagerFactory + layoutManagerDelegate:attributes.layoutManagerDelegate]; [self truncater]; } diff --git a/AsyncDisplayKit/TextKit/ASTextKitTailTruncater.mm b/AsyncDisplayKit/TextKit/ASTextKitTailTruncater.mm index 7617bfe82e..0c9870b3ce 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitTailTruncater.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitTailTruncater.mm @@ -72,8 +72,8 @@ maximumNumberOfLines:1 exclusionPaths:nil constrainedSize:constrainedRect.size - layoutManagerFactory:nil]; - + layoutManagerFactory:nil + layoutManagerDelegate:nil]; __block CGRect truncationUsedRect; [truncationContext performBlockWithLockedTextKitComponents:^(NSLayoutManager *truncationLayoutManager, NSTextStorage *truncationTextStorage, NSTextContainer *truncationTextContainer) { diff --git a/AsyncDisplayKitTests/ASTextKitTruncationTests.mm b/AsyncDisplayKitTests/ASTextKitTruncationTests.mm index fc7e47f31f..2bd0ba330a 100644 --- a/AsyncDisplayKitTests/ASTextKitTruncationTests.mm +++ b/AsyncDisplayKitTests/ASTextKitTruncationTests.mm @@ -42,7 +42,8 @@ maximumNumberOfLines:0 exclusionPaths:nil constrainedSize:constrainedSize - layoutManagerFactory:nil]; + layoutManagerFactory:nil + layoutManagerDelegate:nil]; __block NSRange textKitVisibleRange; [context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { textKitVisibleRange = [layoutManager characterRangeForGlyphRange:[layoutManager glyphRangeForTextContainer:textContainer] @@ -63,7 +64,8 @@ maximumNumberOfLines:0 exclusionPaths:nil constrainedSize:constrainedSize - layoutManagerFactory:nil]; + layoutManagerFactory:nil + layoutManagerDelegate:nil]; ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context truncationAttributedString:[self _simpleTruncationAttributedString] avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@""]]; @@ -85,7 +87,8 @@ maximumNumberOfLines:0 exclusionPaths:nil constrainedSize:constrainedSize - layoutManagerFactory:nil]; + layoutManagerFactory:nil + layoutManagerDelegate:nil]; ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context truncationAttributedString:[self _simpleTruncationAttributedString] avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]]; @@ -108,7 +111,8 @@ maximumNumberOfLines:0 exclusionPaths:nil constrainedSize:constrainedSize - layoutManagerFactory:nil]; + layoutManagerFactory:nil + layoutManagerDelegate:nil]; ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context truncationAttributedString:[self _simpleTruncationAttributedString] avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]]; @@ -132,7 +136,9 @@ maximumNumberOfLines:0 exclusionPaths:nil constrainedSize:constrainedSize - layoutManagerFactory:nil]; + layoutManagerFactory:nil + layoutManagerDelegate:nil]; + XCTAssertNoThrow([[ASTextKitTailTruncater alloc] initWithContext:context truncationAttributedString:[self _simpleTruncationAttributedString] avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]]); diff --git a/examples/Kittens/Sample/KittenNode.mm b/examples/Kittens/Sample/KittenNode.mm index 5c28fc440c..347460df50 100644 --- a/examples/Kittens/Sample/KittenNode.mm +++ b/examples/Kittens/Sample/KittenNode.mm @@ -16,6 +16,7 @@ #import #import +#import static const CGFloat kImageSize = 80.0f; static const CGFloat kOuterPadding = 16.0f; @@ -128,7 +129,8 @@ static const CGFloat kInnerPadding = 10.0f; style.hyphenationFactor = 1.0; return @{ NSFontAttributeName: font, - NSParagraphStyleAttributeName: style }; + NSParagraphStyleAttributeName: style, + ASTextNodeWordKerningAttributeName : @.5}; } #if UseAutomaticLayout