Skip to content

Commit

Permalink
[ASTextKitFontSizeAdjuster] Replace use of boundingRectWithSize:optio…
Browse files Browse the repository at this point in the history
…ns:context: with boundingRectForGlyphRange: inTextContainer: (#251)

* [ASTextKitFontSizeAdjuster] Replace use of boundingRectWithSize:options:context: with boundingRectForGlyphRange: inTextContainer:

`boundingRectWithSize:options:context:`  started returning different values for the same strings between iOS 10.2 and iOS 10.3. Switching to using `NSLayoutManager`’s `boundingRectForGlyphRange: inTextContainer:` fixed this. It also makes sure we are consistent with what `ASTextKitTailTruncater` uses.

* updated changelog
  • Loading branch information
rcancro authored and Adlai-Holler committed May 11, 2017
1 parent 3439723 commit 002f470
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 34 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## master

* Add your own contributions to the next release on the line below this with your name.
- [ASTextKitFontSizeAdjuster] [Ricky Cancro] Replace use of NSAttributedString's boundingRectWithSize:options:context: with NSLayoutManager's boundingRectForGlyphRange:inTextContainer:
- Add support for IGListKit post-removal-of-IGListSectionType, in preparation for IGListKit 3.0.0 release. [Adlai Holler](https://github.com/Adlai-Holler) [#49](https://github.com/TextureGroup/Texture/pull/49)
- Fix `__has_include` check in ASLog.h [Philipp Smorygo]([email protected])
- Fix potential deadlock in ASControlNode [Garrett Moon](https://github.com/garrettmoon)
Expand Down
98 changes: 64 additions & 34 deletions Source/TextKit/ASTextKitFontSizeAdjuster.mm
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,30 @@
#import <tgmath.h>
#import <mutex>

#import <AsyncDisplayKit/ASTextKitContext.h>
#import <AsyncDisplayKit/ASLayoutManager.h>
#import <AsyncDisplayKit/ASTextKitContext.h>
#import <AsyncDisplayKit/ASThread.h>

//#define LOG(...) NSLog(__VA_ARGS__)
#define LOG(...)

@interface ASTextKitFontSizeAdjuster()
@property (nonatomic, strong, readonly) NSLayoutManager *sizingLayoutManager;
@property (nonatomic, strong, readonly) NSTextContainer *sizingTextContainer;
@end

@implementation ASTextKitFontSizeAdjuster
{
__weak ASTextKitContext *_context;
ASTextKitAttributes _attributes;
std::mutex _textKitMutex;
BOOL _measured;
CGFloat _scaleFactor;
NSLayoutManager *_sizingLayoutManager;
NSTextContainer *_sizingTextContainer;
ASDN::Mutex __instanceLock__;
}

@synthesize sizingLayoutManager = _sizingLayoutManager;
@synthesize sizingTextContainer = _sizingTextContainer;

- (instancetype)initWithContext:(ASTextKitContext *)context
constrainedSize:(CGSize)constrainedSize
textKitAttributes:(const ASTextKitAttributes &)textComponentAttributes;
Expand Down Expand Up @@ -94,38 +101,61 @@ + (void)adjustFontSizeForAttributeString:(NSMutableAttributedString *)attrString

- (NSUInteger)lineCountForString:(NSAttributedString *)attributedString
{
NSUInteger lineCount = 0;

static std::mutex __static_mutex;
std::lock_guard<std::mutex> l(__static_mutex);
NSUInteger lineCount = 0;

NSLayoutManager *sizingLayoutManager = [self sizingLayoutManager];
NSTextContainer *sizingTextContainer = [self sizingTextContainer];

NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString];
[textStorage addLayoutManager:sizingLayoutManager];

[sizingLayoutManager ensureLayoutForTextContainer:sizingTextContainer];
for (NSRange lineRange = { 0, 0 }; NSMaxRange(lineRange) < [sizingLayoutManager numberOfGlyphs] && lineCount <= _attributes.maximumNumberOfLines; lineCount++) {
[sizingLayoutManager lineFragmentRectForGlyphAtIndex:NSMaxRange(lineRange) effectiveRange:&lineRange];
}

[textStorage removeLayoutManager:sizingLayoutManager];
return lineCount;
}

- (CGSize)boundingBoxForString:(NSAttributedString *)attributedString
{
NSLayoutManager *sizingLayoutManager = [self sizingLayoutManager];
NSTextContainer *sizingTextContainer = [self sizingTextContainer];

NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString];
[textStorage addLayoutManager:sizingLayoutManager];

[sizingLayoutManager ensureLayoutForTextContainer:sizingTextContainer];
CGRect textRect = [sizingLayoutManager boundingRectForGlyphRange:NSMakeRange(0, [textStorage length])
inTextContainer:sizingTextContainer];
[textStorage removeLayoutManager:sizingLayoutManager];
return textRect.size;
}

- (NSLayoutManager *)sizingLayoutManager
{
ASDN::MutexLocker l(__instanceLock__);
if (_sizingLayoutManager == nil) {
_sizingLayoutManager = [[ASLayoutManager alloc] init];
_sizingLayoutManager.usesFontLeading = NO;

NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString];
if (_sizingLayoutManager == nil) {
_sizingLayoutManager = [[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, CGFLOAT_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];
// 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, CGFLOAT_MAX)];
_sizingTextContainer.lineFragmentPadding = 0;

// use 0 regardless of what is in the attributes so that we get an accurate line count
_sizingTextContainer.maximumNumberOfLines = 0;

_sizingTextContainer.lineBreakMode = _attributes.lineBreakMode;
_sizingTextContainer.exclusionPaths = _attributes.exclusionPaths;
}

[textStorage removeLayoutManager:_sizingLayoutManager];
return lineCount;
[_sizingLayoutManager addTextContainer:_sizingTextContainer];
}

return _sizingLayoutManager;
}

- (CGFloat)scaleFactor
Expand Down Expand Up @@ -202,7 +232,7 @@ - (CGFloat)scaleFactor
// if max lines still doesn't fit, continue without checking that we fit in the constrained height
if (maxLinesFits == YES && heightFits == NO) {
// max lines fit so make sure that we fit in the constrained height.
CGSize stringSize = [scaledString boundingRectWithSize:CGSizeMake(_constrainedSize.width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin context:nil].size;
CGSize stringSize = [self boundingBoxForString:scaledString];
heightFits = (stringSize.height <= _constrainedSize.height);
}
}
Expand Down

0 comments on commit 002f470

Please sign in to comment.