Cleanup ASTextNode and add locking

For now we use a big recursive lock. This needs to be revisited as we revisit the whole ASDK locking strategy.
This commit is contained in:
Michael Schneider 2016-06-15 16:53:15 -07:00
parent 22fa715682
commit 3d72a6b7e5

View File

@ -11,22 +11,19 @@
#import "ASTextNode.h"
#import "ASTextNode+Beta.h"
#include <mutex>
#import <AsyncDisplayKit/_ASDisplayLayer.h>
#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
#import <AsyncDisplayKit/ASHighlightOverlayLayer.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import "ASTextKitCoreTextAdditions.h"
#import "ASTextKitComponents.h"
#import "ASTextKitFontSizeAdjuster.h"
#import "ASTextKitRenderer.h"
#import "ASTextKitRenderer+Positioning.h"
#import "ASTextKitShadower.h"
#import "ASInternalHelpers.h"
#import "ASEqualityHelpers.h"
#import "ASLayout.h"
static const NSTimeInterval ASTextNodeHighlightFadeOutDuration = 0.15;
@ -38,15 +35,13 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
@interface ASTextNodeDrawParameters : NSObject
@property (nonatomic, assign, readonly) CGRect bounds;
@property (nonatomic, strong, readonly) UIColor *backgroundColor;
@end
@implementation ASTextNodeDrawParameters
- (instancetype)initWithBounds:(CGRect)bounds
backgroundColor:(UIColor *)backgroundColor
- (instancetype)initWithBounds:(CGRect)bounds backgroundColor:(UIColor *)backgroundColor
{
if (self = [super init]) {
_bounds = bounds;
@ -76,7 +71,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
NSRange _highlightRange;
ASHighlightOverlayLayer *_activeHighlightLayer;
ASDN::Mutex _rendererLock;
std::recursive_mutex _textLock;
CGSize _constrainedSize;
@ -160,6 +155,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
- (NSString *)description
{
std::lock_guard<std::recursive_mutex> l(_textLock);
NSString *plainString = [[_attributedText string] stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSString *truncationString = [_composedTruncationText string];
if (plainString.length > 50)
@ -227,7 +224,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
- (ASTextKitRenderer *)_rendererWithBounds:(CGRect)bounds
{
ASDN::MutexLocker l(_rendererLock);
std::lock_guard<std::recursive_mutex> l(_textLock);
if (_renderer == nil) {
CGSize constrainedSize = _constrainedSize.width != -INFINITY ? _constrainedSize : bounds.size;
_renderer = [[ASTextKitRenderer alloc] initWithTextKitAttributes:[self _rendererAttributes]
@ -250,9 +248,28 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
};
}
- (void)_invalidateRendererIfNeeded
{
[self _invalidateRendererIfNeededForBoundsSize:self.threadSafeBounds.size];
}
- (void)_invalidateRendererIfNeededForBoundsSize:(CGSize)boundsSize
{
if ([self _needInvalidateRendererForBoundsSize:boundsSize]) {
// Our bounds of frame have changed to a size that is not identical to our constraining size,
// so our previous layout information is invalid, and TextKit may draw at the
// incorrect origin.
{
std::lock_guard<std::recursive_mutex> l(_textLock);
_constrainedSize = CGSizeMake(-INFINITY, -INFINITY);
}
[self _invalidateRenderer];
}
}
- (void)_invalidateRenderer
{
ASDN::MutexLocker l(_rendererLock);
std::lock_guard<std::recursive_mutex> l(_textLock);
if (_renderer) {
// Destruction of the layout managers/containers/text storage is quite
@ -267,27 +284,13 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
}
}
- (void)_invalidateRendererIfNeeded
{
[self _invalidateRendererIfNeededForBoundsSize:self.threadSafeBounds.size];
}
- (void)_invalidateRendererIfNeededForBoundsSize:(CGSize)boundsSize
{
if ([self _needInvalidateRendererForBoundsSize:boundsSize]) {
// Our bounds of frame have changed to a size that is not identical to our constraining size,
// so our previous layout information is invalid, and TextKit may draw at the
// incorrect origin.
_constrainedSize = CGSizeMake(-INFINITY, -INFINITY);
[self _invalidateRenderer];
}
}
#pragma mark - Layout and Sizing
- (BOOL)_needInvalidateRendererForBoundsSize:(CGSize)boundsSize
{
if (!_renderer) {
std::lock_guard<std::recursive_mutex> l(_textLock);
if (_renderer == nil) {
return YES;
}
@ -322,6 +325,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
[super calculatedLayoutDidChange];
ASLayout *layout = self.calculatedLayout;
std::lock_guard<std::recursive_mutex> l(_textLock);
if (layout != nil) {
_constrainedSize = layout.size;
_renderer.constrainedSize = layout.size;
@ -333,6 +338,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
ASDisplayNodeAssert(constrainedSize.width >= 0, @"Constrained width for text (%f) is too narrow", constrainedSize.width);
ASDisplayNodeAssert(constrainedSize.height >= 0, @"Constrained height for text (%f) is too short", constrainedSize.height);
std::lock_guard<std::recursive_mutex> l(_textLock);
_constrainedSize = constrainedSize;
// Instead of invalidating the renderer, in case this is a new call with a different constrained size,
@ -341,7 +348,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
[self setNeedsDisplay];
CGSize size = [[self _renderer] size];
CGSize size = [self _renderer].size;
if (_attributedText.length > 0) {
CGFloat screenScale = ASScreenScale();
self.ascender = round([[_attributedText attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender] * screenScale)/screenScale;
@ -359,6 +366,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
- (void)setAttributedText:(NSAttributedString *)attributedText
{
std::lock_guard<std::recursive_mutex> l(_textLock);
if (attributedText == nil) {
attributedText = [[NSAttributedString alloc] initWithString:@"" attributes:nil];
}
@ -388,16 +397,18 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
[self setNeedsDisplay];
self.accessibilityLabel = _attributedText.string;
// We're an accessibility element by default if there is a string.
self.isAccessibilityElement = _attributedText.length != 0;
// Accessiblity
self.accessibilityLabel = _attributedText.string;
self.isAccessibilityElement = (_attributedText.length != 0); // We're an accessibility element by default if there is a string.
}
#pragma mark - Text Layout
- (void)setExclusionPaths:(NSArray *)exclusionPaths
{
std::lock_guard<std::recursive_mutex> l(_textLock);
if (ASObjectIsEqual(exclusionPaths, _exclusionPaths)) {
return;
}
@ -410,6 +421,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
- (NSArray *)exclusionPaths
{
std::lock_guard<std::recursive_mutex> l(_textLock);
return _exclusionPaths;
}
@ -417,6 +430,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
- (void)drawRect:(CGRect)bounds withParameters:(ASTextNodeDrawParameters *)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing
{
std::lock_guard<std::recursive_mutex> l(_textLock);
CGContextRef context = UIGraphicsGetCurrentContext();
ASDisplayNodeAssert(context, @"This is no good without a context.");
@ -437,7 +452,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
}
// Draw shadow
[[renderer shadower] setShadowInContext:context];
[renderer.shadower setShadowInContext:context];
// Draw text
bounds.origin = textOrigin;
@ -470,6 +485,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
inAdditionalTruncationMessage:(out BOOL *)inAdditionalTruncationMessageOut
forHighlighting:(BOOL)highlighting
{
ASDisplayNodeAssertMainThread();
ASTextKitRenderer *renderer = [self _renderer];
NSRange visibleRange = renderer.firstVisibleRange;
NSAttributedString *attributedString = _attributedText;
@ -562,6 +579,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
ASDisplayNodeAssertMainThread();
if (gestureRecognizer == _longPressGestureRecognizer) {
// Don't allow long press on truncation message
if ([self _pendingTruncationTap]) {
@ -593,6 +612,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
- (NSRange)highlightRange
{
ASDisplayNodeAssertMainThread();
return _highlightRange;
}
@ -603,6 +624,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
- (void)setHighlightRange:(NSRange)highlightRange animated:(BOOL)animated
{
ASDisplayNodeAssertMainThread();
[self _setHighlightRange:highlightRange forAttributeName:nil value:nil animated:animated];
}
@ -672,7 +695,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
NSMutableArray *converted = [NSMutableArray arrayWithCapacity:highlightRects.count];
for (NSValue *rectValue in highlightRects) {
UIEdgeInsets shadowPadding = _renderer.shadower.shadowPadding;
CGRect rendererRect = [[self class] _adjustRendererRect:rectValue.CGRectValue forShadowPadding:shadowPadding];
CGRect rendererRect = ASTextNodeAdjustRenderRectForShadowPadding(rectValue.CGRectValue, shadowPadding);
CGRect highlightedRect = [self.layer convertRect:rendererRect toLayer:highlightTargetLayer];
// We set our overlay layer's frame to the bounds of the highlight target layer.
@ -709,6 +732,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
- (void)_clearHighlightIfNecessary
{
ASDisplayNodeAssertMainThread();
if ([self _pendingLinkTap] || [self _pendingTruncationTap]) {
[self setHighlightRange:NSMakeRange(0, 0) animated:YES];
}
@ -726,29 +751,12 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
#pragma mark - Text rects
+ (CGRect)_adjustRendererRect:(CGRect)rendererRect forShadowPadding:(UIEdgeInsets)shadowPadding
{
static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UIEdgeInsets shadowPadding) {
rendererRect.origin.x -= shadowPadding.left;
rendererRect.origin.y -= shadowPadding.top;
return rendererRect;
}
- (NSArray *)_rectsForTextRange:(NSRange)textRange measureOption:(ASTextKitRendererMeasureOption)measureOption
{
NSArray *rects = [[self _renderer] rectsForTextRange:textRange measureOption:measureOption];
NSMutableArray *adjustedRects = [NSMutableArray array];
for (NSValue *rectValue in rects) {
CGRect rect = [rectValue CGRectValue];
rect = [self.class _adjustRendererRect:rect forShadowPadding:self.shadowPadding];
NSValue *adjustedRectValue = [NSValue valueWithCGRect:rect];
[adjustedRects addObject:adjustedRectValue];
}
return adjustedRects;
}
- (NSArray *)rectsForTextRange:(NSRange)textRange
{
return [self _rectsForTextRange:textRange measureOption:ASTextKitRendererMeasureOptionCapHeight];
@ -759,22 +767,46 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
return [self _rectsForTextRange:textRange measureOption:ASTextKitRendererMeasureOptionBlock];
}
- (NSArray *)_rectsForTextRange:(NSRange)textRange measureOption:(ASTextKitRendererMeasureOption)measureOption
{
std::lock_guard<std::recursive_mutex> l(_textLock);
NSArray *rects = [[self _renderer] rectsForTextRange:textRange measureOption:measureOption];
NSMutableArray *adjustedRects = [NSMutableArray array];
for (NSValue *rectValue in rects) {
CGRect rect = [rectValue CGRectValue];
rect = ASTextNodeAdjustRenderRectForShadowPadding(rect, self.shadowPadding);
NSValue *adjustedRectValue = [NSValue valueWithCGRect:rect];
[adjustedRects addObject:adjustedRectValue];
}
return adjustedRects;
}
- (CGRect)trailingRect
{
std::lock_guard<std::recursive_mutex> l(_textLock);
CGRect rect = [[self _renderer] trailingRect];
return [self.class _adjustRendererRect:rect forShadowPadding:self.shadowPadding];
return ASTextNodeAdjustRenderRectForShadowPadding(rect, self.shadowPadding);
}
- (CGRect)frameForTextRange:(NSRange)textRange
{
std::lock_guard<std::recursive_mutex> l(_textLock);
CGRect frame = [[self _renderer] frameForTextRange:textRange];
return [self.class _adjustRendererRect:frame forShadowPadding:self.shadowPadding];
return ASTextNodeAdjustRenderRectForShadowPadding(frame, self.shadowPadding);
}
#pragma mark - Placeholders
- (void)setPlaceholderColor:(UIColor *)placeholderColor
{
std::lock_guard<std::recursive_mutex> l(_textLock);
_placeholderColor = placeholderColor;
// prevent placeholders if we don't have a color
@ -790,6 +822,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
return nil;
}
std::lock_guard<std::recursive_mutex> l(_textLock);
UIGraphicsBeginImageContext(size);
[self.placeholderColor setFill];
@ -816,8 +850,10 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
#pragma mark - Touch Handling
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
ASDisplayNodeAssertMainThread();
if (!_passthroughNonlinkTouches) {
return [super pointInside:point withEvent:event];
}
@ -846,9 +882,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
ASDisplayNodeAssertMainThread();
[super touchesBegan:touches withEvent:event];
CGPoint point = [[touches anyObject] locationInView:self.view];
@ -878,6 +913,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
ASDisplayNodeAssertMainThread();
[super touchesCancelled:touches withEvent:event];
[self _clearHighlightIfNecessary];
@ -885,6 +921,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
ASDisplayNodeAssertMainThread();
[super touchesEnded:touches withEvent:event];
if ([self _pendingLinkTap] && [_delegate respondsToSelector:@selector(textNode:tappedLinkAttribute:value:atPoint:textRange:)]) {
@ -903,6 +940,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
ASDisplayNodeAssertMainThread();
[super touchesMoved:touches withEvent:event];
UITouch *touch = [touches anyObject];
@ -928,6 +966,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
- (void)_handleLongPress:(UILongPressGestureRecognizer *)longPressRecognizer
{
ASDisplayNodeAssertMainThread();
// Respond to long-press when it begins, not when it ends.
if (longPressRecognizer.state == UIGestureRecognizerStateBegan) {
if ([self.delegate respondsToSelector:@selector(textNode:longPressedLinkAttribute:value:atPoint:textRange:)]) {
@ -939,11 +979,15 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
- (BOOL)_pendingLinkTap
{
std::lock_guard<std::recursive_mutex> l(_textLock);
return (_highlightedLinkAttributeValue != nil && ![self _pendingTruncationTap]) && _delegate != nil;
}
- (BOOL)_pendingTruncationTap
{
std::lock_guard<std::recursive_mutex> l(_textLock);
return [_highlightedLinkAttributeName isEqualToString:ASTextNodeTruncationTokenAttributeName];
}
@ -951,11 +995,15 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
- (CGColorRef)shadowColor
{
std::lock_guard<std::recursive_mutex> l(_textLock);
return _shadowColor;
}
- (void)setShadowColor:(CGColorRef)shadowColor
{
std::lock_guard<std::recursive_mutex> l(_textLock);
if (_shadowColor != shadowColor) {
if (shadowColor != NULL) {
CGColorRetain(shadowColor);
@ -968,11 +1016,15 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
- (CGSize)shadowOffset
{
std::lock_guard<std::recursive_mutex> l(_textLock);
return _shadowOffset;
}
- (void)setShadowOffset:(CGSize)shadowOffset
{
std::lock_guard<std::recursive_mutex> l(_textLock);
if (!CGSizeEqualToSize(_shadowOffset, shadowOffset)) {
_shadowOffset = shadowOffset;
[self _invalidateRenderer];
@ -982,11 +1034,15 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
- (CGFloat)shadowOpacity
{
std::lock_guard<std::recursive_mutex> l(_textLock);
return _shadowOpacity;
}
- (void)setShadowOpacity:(CGFloat)shadowOpacity
{
std::lock_guard<std::recursive_mutex> l(_textLock);
if (_shadowOpacity != shadowOpacity) {
_shadowOpacity = shadowOpacity;
[self _invalidateRenderer];
@ -996,11 +1052,15 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
- (CGFloat)shadowRadius
{
std::lock_guard<std::recursive_mutex> l(_textLock);
return _shadowRadius;
}
- (void)setShadowRadius:(CGFloat)shadowRadius
{
std::lock_guard<std::recursive_mutex> l(_textLock);
if (_shadowRadius != shadowRadius) {
_shadowRadius = shadowRadius;
[self _invalidateRenderer];
@ -1015,6 +1075,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
- (UIEdgeInsets)shadowPaddingWithRenderer:(ASTextKitRenderer *)renderer
{
std::lock_guard<std::recursive_mutex> l(_textLock);
return renderer.shadower.shadowPadding;
}
@ -1032,6 +1094,8 @@ static NSAttributedString *DefaultTruncationAttributedString()
- (void)setTruncationAttributedText:(NSAttributedString *)truncationAttributedText
{
std::lock_guard<std::recursive_mutex> l(_textLock);
if (ASObjectIsEqual(_truncationAttributedText, truncationAttributedText)) {
return;
}
@ -1042,6 +1106,8 @@ static NSAttributedString *DefaultTruncationAttributedString()
- (void)setAdditionalTruncationMessage:(NSAttributedString *)additionalTruncationMessage
{
std::lock_guard<std::recursive_mutex> l(_textLock);
if (ASObjectIsEqual(_additionalTruncationMessage, additionalTruncationMessage)) {
return;
}
@ -1052,6 +1118,8 @@ static NSAttributedString *DefaultTruncationAttributedString()
- (void)setTruncationMode:(NSLineBreakMode)truncationMode
{
std::lock_guard<std::recursive_mutex> l(_textLock);
if (_truncationMode != truncationMode) {
_truncationMode = truncationMode;
[self _invalidateRenderer];
@ -1061,12 +1129,16 @@ static NSAttributedString *DefaultTruncationAttributedString()
- (BOOL)isTruncated
{
std::lock_guard<std::recursive_mutex> l(_textLock);
ASTextKitRenderer *renderer = [self _renderer];
return renderer.firstVisibleRange.length < _attributedText.length;
}
- (void)setPointSizeScaleFactors:(NSArray *)pointSizeScaleFactors
{
std::lock_guard<std::recursive_mutex> l(_textLock);
if ([_pointSizeScaleFactors isEqualToArray:pointSizeScaleFactors] == NO) {
_pointSizeScaleFactors = pointSizeScaleFactors;
[self _invalidateRenderer];
@ -1075,6 +1147,8 @@ static NSAttributedString *DefaultTruncationAttributedString()
- (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines
{
std::lock_guard<std::recursive_mutex> l(_textLock);
if (_maximumNumberOfLines != maximumNumberOfLines) {
_maximumNumberOfLines = maximumNumberOfLines;
[self _invalidateRenderer];
@ -1084,6 +1158,8 @@ static NSAttributedString *DefaultTruncationAttributedString()
- (NSUInteger)lineCount
{
std::lock_guard<std::recursive_mutex> l(_textLock);
return [[self _renderer] lineCount];
}
@ -1091,6 +1167,8 @@ static NSAttributedString *DefaultTruncationAttributedString()
- (void)_updateComposedTruncationText
{
std::lock_guard<std::recursive_mutex> l(_textLock);
_composedTruncationText = [self _prepareTruncationStringForDrawing:[self _composedTruncationText]];
}
@ -1107,6 +1185,8 @@ static NSAttributedString *DefaultTruncationAttributedString()
*/
- (NSRange)_additionalTruncationMessageRangeWithVisibleRange:(NSRange)visibleRange
{
std::lock_guard<std::recursive_mutex> l(_textLock);
// Check if we even have an additional truncation message.
if (!_additionalTruncationMessage) {
return NSMakeRange(NSNotFound, 0);
@ -1118,8 +1198,7 @@ static NSAttributedString *DefaultTruncationAttributedString()
NSUInteger additionalTruncationMessageLength = _additionalTruncationMessage.length;
// We get the location of the truncation token, then add the length of the
// truncation attributed string +1 for the space between.
NSRange range = NSMakeRange(truncationTokenIndex + _truncationAttributedText.length + 1, additionalTruncationMessageLength);
return range;
return NSMakeRange(truncationTokenIndex + _truncationAttributedText.length + 1, additionalTruncationMessageLength);
}
/**
@ -1129,6 +1208,8 @@ static NSAttributedString *DefaultTruncationAttributedString()
*/
- (NSAttributedString *)_composedTruncationText
{
std::lock_guard<std::recursive_mutex> l(_textLock);
//If we have neither return the default
if (!_additionalTruncationMessage && !_truncationAttributedText) {
return _composedTruncationText;
@ -1157,6 +1238,8 @@ static NSAttributedString *DefaultTruncationAttributedString()
*/
- (NSAttributedString *)_prepareTruncationStringForDrawing:(NSAttributedString *)truncationString
{
std::lock_guard<std::recursive_mutex> l(_textLock);
truncationString = ASCleanseAttributedStringOfCoreTextAttributes(truncationString);
NSMutableAttributedString *truncationMutableString = [truncationString mutableCopy];
// Grab the attributes from the full string