//
|
// YYTextAttribute.m
|
// YYText <https://github.com/ibireme/YYText>
|
//
|
// 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 <UIKit/UIKit.h>
|
#import <CoreText/CoreText.h>
|
#import "NSAttributedString+YYText.h"
|
#import "YYTextArchiver.h"
|
|
|
static double _YYDeviceSystemVersion() {
|
static double version;
|
static dispatch_once_t onceToken;
|
dispatch_once(&onceToken, ^{
|
version = [UIDevice currentDevice].systemVersion.doubleValue;
|
});
|
return version;
|
}
|
|
|
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 (_YYDeviceSystemVersion() >= 7) {
|
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 (_YYDeviceSystemVersion() >= 8) {
|
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) 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 = CTFontCreateWithName((__bridge CFStringRef)font.fontName, font.pointSize, NULL);
|
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
|