New file |
| | |
| | | // |
| | | // YYTextParser.m |
| | | // YYText <https://github.com/ibireme/YYText> |
| | | // |
| | | // Created by ibireme on 15/3/6. |
| | | // 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 "YYTextParser.h" |
| | | #import "YYTextUtilities.h" |
| | | #import "YYTextAttribute.h" |
| | | #import "NSAttributedString+YYText.h" |
| | | #import "NSParagraphStyle+YYText.h" |
| | | |
| | | |
| | | #pragma mark - Markdown Parser |
| | | |
| | | @implementation YYTextSimpleMarkdownParser { |
| | | UIFont *_font; |
| | | NSMutableArray *_headerFonts; ///< h1~h6 |
| | | UIFont *_boldFont; |
| | | UIFont *_italicFont; |
| | | UIFont *_boldItalicFont; |
| | | UIFont *_monospaceFont; |
| | | YYTextBorder *_border; |
| | | |
| | | NSRegularExpression *_regexEscape; ///< escape |
| | | NSRegularExpression *_regexHeader; ///< #header |
| | | NSRegularExpression *_regexH1; ///< header\n==== |
| | | NSRegularExpression *_regexH2; ///< header\n---- |
| | | NSRegularExpression *_regexBreakline; ///< ****** |
| | | NSRegularExpression *_regexEmphasis; ///< *text* _text_ |
| | | NSRegularExpression *_regexStrong; ///< **text** |
| | | NSRegularExpression *_regexStrongEmphasis; ///< ***text*** ___text___ |
| | | NSRegularExpression *_regexUnderline; ///< __text__ |
| | | NSRegularExpression *_regexStrikethrough; ///< ~~text~~ |
| | | NSRegularExpression *_regexInlineCode; ///< `text` |
| | | NSRegularExpression *_regexLink; ///< [name](link) |
| | | NSRegularExpression *_regexLinkRefer; ///< [ref]: |
| | | NSRegularExpression *_regexList; ///< 1.text 2.text 3.text |
| | | NSRegularExpression *_regexBlockQuote; ///< > quote |
| | | NSRegularExpression *_regexCodeBlock; ///< \tcode \tcode |
| | | NSRegularExpression *_regexNotEmptyLine; |
| | | } |
| | | |
| | | - (void)initRegex { |
| | | #define regexp(reg, option) [NSRegularExpression regularExpressionWithPattern : @reg options : option error : NULL] |
| | | _regexEscape = regexp("(\\\\\\\\|\\\\\\`|\\\\\\*|\\\\\\_|\\\\\\(|\\\\\\)|\\\\\\[|\\\\\\]|\\\\#|\\\\\\+|\\\\\\-|\\\\\\!)", 0); |
| | | _regexHeader = regexp("^((\\#{1,6}[^#].*)|(\\#{6}.+))$", NSRegularExpressionAnchorsMatchLines); |
| | | _regexH1 = regexp("^[^=\\n][^\\n]*\\n=+$", NSRegularExpressionAnchorsMatchLines); |
| | | _regexH2 = regexp("^[^-\\n][^\\n]*\\n-+$", NSRegularExpressionAnchorsMatchLines); |
| | | _regexBreakline = regexp("^[ \\t]*([*-])[ \\t]*((\\1)[ \\t]*){2,}[ \\t]*$", NSRegularExpressionAnchorsMatchLines); |
| | | _regexEmphasis = regexp("((?<!\\*)\\*(?=[^ \\t*])(.+?)(?<=[^ \\t*])\\*(?!\\*)|(?<!_)_(?=[^ \\t_])(.+?)(?<=[^ \\t_])_(?!_))", 0); |
| | | _regexStrong = regexp("(?<!\\*)\\*{2}(?=[^ \\t*])(.+?)(?<=[^ \\t*])\\*{2}(?!\\*)", 0); |
| | | _regexStrongEmphasis = regexp("((?<!\\*)\\*{3}(?=[^ \\t*])(.+?)(?<=[^ \\t*])\\*{3}(?!\\*)|(?<!_)_{3}(?=[^ \\t_])(.+?)(?<=[^ \\t_])_{3}(?!_))", 0); |
| | | _regexUnderline = regexp("(?<!_)__(?=[^ \\t_])(.+?)(?<=[^ \\t_])\\__(?!_)", 0); |
| | | _regexStrikethrough = regexp("(?<!~)~~(?=[^ \\t~])(.+?)(?<=[^ \\t~])\\~~(?!~)", 0); |
| | | _regexInlineCode = regexp("(?<!`)(`{1,3})([^`\n]+?)\\1(?!`)", 0); |
| | | _regexLink = regexp("!?\\[([^\\[\\]]+)\\](\\(([^\\(\\)]+)\\)|\\[([^\\[\\]]+)\\])", 0); |
| | | _regexLinkRefer = regexp("^[ \\t]*\\[[^\\[\\]]\\]:", NSRegularExpressionAnchorsMatchLines); |
| | | _regexList = regexp("^[ \\t]*([*+-]|\\d+[.])[ \\t]+", NSRegularExpressionAnchorsMatchLines); |
| | | _regexBlockQuote = regexp("^[ \\t]*>[ \\t>]*", NSRegularExpressionAnchorsMatchLines); |
| | | _regexCodeBlock = regexp("(^\\s*$\\n)((( {4}|\\t).*(\\n|\\z))|(^\\s*$\\n))+", NSRegularExpressionAnchorsMatchLines); |
| | | _regexNotEmptyLine = regexp("^[ \\t]*[^ \\t]+[ \\t]*$", NSRegularExpressionAnchorsMatchLines); |
| | | #undef regexp |
| | | } |
| | | |
| | | - (instancetype)init { |
| | | self = [super init]; |
| | | _fontSize = 14; |
| | | _headerFontSize = 20; |
| | | [self _updateFonts]; |
| | | [self setColorWithBrightTheme]; |
| | | [self initRegex]; |
| | | return self; |
| | | } |
| | | |
| | | - (void)setFontSize:(CGFloat)fontSize { |
| | | if (fontSize < 1) fontSize = 12; |
| | | _fontSize = fontSize; |
| | | [self _updateFonts]; |
| | | } |
| | | |
| | | - (void)setHeaderFontSize:(CGFloat)headerFontSize { |
| | | if (headerFontSize < 1) headerFontSize = 20; |
| | | _headerFontSize = headerFontSize; |
| | | [self _updateFonts]; |
| | | } |
| | | |
| | | - (void)_updateFonts { |
| | | _font = [UIFont systemFontOfSize:_fontSize]; |
| | | _headerFonts = [NSMutableArray new]; |
| | | for (int i = 0; i < 6; i++) { |
| | | CGFloat size = _headerFontSize - (_headerFontSize - _fontSize) / 5.0 * i; |
| | | [_headerFonts addObject:[UIFont systemFontOfSize:size]]; |
| | | } |
| | | _boldFont = YYTextFontWithBold(_font); |
| | | _italicFont = YYTextFontWithItalic(_font); |
| | | _boldItalicFont = YYTextFontWithBoldItalic(_font); |
| | | _monospaceFont = [UIFont fontWithName:@"Menlo" size:_fontSize]; // Since iOS 7 |
| | | if (!_monospaceFont) _monospaceFont = [UIFont fontWithName:@"Courier" size:_fontSize]; // Since iOS 3 |
| | | } |
| | | |
| | | - (void)setColorWithBrightTheme { |
| | | _textColor = [UIColor blackColor]; |
| | | _controlTextColor = [UIColor colorWithWhite:0.749 alpha:1.000]; |
| | | _headerTextColor = [UIColor colorWithRed:1.000 green:0.502 blue:0.000 alpha:1.000]; |
| | | _inlineTextColor = [UIColor colorWithWhite:0.150 alpha:1.000]; |
| | | _codeTextColor = [UIColor colorWithWhite:0.150 alpha:1.000]; |
| | | _linkTextColor = [UIColor colorWithRed:0.000 green:0.478 blue:0.962 alpha:1.000]; |
| | | |
| | | _border = [YYTextBorder new]; |
| | | _border.lineStyle = YYTextLineStyleSingle; |
| | | _border.fillColor = [UIColor colorWithWhite:0.184 alpha:0.090]; |
| | | _border.strokeColor = [UIColor colorWithWhite:0.546 alpha:0.650]; |
| | | _border.insets = UIEdgeInsetsMake(-1, 0, -1, 0); |
| | | _border.cornerRadius = 2; |
| | | _border.strokeWidth = YYTextCGFloatFromPixel(1); |
| | | } |
| | | |
| | | - (void)setColorWithDarkTheme { |
| | | _textColor = [UIColor whiteColor]; |
| | | _controlTextColor = [UIColor colorWithWhite:0.604 alpha:1.000]; |
| | | _headerTextColor = [UIColor colorWithRed:0.558 green:1.000 blue:0.502 alpha:1.000]; |
| | | _inlineTextColor = [UIColor colorWithRed:1.000 green:0.862 blue:0.387 alpha:1.000]; |
| | | _codeTextColor = [UIColor colorWithWhite:0.906 alpha:1.000]; |
| | | _linkTextColor = [UIColor colorWithRed:0.000 green:0.646 blue:1.000 alpha:1.000]; |
| | | |
| | | _border = [YYTextBorder new]; |
| | | _border.lineStyle = YYTextLineStyleSingle; |
| | | _border.fillColor = [UIColor colorWithWhite:0.820 alpha:0.130]; |
| | | _border.strokeColor = [UIColor colorWithWhite:1.000 alpha:0.280]; |
| | | _border.insets = UIEdgeInsetsMake(-1, 0, -1, 0); |
| | | _border.cornerRadius = 2; |
| | | _border.strokeWidth = YYTextCGFloatFromPixel(1); |
| | | } |
| | | |
| | | - (NSUInteger)lenghOfBeginWhiteInString:(NSString *)str withRange:(NSRange)range{ |
| | | for (NSUInteger i = 0; i < range.length; i++) { |
| | | unichar c = [str characterAtIndex:i + range.location]; |
| | | if (c != ' ' && c != '\t' && c != '\n') return i; |
| | | } |
| | | return str.length; |
| | | } |
| | | |
| | | - (NSUInteger)lenghOfEndWhiteInString:(NSString *)str withRange:(NSRange)range{ |
| | | for (NSInteger i = range.length - 1; i >= 0; i--) { |
| | | unichar c = [str characterAtIndex:i + range.location]; |
| | | if (c != ' ' && c != '\t' && c != '\n') return range.length - i; |
| | | } |
| | | return str.length; |
| | | } |
| | | |
| | | - (NSUInteger)lenghOfBeginChar:(unichar)c inString:(NSString *)str withRange:(NSRange)range{ |
| | | for (NSUInteger i = 0; i < range.length; i++) { |
| | | if ([str characterAtIndex:i + range.location] != c) return i; |
| | | } |
| | | return str.length; |
| | | } |
| | | |
| | | - (BOOL)parseText:(NSMutableAttributedString *)text selectedRange:(NSRangePointer)range { |
| | | if (text.length == 0) return NO; |
| | | [text yy_removeAttributesInRange:NSMakeRange(0, text.length)]; |
| | | text.yy_font = _font; |
| | | text.yy_color = _textColor; |
| | | |
| | | NSMutableString *str = text.string.mutableCopy; |
| | | [_regexEscape replaceMatchesInString:str options:kNilOptions range:NSMakeRange(0, str.length) withTemplate:@"@@"]; |
| | | |
| | | [_regexHeader enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { |
| | | NSRange r = result.range; |
| | | NSUInteger whiteLen = [self lenghOfBeginWhiteInString:str withRange:r]; |
| | | NSUInteger sharpLen = [self lenghOfBeginChar:'#' inString:str withRange:NSMakeRange(r.location + whiteLen, r.length - whiteLen)]; |
| | | if (sharpLen > 6) sharpLen = 6; |
| | | [text yy_setColor:_controlTextColor range:NSMakeRange(r.location, whiteLen + sharpLen)]; |
| | | [text yy_setColor:_headerTextColor range:NSMakeRange(r.location + whiteLen + sharpLen, r.length - whiteLen - sharpLen)]; |
| | | [text yy_setFont:_headerFonts[sharpLen - 1] range:result.range]; |
| | | }]; |
| | | |
| | | [_regexH1 enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { |
| | | NSRange r = result.range; |
| | | NSRange linebreak = [str rangeOfString:@"\n" options:0 range:result.range locale:nil]; |
| | | if (linebreak.location != NSNotFound) { |
| | | [text yy_setColor:_headerTextColor range:NSMakeRange(r.location, linebreak.location - r.location)]; |
| | | [text yy_setFont:_headerFonts[0] range:NSMakeRange(r.location, linebreak.location - r.location + 1)]; |
| | | [text yy_setColor:_controlTextColor range:NSMakeRange(linebreak.location + linebreak.length, r.location + r.length - linebreak.location - linebreak.length)]; |
| | | } |
| | | }]; |
| | | |
| | | [_regexH2 enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { |
| | | NSRange r = result.range; |
| | | NSRange linebreak = [str rangeOfString:@"\n" options:0 range:result.range locale:nil]; |
| | | if (linebreak.location != NSNotFound) { |
| | | [text yy_setColor:_headerTextColor range:NSMakeRange(r.location, linebreak.location - r.location)]; |
| | | [text yy_setFont:_headerFonts[1] range:NSMakeRange(r.location, linebreak.location - r.location + 1)]; |
| | | [text yy_setColor:_controlTextColor range:NSMakeRange(linebreak.location + linebreak.length, r.location + r.length - linebreak.location - linebreak.length)]; |
| | | } |
| | | }]; |
| | | |
| | | [_regexBreakline enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { |
| | | [text yy_setColor:_controlTextColor range:result.range]; |
| | | }]; |
| | | |
| | | [_regexEmphasis enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { |
| | | NSRange r = result.range; |
| | | [text yy_setColor:_controlTextColor range:NSMakeRange(r.location, 1)]; |
| | | [text yy_setColor:_controlTextColor range:NSMakeRange(r.location + r.length - 1, 1)]; |
| | | [text yy_setFont:_italicFont range:NSMakeRange(r.location + 1, r.length - 2)]; |
| | | }]; |
| | | |
| | | [_regexStrong enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { |
| | | NSRange r = result.range; |
| | | [text yy_setColor:_controlTextColor range:NSMakeRange(r.location, 2)]; |
| | | [text yy_setColor:_controlTextColor range:NSMakeRange(r.location + r.length - 2, 2)]; |
| | | [text yy_setFont:_boldFont range:NSMakeRange(r.location + 2, r.length - 4)]; |
| | | }]; |
| | | |
| | | [_regexStrongEmphasis enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { |
| | | NSRange r = result.range; |
| | | [text yy_setColor:_controlTextColor range:NSMakeRange(r.location, 3)]; |
| | | [text yy_setColor:_controlTextColor range:NSMakeRange(r.location + r.length - 3, 3)]; |
| | | [text yy_setFont:_boldItalicFont range:NSMakeRange(r.location + 3, r.length - 6)]; |
| | | }]; |
| | | |
| | | [_regexUnderline enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { |
| | | NSRange r = result.range; |
| | | [text yy_setColor:_controlTextColor range:NSMakeRange(r.location, 2)]; |
| | | [text yy_setColor:_controlTextColor range:NSMakeRange(r.location + r.length - 2, 2)]; |
| | | [text yy_setTextUnderline:[YYTextDecoration decorationWithStyle:YYTextLineStyleSingle width:@1 color:nil] range:NSMakeRange(r.location + 2, r.length - 4)]; |
| | | }]; |
| | | |
| | | [_regexStrikethrough enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { |
| | | NSRange r = result.range; |
| | | [text yy_setColor:_controlTextColor range:NSMakeRange(r.location, 2)]; |
| | | [text yy_setColor:_controlTextColor range:NSMakeRange(r.location + r.length - 2, 2)]; |
| | | [text yy_setTextStrikethrough:[YYTextDecoration decorationWithStyle:YYTextLineStyleSingle width:@1 color:nil] range:NSMakeRange(r.location + 2, r.length - 4)]; |
| | | }]; |
| | | |
| | | [_regexInlineCode enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { |
| | | NSRange r = result.range; |
| | | NSUInteger len = [self lenghOfBeginChar:'`' inString:str withRange:r]; |
| | | [text yy_setColor:_controlTextColor range:NSMakeRange(r.location, len)]; |
| | | [text yy_setColor:_controlTextColor range:NSMakeRange(r.location + r.length - len, len)]; |
| | | [text yy_setColor:_inlineTextColor range:NSMakeRange(r.location + len, r.length - 2 * len)]; |
| | | [text yy_setFont:_monospaceFont range:r]; |
| | | [text yy_setTextBorder:_border.copy range:r]; |
| | | }]; |
| | | |
| | | [_regexLink enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { |
| | | NSRange r = result.range; |
| | | [text yy_setColor:_linkTextColor range:r]; |
| | | }]; |
| | | |
| | | [_regexLinkRefer enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { |
| | | NSRange r = result.range; |
| | | [text yy_setColor:_controlTextColor range:r]; |
| | | }]; |
| | | |
| | | [_regexList enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { |
| | | NSRange r = result.range; |
| | | [text yy_setColor:_controlTextColor range:r]; |
| | | }]; |
| | | |
| | | [_regexBlockQuote enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { |
| | | NSRange r = result.range; |
| | | [text yy_setColor:_controlTextColor range:r]; |
| | | }]; |
| | | |
| | | [_regexCodeBlock enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { |
| | | NSRange r = result.range; |
| | | NSRange firstLineRange = [_regexNotEmptyLine rangeOfFirstMatchInString:str options:kNilOptions range:r]; |
| | | NSUInteger lenStart = (firstLineRange.location != NSNotFound) ? firstLineRange.location - r.location : 0; |
| | | NSUInteger lenEnd = [self lenghOfEndWhiteInString:str withRange:r]; |
| | | if (lenStart + lenEnd < r.length) { |
| | | NSRange codeR = NSMakeRange(r.location + lenStart, r.length - lenStart - lenEnd); |
| | | [text yy_setColor:_codeTextColor range:codeR]; |
| | | [text yy_setFont:_monospaceFont range:codeR]; |
| | | YYTextBorder *border = [YYTextBorder new]; |
| | | border.lineStyle = YYTextLineStyleSingle; |
| | | border.fillColor = [UIColor colorWithWhite:0.184 alpha:0.090]; |
| | | border.strokeColor = [UIColor colorWithWhite:0.200 alpha:0.300]; |
| | | border.insets = UIEdgeInsetsMake(-1, 0, -1, 0); |
| | | border.cornerRadius = 3; |
| | | border.strokeWidth = YYTextCGFloatFromPixel(2); |
| | | [text yy_setTextBlockBorder:_border.copy range:codeR]; |
| | | } |
| | | }]; |
| | | |
| | | return YES; |
| | | } |
| | | |
| | | |
| | | @end |
| | | |
| | | |
| | | |
| | | #pragma mark - Emoticon Parser |
| | | |
| | | #define LOCK(...) dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \ |
| | | __VA_ARGS__; \ |
| | | dispatch_semaphore_signal(_lock); |
| | | |
| | | @implementation YYTextSimpleEmoticonParser { |
| | | NSRegularExpression *_regex; |
| | | NSDictionary *_mapper; |
| | | dispatch_semaphore_t _lock; |
| | | } |
| | | |
| | | - (instancetype)init { |
| | | self = [super init]; |
| | | _lock = dispatch_semaphore_create(1); |
| | | return self; |
| | | } |
| | | |
| | | - (NSDictionary *)emoticonMapper { |
| | | LOCK(NSDictionary *mapper = _mapper); return mapper; |
| | | } |
| | | |
| | | - (void)setEmoticonMapper:(NSDictionary *)emoticonMapper { |
| | | LOCK( |
| | | _mapper = emoticonMapper.copy; |
| | | if (_mapper.count == 0) { |
| | | _regex = nil; |
| | | } else { |
| | | NSMutableString *pattern = @"(".mutableCopy; |
| | | NSArray *allKeys = _mapper.allKeys; |
| | | NSCharacterSet *charset = [NSCharacterSet characterSetWithCharactersInString:@"$^?+*.,#|{}[]()\\"]; |
| | | for (NSUInteger i = 0, max = allKeys.count; i < max; i++) { |
| | | NSMutableString *one = [allKeys[i] mutableCopy]; |
| | | |
| | | // escape regex characters |
| | | for (NSUInteger ci = 0, cmax = one.length; ci < cmax; ci++) { |
| | | unichar c = [one characterAtIndex:ci]; |
| | | if ([charset characterIsMember:c]) { |
| | | [one insertString:@"\\" atIndex:ci]; |
| | | ci++; |
| | | cmax++; |
| | | } |
| | | } |
| | | |
| | | [pattern appendString:one]; |
| | | if (i != max - 1) [pattern appendString:@"|"]; |
| | | } |
| | | [pattern appendString:@")"]; |
| | | _regex = [[NSRegularExpression alloc] initWithPattern:pattern options:kNilOptions error:nil]; |
| | | } |
| | | ); |
| | | } |
| | | |
| | | // correct the selected range during text replacement |
| | | - (NSRange)_replaceTextInRange:(NSRange)range withLength:(NSUInteger)length selectedRange:(NSRange)selectedRange { |
| | | // no change |
| | | if (range.length == length) return selectedRange; |
| | | // right |
| | | if (range.location >= selectedRange.location + selectedRange.length) return selectedRange; |
| | | // left |
| | | if (selectedRange.location >= range.location + range.length) { |
| | | selectedRange.location = selectedRange.location + length - range.length; |
| | | return selectedRange; |
| | | } |
| | | // same |
| | | if (NSEqualRanges(range, selectedRange)) { |
| | | selectedRange.length = length; |
| | | return selectedRange; |
| | | } |
| | | // one edge same |
| | | if ((range.location == selectedRange.location && range.length < selectedRange.length) || |
| | | (range.location + range.length == selectedRange.location + selectedRange.length && range.length < selectedRange.length)) { |
| | | selectedRange.length = selectedRange.length + length - range.length; |
| | | return selectedRange; |
| | | } |
| | | selectedRange.location = range.location + length; |
| | | selectedRange.length = 0; |
| | | return selectedRange; |
| | | } |
| | | |
| | | - (BOOL)parseText:(NSMutableAttributedString *)text selectedRange:(NSRangePointer)range { |
| | | if (text.length == 0) return NO; |
| | | |
| | | NSDictionary *mapper; |
| | | NSRegularExpression *regex; |
| | | LOCK(mapper = _mapper; regex = _regex;); |
| | | if (mapper.count == 0 || regex == nil) return NO; |
| | | |
| | | NSArray *matches = [regex matchesInString:text.string options:kNilOptions range:NSMakeRange(0, text.length)]; |
| | | if (matches.count == 0) return NO; |
| | | |
| | | NSRange selectedRange = range ? *range : NSMakeRange(0, 0); |
| | | NSUInteger cutLength = 0; |
| | | for (NSUInteger i = 0, max = matches.count; i < max; i++) { |
| | | NSTextCheckingResult *one = matches[i]; |
| | | NSRange oneRange = one.range; |
| | | if (oneRange.length == 0) continue; |
| | | oneRange.location -= cutLength; |
| | | NSString *subStr = [text.string substringWithRange:oneRange]; |
| | | UIImage *emoticon = mapper[subStr]; |
| | | if (!emoticon) continue; |
| | | |
| | | CGFloat fontSize = 12; // CoreText default value |
| | | CTFontRef font = (__bridge CTFontRef)([text yy_attribute:NSFontAttributeName atIndex:oneRange.location]); |
| | | if (font) fontSize = CTFontGetSize(font); |
| | | NSMutableAttributedString *atr = [NSAttributedString yy_attachmentStringWithEmojiImage:emoticon fontSize:fontSize]; |
| | | [atr yy_setTextBackedString:[YYTextBackedString stringWithString:subStr] range:NSMakeRange(0, atr.length)]; |
| | | [text replaceCharactersInRange:oneRange withString:atr.string]; |
| | | [text yy_removeDiscontinuousAttributesInRange:NSMakeRange(oneRange.location, atr.length)]; |
| | | [text addAttributes:atr.yy_attributes range:NSMakeRange(oneRange.location, atr.length)]; |
| | | selectedRange = [self _replaceTextInRange:oneRange withLength:atr.length selectedRange:selectedRange]; |
| | | cutLength += oneRange.length - 1; |
| | | } |
| | | if (range) *range = selectedRange; |
| | | |
| | | return YES; |
| | | } |
| | | @end |