diff --git a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm b/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm index 5a2289582d..2ff6cf68d3 100644 --- a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm @@ -22,6 +22,8 @@ std::mutex _textKitMutex; BOOL _measured; CGFloat _scaleFactor; + NSLayoutManager *_sizingLayoutManager; + NSTextContainer *_sizingTextContainer; } - (instancetype)initWithContext:(ASTextKitContext *)context @@ -78,30 +80,38 @@ - (NSUInteger)lineCountForString:(NSAttributedString *)attributedString { - NSUInteger lineCount = 0; - - static std::mutex __static_mutex; - std::lock_guard l(__static_mutex); - - NSTextStorage *textStorage = _attributes.textStorageCreationBlock ? _attributes.textStorageCreationBlock(attributedString) : [[NSTextStorage alloc] initWithAttributedString:attributedString]; - NSLayoutManager *layoutManager = _attributes.layoutManagerCreationBlock ? _attributes.layoutManagerCreationBlock() : [[ASLayoutManager alloc] init]; - layoutManager.usesFontLeading = NO; - [textStorage addLayoutManager:layoutManager]; - NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeMake(_constrainedSize.width, FLT_MAX)]; - - textContainer.lineFragmentPadding = 0; - textContainer.lineBreakMode = _attributes.lineBreakMode; - - // use 0 regardless of what is in the attributes so that we get an accurate line count - textContainer.maximumNumberOfLines = 0; - textContainer.exclusionPaths = _attributes.exclusionPaths; - [layoutManager addTextContainer:textContainer]; - - for (NSRange lineRange = { 0, 0 }; NSMaxRange(lineRange) < [layoutManager numberOfGlyphs] && lineCount <= _attributes.maximumNumberOfLines; lineCount++) { - [layoutManager lineFragmentRectForGlyphAtIndex:NSMaxRange(lineRange) effectiveRange:&lineRange]; - } - - return lineCount; + NSUInteger lineCount = 0; + + static std::mutex __static_mutex; + std::lock_guard l(__static_mutex); + + NSTextStorage *textStorage = _attributes.textStorageCreationBlock ? _attributes.textStorageCreationBlock(attributedString) : [[NSTextStorage alloc] initWithAttributedString:attributedString]; + if (_sizingLayoutManager == nil) { + _sizingLayoutManager = _attributes.layoutManagerCreationBlock ? _attributes.layoutManagerCreationBlock() : [[ASLayoutManager alloc] init]; + _sizingLayoutManager.usesFontLeading = NO; + } + [textStorage addLayoutManager:_sizingLayoutManager]; + if (_sizingTextContainer == nil) { + // make this text container unbounded in height so that the layout manager will compute the total + // number of lines and not stop counting when height runs out. + _sizingTextContainer = [[NSTextContainer alloc] initWithSize:CGSizeMake(_constrainedSize.width, FLT_MAX)]; + _sizingTextContainer.lineFragmentPadding = 0; + + // use 0 regardless of what is in the attributes so that we get an accurate line count + _sizingTextContainer.maximumNumberOfLines = 0; + [_sizingLayoutManager addTextContainer:_sizingTextContainer]; + } + + _sizingTextContainer.lineBreakMode = _attributes.lineBreakMode; + _sizingTextContainer.exclusionPaths = _attributes.exclusionPaths; + + + for (NSRange lineRange = { 0, 0 }; NSMaxRange(lineRange) < [_sizingLayoutManager numberOfGlyphs] && lineCount <= _attributes.maximumNumberOfLines; lineCount++) { + [_sizingLayoutManager lineFragmentRectForGlyphAtIndex:NSMaxRange(lineRange) effectiveRange:&lineRange]; + } + + [textStorage removeLayoutManager:_sizingLayoutManager]; + return lineCount; } - (CGFloat)scaleFactor diff --git a/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm b/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm index 0fc6f5c5b3..84f777e3cd 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm @@ -142,28 +142,44 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() if (isinf(_constrainedSize.width) == NO && [_attributes.pointSizeScaleFactors count] > 0) { _currentScaleFactor = [[self fontSizeAdjuster] scaleFactor]; } - + // Force glyph generation and layout, which may not have happened yet (and isn't triggered by // -usedRectForTextContainer:). + __block NSTextStorage *scaledTextStorage = nil; + BOOL isScaled = [self isScaled]; [[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { + if (isScaled) { + NSMutableAttributedString *scaledString = [[NSMutableAttributedString alloc] initWithAttributedString:textStorage]; + [ASTextKitFontSizeAdjuster adjustFontSizeForAttributeString:scaledString withScaleFactor:_currentScaleFactor]; + scaledTextStorage = [[NSTextStorage alloc] initWithAttributedString:scaledString]; + + [textStorage removeLayoutManager:layoutManager]; + [scaledTextStorage addLayoutManager:layoutManager]; + } [layoutManager ensureLayoutForTextContainer:textContainer]; }]; - + CGRect constrainedRect = {CGPointZero, _constrainedSize}; __block CGRect boundingRect; [[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { boundingRect = [layoutManager usedRectForTextContainer:textContainer]; + if (isScaled) { + // put the non-scaled version back + [scaledTextStorage removeLayoutManager:layoutManager]; + [textStorage addLayoutManager:layoutManager]; + } }]; - + // TextKit often returns incorrect glyph bounding rects in the horizontal direction, so we clip to our bounding rect // to make sure our width calculations aren't being offset by glyphs going beyond the constrained rect. boundingRect = CGRectIntersection(boundingRect, {.size = constrainedRect.size}); CGSize boundingSize = [_shadower outsetSizeWithInsetSize:boundingRect.size]; _calculatedSize = CGSizeMake(boundingSize.width, boundingSize.height); - - if (_currentScaleFactor > 0.0 && _currentScaleFactor < 1.0) { - _calculatedSize.height = ceilf(_calculatedSize.height * _currentScaleFactor); - } +} + +- (BOOL)isScaled +{ + return (self.currentScaleFactor > 0 && self.currentScaleFactor < 1.0); } #pragma mark - Drawing @@ -189,7 +205,7 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() [[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { NSTextStorage *scaledTextStorage = nil; - BOOL isScaled = (self.currentScaleFactor > 0 && self.currentScaleFactor < 1.0); + BOOL isScaled = [self isScaled]; if (isScaled) { // if we are going to scale the text, swap out the non-scaled text for the scaled version.