// // YYTextAttribute.m // YYKit // // Created by ibireme on 14/10/26. // Copyright (c) 2015 ibireme. // // This source code is licensed under the MIT-style license found in the // LICENSE file in the root directory of this source tree. // #import "YYTextAttribute.h" #import #import #import "NSObject+YYAdd.h" #import "NSAttributedString+YYText.h" #import "YYAnimatedImageView.h" #import "YYTextArchiver.h" #import "UIFont+YYAdd.h" #import "UIDevice+YYAdd.h" NSString *const YYTextBackedStringAttributeName = @"YYTextBackedString"; NSString *const YYTextBindingAttributeName = @"YYTextBinding"; NSString *const YYTextShadowAttributeName = @"YYTextShadow"; NSString *const YYTextInnerShadowAttributeName = @"YYTextInnerShadow"; NSString *const YYTextUnderlineAttributeName = @"YYTextUnderline"; NSString *const YYTextStrikethroughAttributeName = @"YYTextStrikethrough"; NSString *const YYTextBorderAttributeName = @"YYTextBorder"; NSString *const YYTextBackgroundBorderAttributeName = @"YYTextBackgroundBorder"; NSString *const YYTextBlockBorderAttributeName = @"YYTextBlockBorder"; NSString *const YYTextAttachmentAttributeName = @"YYTextAttachment"; NSString *const YYTextHighlightAttributeName = @"YYTextHighlight"; NSString *const YYTextGlyphTransformAttributeName = @"YYTextGlyphTransform"; NSString *const YYTextAttachmentToken = @"\uFFFC"; NSString *const YYTextTruncationToken = @"\u2026"; YYTextAttributeType YYTextAttributeGetType(NSString *name){ if (name.length == 0) return YYTextAttributeTypeNone; static NSMutableDictionary *dic; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ dic = [NSMutableDictionary new]; NSNumber *All = @(YYTextAttributeTypeUIKit | YYTextAttributeTypeCoreText | YYTextAttributeTypeYYText); NSNumber *CoreText_YYText = @(YYTextAttributeTypeCoreText | YYTextAttributeTypeYYText); NSNumber *UIKit_YYText = @(YYTextAttributeTypeUIKit | YYTextAttributeTypeYYText); NSNumber *UIKit_CoreText = @(YYTextAttributeTypeUIKit | YYTextAttributeTypeCoreText); NSNumber *UIKit = @(YYTextAttributeTypeUIKit); NSNumber *CoreText = @(YYTextAttributeTypeCoreText); NSNumber *YYText = @(YYTextAttributeTypeYYText); dic[NSFontAttributeName] = All; dic[NSKernAttributeName] = All; dic[NSForegroundColorAttributeName] = UIKit; dic[(id)kCTForegroundColorAttributeName] = CoreText; dic[(id)kCTForegroundColorFromContextAttributeName] = CoreText; dic[NSBackgroundColorAttributeName] = UIKit; dic[NSStrokeWidthAttributeName] = All; dic[NSStrokeColorAttributeName] = UIKit; dic[(id)kCTStrokeColorAttributeName] = CoreText_YYText; dic[NSShadowAttributeName] = UIKit_YYText; dic[NSStrikethroughStyleAttributeName] = UIKit; dic[NSUnderlineStyleAttributeName] = UIKit_CoreText; dic[(id)kCTUnderlineColorAttributeName] = CoreText; dic[NSLigatureAttributeName] = All; dic[(id)kCTSuperscriptAttributeName] = UIKit; //it's a CoreText attrubite, but only supported by UIKit... dic[NSVerticalGlyphFormAttributeName] = All; dic[(id)kCTGlyphInfoAttributeName] = CoreText_YYText; dic[(id)kCTCharacterShapeAttributeName] = CoreText_YYText; dic[(id)kCTRunDelegateAttributeName] = CoreText_YYText; dic[(id)kCTBaselineClassAttributeName] = CoreText_YYText; dic[(id)kCTBaselineInfoAttributeName] = CoreText_YYText; dic[(id)kCTBaselineReferenceInfoAttributeName] = CoreText_YYText; dic[(id)kCTWritingDirectionAttributeName] = CoreText_YYText; dic[NSParagraphStyleAttributeName] = All; if (kiOS7Later) { dic[NSStrikethroughColorAttributeName] = UIKit; dic[NSUnderlineColorAttributeName] = UIKit; dic[NSTextEffectAttributeName] = UIKit; dic[NSObliquenessAttributeName] = UIKit; dic[NSExpansionAttributeName] = UIKit; dic[(id)kCTLanguageAttributeName] = CoreText_YYText; dic[NSBaselineOffsetAttributeName] = UIKit; dic[NSWritingDirectionAttributeName] = All; dic[NSAttachmentAttributeName] = UIKit; dic[NSLinkAttributeName] = UIKit; } if (kiOS8Later) { dic[(id)kCTRubyAnnotationAttributeName] = CoreText; } dic[YYTextBackedStringAttributeName] = YYText; dic[YYTextBindingAttributeName] = YYText; dic[YYTextShadowAttributeName] = YYText; dic[YYTextInnerShadowAttributeName] = YYText; dic[YYTextUnderlineAttributeName] = YYText; dic[YYTextStrikethroughAttributeName] = YYText; dic[YYTextBorderAttributeName] = YYText; dic[YYTextBackgroundBorderAttributeName] = YYText; dic[YYTextBlockBorderAttributeName] = YYText; dic[YYTextAttachmentAttributeName] = YYText; dic[YYTextHighlightAttributeName] = YYText; dic[YYTextGlyphTransformAttributeName] = YYText; }); NSNumber *num = dic[name]; if (num != nil) return num.integerValue; return YYTextAttributeTypeNone; } @implementation YYTextBackedString + (instancetype)stringWithString:(NSString *)string { YYTextBackedString *one = [self new]; one.string = string; return one; } - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.string forKey:@"string"]; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super init]; _string = [aDecoder decodeObjectForKey:@"string"]; return self; } - (id)copyWithZone:(NSZone *)zone { typeof(self) one = [self.class new]; one.string = self.string; return one; } @end @implementation YYTextBinding + (instancetype)bindingWithDeleteConfirm:(BOOL)deleteConfirm { YYTextBinding *one = [self new]; one.deleteConfirm = deleteConfirm; return one; } - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:@(self.deleteConfirm) forKey:@"deleteConfirm"]; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super init]; _deleteConfirm = ((NSNumber *)[aDecoder decodeObjectForKey:@"deleteConfirm"]).boolValue; return self; } - (id)copyWithZone:(NSZone *)zone { typeof(self) one = [self.class new]; one.deleteConfirm = self.deleteConfirm; return one; } @end @implementation YYTextShadow + (instancetype)shadowWithColor:(UIColor *)color offset:(CGSize)offset radius:(CGFloat)radius { YYTextShadow *one = [self new]; one.color = color; one.offset = offset; one.radius = radius; return one; } + (instancetype)shadowWithNSShadow:(NSShadow *)nsShadow { if (!nsShadow) return nil; YYTextShadow *shadow = [self new]; shadow.offset = nsShadow.shadowOffset; shadow.radius = nsShadow.shadowBlurRadius; id color = nsShadow.shadowColor; if (color) { if (CGColorGetTypeID() == CFGetTypeID((__bridge CFTypeRef)(color))) { color = [UIColor colorWithCGColor:(__bridge CGColorRef)(color)]; } if ([color isKindOfClass:[UIColor class]]) { shadow.color = color; } } return shadow; } - (NSShadow *)nsShadow { NSShadow *shadow = [NSShadow new]; shadow.shadowOffset = self.offset; shadow.shadowBlurRadius = self.radius; shadow.shadowColor = self.color; return shadow; } - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.color forKey:@"color"]; [aCoder encodeObject:@(self.radius) forKey:@"radius"]; [aCoder encodeObject:[NSValue valueWithCGSize:self.offset] forKey:@"offset"]; [aCoder encodeObject:self.subShadow forKey:@"subShadow"]; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super init]; _color = [aDecoder decodeObjectForKey:@"color"]; _radius = ((NSNumber *)[aDecoder decodeObjectForKey:@"radius"]).floatValue; _offset = ((NSValue *)[aDecoder decodeObjectForKey:@"offset"]).CGSizeValue; _subShadow = [aDecoder decodeObjectForKey:@"subShadow"]; return self; } - (id)copyWithZone:(NSZone *)zone { typeof(self) one = [self.class new]; one.color = self.color; one.radius = self.radius; one.offset = self.offset; one.subShadow = self.subShadow.copy; return one; } @end @implementation YYTextDecoration - (instancetype)init { self = [super init]; _style = YYTextLineStyleSingle; return self; } + (instancetype)decorationWithStyle:(YYTextLineStyle)style { YYTextDecoration *one = [self new]; one.style = style; return one; } + (instancetype)decorationWithStyle:(YYTextLineStyle)style width:(NSNumber *)width color:(UIColor *)color { YYTextDecoration *one = [self new]; one.style = style; one.width = width; one.color = color; return one; } - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:@(self.style) forKey:@"style"]; [aCoder encodeObject:self.width forKey:@"width"]; [aCoder encodeObject:self.color forKey:@"color"]; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super init]; self.style = ((NSNumber *)[aDecoder decodeObjectForKey:@"style"]).unsignedIntegerValue; self.width = [aDecoder decodeObjectForKey:@"width"]; self.color = [aDecoder decodeObjectForKey:@"color"]; return self; } - (id)copyWithZone:(NSZone *)zone { typeof(self) one = [self.class new]; one.style = self.style; one.width = self.width; one.color = self.color; return one; } @end @implementation YYTextBorder + (instancetype)borderWithLineStyle:(YYTextLineStyle)lineStyle lineWidth:(CGFloat)width strokeColor:(UIColor *)color { YYTextBorder *one = [self new]; one.lineStyle = lineStyle; one.strokeWidth = width; one.strokeColor = color; return one; } + (instancetype)borderWithFillColor:(UIColor *)color cornerRadius:(CGFloat)cornerRadius { YYTextBorder *one = [self new]; one.fillColor = color; one.cornerRadius = cornerRadius; one.insets = UIEdgeInsetsMake(-2, 0, 0, -2); return one; } - (instancetype)init { self = [super init]; self.lineStyle = YYTextLineStyleSingle; return self; } - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:@(self.lineStyle) forKey:@"lineStyle"]; [aCoder encodeObject:@(self.strokeWidth) forKey:@"strokeWidth"]; [aCoder encodeObject:self.strokeColor forKey:@"strokeColor"]; [aCoder encodeObject:@(self.lineJoin) forKey:@"lineJoin"]; [aCoder encodeObject:[NSValue valueWithUIEdgeInsets:self.insets] forKey:@"insets"]; [aCoder encodeObject:@(self.cornerRadius) forKey:@"cornerRadius"]; [aCoder encodeObject:self.shadow forKey:@"shadow"]; [aCoder encodeObject:self.fillColor forKey:@"fillColor"]; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super init]; _lineStyle = ((NSNumber *)[aDecoder decodeObjectForKey:@"lineStyle"]).unsignedIntegerValue; _strokeWidth = ((NSNumber *)[aDecoder decodeObjectForKey:@"strokeWidth"]).doubleValue; _strokeColor = [aDecoder decodeObjectForKey:@"strokeColor"]; _lineJoin = (CGLineJoin)((NSNumber *)[aDecoder decodeObjectForKey:@"join"]).unsignedIntegerValue; _insets = ((NSValue *)[aDecoder decodeObjectForKey:@"insets"]).UIEdgeInsetsValue; _cornerRadius = ((NSNumber *)[aDecoder decodeObjectForKey:@"cornerRadius"]).doubleValue; _shadow = [aDecoder decodeObjectForKey:@"shadow"]; _fillColor = [aDecoder decodeObjectForKey:@"fillColor"]; return self; } - (id)copyWithZone:(NSZone *)zone { typeof(self) one = [self.class new]; one.lineStyle = self.lineStyle; one.strokeWidth = self.strokeWidth; one.strokeColor = self.strokeColor; one.lineJoin = self.lineJoin; one.insets = self.insets; one.cornerRadius = self.cornerRadius; one.shadow = self.shadow.copy; one.fillColor = self.fillColor; return one; } @end @implementation YYTextAttachment + (instancetype)attachmentWithContent:(id)content { YYTextAttachment *one = [self new]; one.content = content; return one; } - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.content forKey:@"content"]; [aCoder encodeObject:[NSValue valueWithUIEdgeInsets:self.contentInsets] forKey:@"contentInsets"]; [aCoder encodeObject:self.userInfo forKey:@"userInfo"]; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super init]; _content = [aDecoder decodeObjectForKey:@"content"]; _contentInsets = ((NSValue *)[aDecoder decodeObjectForKey:@"contentInsets"]).UIEdgeInsetsValue; _userInfo = [aDecoder decodeObjectForKey:@"userInfo"]; return self; } - (id)copyWithZone:(NSZone *)zone { typeof(self) one = [self.class new]; if ([self.content respondsToSelector:@selector(copy)]) { one.content = [self.content copy]; } else { one.content = self.content; } one.contentInsets = self.contentInsets; one.userInfo = self.userInfo.copy; return one; } @end @implementation YYTextHighlight + (instancetype)highlightWithAttributes:(NSDictionary *)attributes { YYTextHighlight *one = [self new]; one.attributes = attributes; return one; } + (instancetype)highlightWithBackgroundColor:(UIColor *)color { YYTextBorder *highlightBorder = [YYTextBorder new]; highlightBorder.insets = UIEdgeInsetsMake(-2, -1, -2, -1); highlightBorder.cornerRadius = 3; highlightBorder.fillColor = color; YYTextHighlight *one = [self new]; [one setBackgroundBorder:highlightBorder]; return one; } - (void)setAttributes:(NSDictionary *)attributes { _attributes = attributes.mutableCopy; } - (void)encodeWithCoder:(NSCoder *)aCoder { NSData *data = nil; @try { data = [YYTextArchiver archivedDataWithRootObject:self.attributes]; } @catch (NSException *exception) { NSLog(@"%@",exception); } [aCoder encodeObject:data forKey:@"attributes"]; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super init]; NSData *data = [aDecoder decodeObjectForKey:@"attributes"]; @try { _attributes = [YYTextUnarchiver unarchiveObjectWithData:data]; } @catch (NSException *exception) { NSLog(@"%@",exception); } return self; } - (id)copyWithZone:(NSZone *)zone { typeof(self) one = [self.class new]; one.attributes = self.attributes.mutableCopy; return one; } - (void)_makeMutableAttributes { if (!_attributes) { _attributes = [NSMutableDictionary new]; } else if (![_attributes isKindOfClass:[NSMutableDictionary class]]) { _attributes = _attributes.mutableCopy; } } - (void)setFont:(UIFont *)font { [self _makeMutableAttributes]; if (font == (id)[NSNull null] || font == nil) { ((NSMutableDictionary *)_attributes)[(id)kCTFontAttributeName] = [NSNull null]; } else { CTFontRef ctFont = [font CTFontRef]; if (ctFont) { ((NSMutableDictionary *)_attributes)[(id)kCTFontAttributeName] = (__bridge id)(ctFont); CFRelease(ctFont); } } } - (void)setColor:(UIColor *)color { [self _makeMutableAttributes]; if (color == (id)[NSNull null] || color == nil) { ((NSMutableDictionary *)_attributes)[(id)kCTForegroundColorAttributeName] = [NSNull null]; ((NSMutableDictionary *)_attributes)[NSForegroundColorAttributeName] = [NSNull null]; } else { ((NSMutableDictionary *)_attributes)[(id)kCTForegroundColorAttributeName] = (__bridge id)(color.CGColor); ((NSMutableDictionary *)_attributes)[NSForegroundColorAttributeName] = color; } } - (void)setStrokeWidth:(NSNumber *)width { [self _makeMutableAttributes]; if (width == (id)[NSNull null] || width == nil) { ((NSMutableDictionary *)_attributes)[(id)kCTStrokeWidthAttributeName] = [NSNull null]; } else { ((NSMutableDictionary *)_attributes)[(id)kCTStrokeWidthAttributeName] = width; } } - (void)setStrokeColor:(UIColor *)color { [self _makeMutableAttributes]; if (color == (id)[NSNull null] || color == nil) { ((NSMutableDictionary *)_attributes)[(id)kCTStrokeColorAttributeName] = [NSNull null]; ((NSMutableDictionary *)_attributes)[NSStrokeColorAttributeName] = [NSNull null]; } else { ((NSMutableDictionary *)_attributes)[(id)kCTStrokeColorAttributeName] = (__bridge id)(color.CGColor); ((NSMutableDictionary *)_attributes)[NSStrokeColorAttributeName] = color; } } - (void)setTextAttribute:(NSString *)attribute value:(id)value { [self _makeMutableAttributes]; if (value == nil) value = [NSNull null]; ((NSMutableDictionary *)_attributes)[attribute] = value; } - (void)setShadow:(YYTextShadow *)shadow { [self setTextAttribute:YYTextShadowAttributeName value:shadow]; } - (void)setInnerShadow:(YYTextShadow *)shadow { [self setTextAttribute:YYTextInnerShadowAttributeName value:shadow]; } - (void)setUnderline:(YYTextDecoration *)underline { [self setTextAttribute:YYTextUnderlineAttributeName value:underline]; } - (void)setStrikethrough:(YYTextDecoration *)strikethrough { [self setTextAttribute:YYTextStrikethroughAttributeName value:strikethrough]; } - (void)setBackgroundBorder:(YYTextBorder *)border { [self setTextAttribute:YYTextBackgroundBorderAttributeName value:border]; } - (void)setBorder:(YYTextBorder *)border { [self setTextAttribute:YYTextBorderAttributeName value:border]; } - (void)setAttachment:(YYTextAttachment *)attachment { [self setTextAttribute:YYTextAttachmentAttributeName value:attachment]; } @end