diff --git a/AsyncDisplayKit/ASTextNode+Beta.h b/AsyncDisplayKit/ASTextNode+Beta.h index 8c9bd0286c..38059aa7c3 100644 --- a/AsyncDisplayKit/ASTextNode+Beta.h +++ b/AsyncDisplayKit/ASTextNode+Beta.h @@ -10,9 +10,9 @@ @interface ASTextNode () /** - @abstract The minimum scale that the textnode can apply to fit long words. - @default 0 (No scaling) + @abstract An array of descending scale factors that will be applied to this text node to try to make it fit within its constrained size + @default nil (no scaling) */ -@property (nonatomic, assign) CGFloat minimumScaleFactor; +@property (nonatomic, copy) NSArray *pointSizeScaleFactors; @end \ No newline at end of file diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index a2b9f3121a..a6c17e5bf0 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -18,6 +18,7 @@ #import "ASTextKitCoreTextAdditions.h" #import "ASTextKitHelpers.h" +#import "ASTextKitFontSizeAdjuster.h" #import "ASTextKitRenderer.h" #import "ASTextKitRenderer+Positioning.h" #import "ASTextKitShadower.h" @@ -78,6 +79,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation CGSize _constrainedSize; ASTextKitRenderer *_renderer; + CGFloat _currentScaleFactor; UILongPressGestureRecognizer *_longPressGestureRecognizer; } @@ -242,7 +244,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; .lineBreakMode = _truncationMode, .maximumNumberOfLines = _maximumNumberOfLines, .exclusionPaths = _exclusionPaths, - .minimumScaleFactor = _minimumScaleFactor, + .pointSizeScaleFactors = _pointSizeScaleFactors, + .currentScaleFactor = _currentScaleFactor, }; } @@ -256,6 +259,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; // actually dealloc. __block ASTextKitRenderer *renderer = _renderer; ASPerformBlockOnBackgroundThread(^{ + // before we remove the renderer, take its scale factor so we set it when a new renderer is created + _currentScaleFactor = renderer.currentScaleFactor; renderer = nil; }); _renderer = nil; @@ -1059,16 +1064,15 @@ static NSAttributedString *DefaultTruncationAttributedString() return visibleRange.length < _attributedString.length; } -- (void)setMinimumScaleFactor:(CGFloat)minimumScaleFactor +- (void)setPointSizeScaleFactors:(NSArray *)pointSizeScaleFactors { - if (_minimumScaleFactor != minimumScaleFactor) { - _minimumScaleFactor = minimumScaleFactor; + if ([_pointSizeScaleFactors isEqualToArray:pointSizeScaleFactors] == NO) { + _pointSizeScaleFactors = pointSizeScaleFactors; [self _invalidateRenderer]; ASDisplayNodeRespectThreadAffinityOfNode(self, ^{ [self setNeedsDisplay]; }); - } -} + }} - (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines { diff --git a/AsyncDisplayKit/TextKit/ASTextKitAttributes.h b/AsyncDisplayKit/TextKit/ASTextKitAttributes.h index d7bf44fef2..4d2160ea33 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitAttributes.h +++ b/AsyncDisplayKit/TextKit/ASTextKitAttributes.h @@ -58,6 +58,7 @@ struct ASTextKitAttributes { NSLineBreakMode lineBreakMode; /** The maximum number of lines to draw in the drawable region. Leave blank or set to 0 to define no maximum. + This is required to apply scale factors to shrink text to fit within a number of lines */ NSUInteger maximumNumberOfLines; /** @@ -82,9 +83,13 @@ struct ASTextKitAttributes { */ CGFloat shadowRadius; /** - The minimum scale that the textnode can apply to fit long words in constrained size. + An array of scale factors in descending order to apply to the text to try to make it fit into a constrained size. */ - CGFloat minimumScaleFactor; + NSArray *pointSizeScaleFactors; + /** + The currently applied scale factor. Only valid if pointSizeScaleFactors are provided. Defaults to 0 (no scaling) + */ + CGFloat currentScaleFactor; /** A pointer to a function that that returns a custom layout manager subclass. If nil, defaults to NSLayoutManager. */ @@ -112,8 +117,10 @@ struct ASTextKitAttributes { [shadowColor copy], shadowOpacity, shadowRadius, - minimumScaleFactor, - layoutManagerFactory + pointSizeScaleFactors, + currentScaleFactor, + layoutManagerFactory, + layoutManagerDelegate, }; }; @@ -124,7 +131,8 @@ struct ASTextKitAttributes { && maximumNumberOfLines == other.maximumNumberOfLines && shadowOpacity == other.shadowOpacity && shadowRadius == other.shadowRadius - && minimumScaleFactor == other.minimumScaleFactor + && [pointSizeScaleFactors isEqualToArray:other.pointSizeScaleFactors] + && currentScaleFactor == currentScaleFactor && layoutManagerFactory == other.layoutManagerFactory && CGSizeEqualToSize(shadowOffset, other.shadowOffset) && _objectsEqual(exclusionPaths, other.exclusionPaths) diff --git a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.h b/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.h index 84ba5fa367..cd90726dd0 100644 --- a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.h +++ b/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.h @@ -1,20 +1,46 @@ -// -// ASTextKitFontSizeAdjuster.h -// AsyncDisplayKit -// -// Created by Luke on 1/20/16. -// Copyright © 2016 Facebook. All rights reserved. -// +/* 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 +#import "ASTextKitAttributes.h" +#import "ASTextKitContext.h" @interface ASTextKitFontSizeAdjuster : NSObject @property (nonatomic, assign) CGSize constrainedSize; +/** + * Creates a class that will return a scale factor the will make a string fit inside the constrained size. + * + * "Fitting" means that both the longest word in the string will fit without breaking in the constrained + * size's width AND that the entire string will try to fit within attribute's maximumLineCount. The amount + * that the string will scale is based upon the attribute's pointSizeScaleFactors. If the string cannot fit + * in the given width/number of lines, the smallest scale factor will be returned. + * + * @param context The text kit context + * @param constrainedSize The constrained size to render into + * @param textComponentAttributes The renderer's text attributes + */ - (instancetype)initWithContext:(ASTextKitContext *)context - minimumScaleFactor:(CGFloat)minimumScaleFactor - constrainedSize:(CGSize)constrainedSize; + constrainedSize:(CGSize)constrainedSize + textKitAttributes:(const ASTextKitAttributes &)textComponentAttributes; + +/** + * Returns the best fit scale factor for the text + */ +- (CGFloat)scaleFactor; + +/** + * Takes all of the attributed string attributes dealing with size (font size, line spacing, kerning, etc) and + * scales them by the scaleFactor. I wouldn't be surprised if I missed some in here. + */ ++ (void)adjustFontSizeForAttributeString:(NSMutableAttributedString *)attrString withScaleFactor:(CGFloat)scaleFactor; -- (void) adjustFontSize; @end + + diff --git a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.m b/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.m deleted file mode 100644 index 731c19c208..0000000000 --- a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.m +++ /dev/null @@ -1,98 +0,0 @@ -// -// ASTextKitFontSizeAdjuster.m -// AsyncDisplayKit -// -// Created by Luke on 1/20/16. -// Copyright © 2016 Facebook. All rights reserved. -// - -#import "ASTextKitContext.h" -#import "ASTextKitFontSizeAdjuster.h" - -@implementation ASTextKitFontSizeAdjuster -{ - __weak ASTextKitContext *_context; - CGFloat _minimumScaleFactor; -} - -- (instancetype)initWithContext:(ASTextKitContext *)context - minimumScaleFactor:(CGFloat)minimumScaleFactor - constrainedSize:(CGSize)constrainedSize -{ - if (self = [super init]) { - _context = context; - _minimumScaleFactor = minimumScaleFactor; - _constrainedSize = constrainedSize; - } - return self; -} - -- (CGSize)sizeForAttributedString:(NSAttributedString *)attrString -{ - return [attrString boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) - options:NSStringDrawingUsesLineFragmentOrigin - context:nil].size; -} - - -- (void) adjustFontSizeForAttributeString:(NSMutableAttributedString *)attrString withScaleFactor:(CGFloat)scaleFactor -{ - { - [attrString beginEditing]; - - [attrString enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, attrString.length) options:0 usingBlock:^(id value, NSRange range, BOOL *stop) { - - UIFont* font = value; - font = [font fontWithSize:font.pointSize * scaleFactor]; - - [attrString removeAttribute:NSFontAttributeName range:range]; - [attrString addAttribute:NSFontAttributeName value:font range:range]; - }]; - - [attrString endEditing]; - } -} - - -- (void)adjustFontSize -{ - if (_minimumScaleFactor <= 0 || _minimumScaleFactor >= 1) { - return; - } - [_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { - NSString *str = textStorage.string; - NSArray *words = [str componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - NSString *longestWordNeedingResize = @""; - for (NSString *word in words) { - if ([word length] > [longestWordNeedingResize length]) { - longestWordNeedingResize = word; - } - } - - if ([longestWordNeedingResize length] == 0) { - return; - } - - NSRange range = [str rangeOfString:longestWordNeedingResize]; - NSMutableAttributedString *attrString = [textStorage attributedSubstringFromRange:range].mutableCopy; - CGSize defaultSize = [self sizeForAttributedString:attrString]; - - if (defaultSize.width > _constrainedSize.width) { - [attrString removeAttribute:NSParagraphStyleAttributeName range:NSMakeRange(0, [attrString length])]; - NSStringDrawingContext *context = [[NSStringDrawingContext alloc] init]; - context.minimumScaleFactor = _minimumScaleFactor; - [attrString boundingRectWithSize:CGSizeMake(_constrainedSize.width, defaultSize.height) - options:NSStringDrawingUsesLineFragmentOrigin - context:context]; - - [self adjustFontSizeForAttributeString:attrString withScaleFactor:context.actualScaleFactor]; - - if ([self sizeForAttributedString:attrString].width <= _constrainedSize.width) { - [self adjustFontSizeForAttributeString:textStorage withScaleFactor:context.actualScaleFactor]; - NSLog(@"ASTextKitFontSizeAdjuster : adjusted \"%@\"to fontsize actualScaleFactor:%f", longestWordNeedingResize, context.actualScaleFactor); - } - } - }]; -} - -@end diff --git a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm b/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm new file mode 100644 index 0000000000..4f92c607aa --- /dev/null +++ b/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm @@ -0,0 +1,185 @@ +/* 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 "ASTextKitContext.h" +#import "ASTextKitFontSizeAdjuster.h" +#import "ASLayoutManager.h" + +#import + +//#define LOG(...) NSLog(__VA_ARGS__) +#define LOG(...) + +@implementation ASTextKitFontSizeAdjuster +{ + __weak ASTextKitContext *_context; + ASTextKitAttributes _attributes; + std::mutex _textKitMutex; +} + +- (instancetype)initWithContext:(ASTextKitContext *)context + constrainedSize:(CGSize)constrainedSize + textKitAttributes:(const ASTextKitAttributes &)textComponentAttributes; +{ + if (self = [super init]) { + _context = context; + _constrainedSize = constrainedSize; + _attributes = textComponentAttributes; + } + return self; +} + ++ (void)adjustFontSizeForAttributeString:(NSMutableAttributedString *)attrString withScaleFactor:(CGFloat)scaleFactor +{ + [attrString beginEditing]; + + // scale all the attributes that will change the bounding box + [attrString enumerateAttributesInRange:NSMakeRange(0, attrString.length) options:0 usingBlock:^(NSDictionary * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) { + if (attrs[NSFontAttributeName] != nil) { + UIFont *font = attrs[NSFontAttributeName]; + font = [font fontWithSize:roundf(font.pointSize * scaleFactor)]; + [attrString removeAttribute:NSFontAttributeName range:range]; + [attrString addAttribute:NSFontAttributeName value:font range:range]; + } + + if (attrs[NSKernAttributeName] != nil) { + NSNumber *kerning = attrs[NSKernAttributeName]; + [attrString removeAttribute:NSKernAttributeName range:range]; + [attrString addAttribute:NSKernAttributeName value:@([kerning floatValue] * scaleFactor) range:range]; + } + + if (attrs[NSParagraphStyleAttributeName] != nil) { + NSMutableParagraphStyle *paragraphStyle = [attrs[NSParagraphStyleAttributeName] mutableCopy]; + paragraphStyle.lineSpacing = (paragraphStyle.lineSpacing * scaleFactor); + paragraphStyle.paragraphSpacing = (paragraphStyle.paragraphSpacing * scaleFactor); + paragraphStyle.firstLineHeadIndent = (paragraphStyle.firstLineHeadIndent * scaleFactor); + paragraphStyle.headIndent = (paragraphStyle.headIndent * scaleFactor); + paragraphStyle.tailIndent = (paragraphStyle.tailIndent * scaleFactor); + paragraphStyle.minimumLineHeight = (paragraphStyle.minimumLineHeight * scaleFactor); + paragraphStyle.maximumLineHeight = (paragraphStyle.maximumLineHeight * scaleFactor); + paragraphStyle.lineHeightMultiple = (paragraphStyle.lineHeightMultiple * scaleFactor); + paragraphStyle.paragraphSpacing = (paragraphStyle.paragraphSpacing * scaleFactor); + + [attrString removeAttribute:NSParagraphStyleAttributeName range:range]; + [attrString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range]; + } + + }]; + + [attrString endEditing]; +} + +- (NSUInteger)lineCountForString:(NSAttributedString *)attributedString +{ + NSUInteger lineCount = 0; + + static std::mutex __static_mutex; + std::lock_guard l(__static_mutex); + + NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString]; + NSLayoutManager *layoutManager = _attributes.layoutManagerFactory ? _attributes.layoutManagerFactory() : [[ASLayoutManager alloc] init]; + layoutManager.usesFontLeading = NO; + [textStorage addLayoutManager:layoutManager]; + NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:_constrainedSize]; + + 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]; + + NSRange lineRange = { 0, 0 }; + while (NSMaxRange(lineRange) < [layoutManager numberOfGlyphs]/* && lineCount <= _attributes.maximumNumberOfLines*/) { + [layoutManager lineFragmentRectForGlyphAtIndex:NSMaxRange(lineRange) effectiveRange:&lineRange]; + lineCount++; + } + + return lineCount; +} + +- (CGFloat)scaleFactor +{ + if ([_attributes.pointSizeScaleFactors count] == 0 || isinf(_constrainedSize.width)) { + return 1.0; + } + + __block CGFloat adjustedScale = 1.0; + + NSArray *scaleFactors = _attributes.pointSizeScaleFactors; + [_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { + + // Check for two different situations (and correct for both) + // 1. The longest word in the string fits without being wrapped + // 2. The entire text fits in the given constrained size. + + NSString *str = textStorage.string; + NSArray *words = [str componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + NSString *longestWordNeedingResize = @""; + for (NSString *word in words) { + if ([word length] > [longestWordNeedingResize length]) { + longestWordNeedingResize = word; + } + } + + NSUInteger scaleIndex = 0; + + // find the longest word and make sure it fits in the constrained width + if ([longestWordNeedingResize length] > 0) { + + NSRange longestWordRange = [str rangeOfString:longestWordNeedingResize]; + NSMutableAttributedString *attrString = [textStorage attributedSubstringFromRange:longestWordRange].mutableCopy; + CGSize longestWordSize = [attrString boundingRectWithSize:CGSizeMake(FLT_MAX, FLT_MAX) options:NSStringDrawingUsesLineFragmentOrigin context:nil].size; + + // check if the longest word is larger than our constrained width + if (longestWordSize.width > _constrainedSize.width) { + + // we have a word that is too long. Loop through our scale factors until we fit + for (NSNumber *scaleFactor in scaleFactors) { + // even if we still don't fit, save this scaleFactor so more of the word will fit + adjustedScale = [scaleFactor floatValue]; + + // adjust here so we start at the proper place in our scale array if we have too many lines + scaleIndex++; + + if (ceilf(longestWordSize.width * [scaleFactor floatValue]) <= _constrainedSize.width) { + // we fit! we are done + break; + } + } + } + } + + if (_attributes.maximumNumberOfLines > 0) { + // get the number of lines in our possibly scaled string + NSUInteger numberOfLines = [self lineCountForString:textStorage]; + if (numberOfLines > _attributes.maximumNumberOfLines) { + + for (NSUInteger index = scaleIndex; index < scaleFactors.count; index++) { + NSMutableAttributedString *entireAttributedString = [[NSMutableAttributedString alloc] initWithAttributedString:textStorage]; + [[self class] adjustFontSizeForAttributeString:entireAttributedString withScaleFactor:[scaleFactors[index] floatValue]]; + + + // save away this scale factor. Even if we don't fit completely we should still scale down + adjustedScale = [scaleFactors[index] floatValue]; + + if ([self lineCountForString:entireAttributedString] <= _attributes.maximumNumberOfLines) { + // we fit! we are done + break; + } + } + + } + } + + }]; + return adjustedScale; +} + +@end diff --git a/AsyncDisplayKit/TextKit/ASTextKitRenderer.h b/AsyncDisplayKit/TextKit/ASTextKitRenderer.h index 75fc5e03a9..1889131f52 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitRenderer.h +++ b/AsyncDisplayKit/TextKit/ASTextKitRenderer.h @@ -55,6 +55,8 @@ @property (nonatomic, assign, readwrite) CGSize constrainedSize; +@property (nonatomic, assign, readonly) CGFloat currentScaleFactor; + #pragma mark - Drawing /* Draw the renderer's text content into the bounds provided. diff --git a/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm b/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm index 05996c4de3..825ce2e26e 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm @@ -49,6 +49,9 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() _constrainedSize = constrainedSize; _attributes = attributes; _sizeIsCalculated = NO; + if ([attributes.pointSizeScaleFactors count] > 0) { + _currentScaleFactor = attributes.currentScaleFactor; + } } return self; } @@ -84,8 +87,8 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() // We must inset the constrained size by the size of the shadower. CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize]; _fontSizeAdjuster = [[ASTextKitFontSizeAdjuster alloc] initWithContext:[self context] - minimumScaleFactor:attributes.minimumScaleFactor - constrainedSize:shadowConstrainedSize]; + constrainedSize:shadowConstrainedSize + textKitAttributes:attributes]; } return _fontSizeAdjuster; } @@ -137,8 +140,8 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() - (void)_calculateSize { [self truncater]; - if (_attributes.minimumScaleFactor < 1 && _attributes.minimumScaleFactor > 0) { - [[self fontSizeAdjuster] adjustFontSize]; + if ([_attributes.pointSizeScaleFactors count] > 0) { + _currentScaleFactor = [[self fontSizeAdjuster] scaleFactor]; } // Force glyph generation and layout, which may not have happened yet (and isn't triggered by @@ -156,8 +159,12 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() // 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}); - - _calculatedSize = [_shadower outsetSizeWithInsetSize:boundingRect.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); + } } #pragma mark - Drawing @@ -176,11 +183,32 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() LOG(@"%@, shadowInsetBounds = %@",self, NSStringFromCGRect(shadowInsetBounds)); [[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { + + NSTextStorage *scaledTextStorage = nil; + BOOL isScaled = (self.currentScaleFactor > 0 && self.currentScaleFactor < 1.0); + + if (isScaled) { + // if we are going to scale the text, swap out the non-scaled text for the scaled version. + NSMutableAttributedString *scaledString = [[NSMutableAttributedString alloc] initWithAttributedString:textStorage]; + [ASTextKitFontSizeAdjuster adjustFontSizeForAttributeString:scaledString withScaleFactor:_currentScaleFactor]; + scaledTextStorage = [[NSTextStorage alloc] initWithAttributedString:scaledString]; + + [textStorage removeLayoutManager:layoutManager]; + [scaledTextStorage addLayoutManager:layoutManager]; + } + LOG(@"usedRect: %@", NSStringFromCGRect([layoutManager usedRectForTextContainer:textContainer])); - NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; + NSRange glyphRange = [layoutManager glyphRangeForBoundingRect:CGRectMake(0,0,textContainer.size.width, textContainer.size.height) inTextContainer:textContainer]; LOG(@"boundingRect: %@", NSStringFromCGRect([layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer])); + [layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin]; [layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin]; + + if (isScaled) { + // put the non-scaled version back + [scaledTextStorage removeLayoutManager:layoutManager]; + [textStorage addLayoutManager:layoutManager]; + } }]; UIGraphicsPopContext();