From bcb1832fcd95bc3f78002891bbd4c7b9639f220c Mon Sep 17 00:00:00 2001 From: rcancro Date: Wed, 9 Mar 2016 15:27:33 -0800 Subject: [PATCH] Fixed some text shrinking issues Previously I was multiplying the calculated height of the text by the scale factor. This fails in the case where we have a long string that doesn't fit, but it is shrunk so much that it now has more than enough room in the renderer's constrained size. In this case we fail to update the calculated size's width. Also updated `lineCountForString` in `ASTextKitFontSizeAdjuster` to reuse a sizer layout manager and text container. --- .../TextKit/ASTextKitFontSizeAdjuster.mm | 58 +++++++++++-------- AsyncDisplayKit/TextKit/ASTextKitRenderer.mm | 32 +++++++--- 2 files changed, 58 insertions(+), 32 deletions(-) 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.