YYTextParser.m 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. //
  2. // YYTextParser.m
  3. // YYKit <https://github.com/ibireme/YYKit>
  4. //
  5. // Created by ibireme on 15/3/6.
  6. // Copyright (c) 2015 ibireme.
  7. //
  8. // This source code is licensed under the MIT-style license found in the
  9. // LICENSE file in the root directory of this source tree.
  10. //
  11. #import "YYTextParser.h"
  12. #import "YYCGUtilities.h"
  13. #import "YYTextUtilities.h"
  14. #import "YYTextAttribute.h"
  15. #import "NSAttributedString+YYText.h"
  16. #import "NSParagraphStyle+YYText.h"
  17. #import "UIFont+YYAdd.h"
  18. #pragma mark - Markdown Parser
  19. @implementation YYTextSimpleMarkdownParser {
  20. UIFont *_font;
  21. NSMutableArray *_headerFonts; ///< h1~h6
  22. UIFont *_boldFont;
  23. UIFont *_italicFont;
  24. UIFont *_boldItalicFont;
  25. UIFont *_monospaceFont;
  26. YYTextBorder *_border;
  27. NSRegularExpression *_regexEscape; ///< escape
  28. NSRegularExpression *_regexHeader; ///< #header
  29. NSRegularExpression *_regexH1; ///< header\n====
  30. NSRegularExpression *_regexH2; ///< header\n----
  31. NSRegularExpression *_regexBreakline; ///< ******
  32. NSRegularExpression *_regexEmphasis; ///< *text* _text_
  33. NSRegularExpression *_regexStrong; ///< **text**
  34. NSRegularExpression *_regexStrongEmphasis; ///< ***text*** ___text___
  35. NSRegularExpression *_regexUnderline; ///< __text__
  36. NSRegularExpression *_regexStrikethrough; ///< ~~text~~
  37. NSRegularExpression *_regexInlineCode; ///< `text`
  38. NSRegularExpression *_regexLink; ///< [name](link)
  39. NSRegularExpression *_regexLinkRefer; ///< [ref]:
  40. NSRegularExpression *_regexList; ///< 1.text 2.text 3.text
  41. NSRegularExpression *_regexBlockQuote; ///< > quote
  42. NSRegularExpression *_regexCodeBlock; ///< \tcode \tcode
  43. NSRegularExpression *_regexNotEmptyLine;
  44. }
  45. - (void)initRegex {
  46. #define regexp(reg, option) [NSRegularExpression regularExpressionWithPattern : @reg options : option error : NULL]
  47. _regexEscape = regexp("(\\\\\\\\|\\\\\\`|\\\\\\*|\\\\\\_|\\\\\\(|\\\\\\)|\\\\\\[|\\\\\\]|\\\\#|\\\\\\+|\\\\\\-|\\\\\\!)", 0);
  48. _regexHeader = regexp("^((\\#{1,6}[^#].*)|(\\#{6}.+))$", NSRegularExpressionAnchorsMatchLines);
  49. _regexH1 = regexp("^[^=\\n][^\\n]*\\n=+$", NSRegularExpressionAnchorsMatchLines);
  50. _regexH2 = regexp("^[^-\\n][^\\n]*\\n-+$", NSRegularExpressionAnchorsMatchLines);
  51. _regexBreakline = regexp("^[ \\t]*([*-])[ \\t]*((\\1)[ \\t]*){2,}[ \\t]*$", NSRegularExpressionAnchorsMatchLines);
  52. _regexEmphasis = regexp("((?<!\\*)\\*(?=[^ \\t*])(.+?)(?<=[^ \\t*])\\*(?!\\*)|(?<!_)_(?=[^ \\t_])(.+?)(?<=[^ \\t_])_(?!_))", 0);
  53. _regexStrong = regexp("(?<!\\*)\\*{2}(?=[^ \\t*])(.+?)(?<=[^ \\t*])\\*{2}(?!\\*)", 0);
  54. _regexStrongEmphasis = regexp("((?<!\\*)\\*{3}(?=[^ \\t*])(.+?)(?<=[^ \\t*])\\*{3}(?!\\*)|(?<!_)_{3}(?=[^ \\t_])(.+?)(?<=[^ \\t_])_{3}(?!_))", 0);
  55. _regexUnderline = regexp("(?<!_)__(?=[^ \\t_])(.+?)(?<=[^ \\t_])\\__(?!_)", 0);
  56. _regexStrikethrough = regexp("(?<!~)~~(?=[^ \\t~])(.+?)(?<=[^ \\t~])\\~~(?!~)", 0);
  57. _regexInlineCode = regexp("(?<!`)(`{1,3})([^`\n]+?)\\1(?!`)", 0);
  58. _regexLink = regexp("!?\\[([^\\[\\]]+)\\](\\(([^\\(\\)]+)\\)|\\[([^\\[\\]]+)\\])", 0);
  59. _regexLinkRefer = regexp("^[ \\t]*\\[[^\\[\\]]\\]:", NSRegularExpressionAnchorsMatchLines);
  60. _regexList = regexp("^[ \\t]*([*+-]|\\d+[.])[ \\t]+", NSRegularExpressionAnchorsMatchLines);
  61. _regexBlockQuote = regexp("^[ \\t]*>[ \\t>]*", NSRegularExpressionAnchorsMatchLines);
  62. _regexCodeBlock = regexp("(^\\s*$\\n)((( {4}|\\t).*(\\n|\\z))|(^\\s*$\\n))+", NSRegularExpressionAnchorsMatchLines);
  63. _regexNotEmptyLine = regexp("^[ \\t]*[^ \\t]+[ \\t]*$", NSRegularExpressionAnchorsMatchLines);
  64. #undef regexp
  65. }
  66. - (instancetype)init {
  67. self = [super init];
  68. _fontSize = 14;
  69. _headerFontSize = 20;
  70. [self _updateFonts];
  71. [self setColorWithBrightTheme];
  72. [self initRegex];
  73. return self;
  74. }
  75. - (void)setFontSize:(CGFloat)fontSize {
  76. if (fontSize < 1) fontSize = 12;
  77. _fontSize = fontSize;
  78. [self _updateFonts];
  79. }
  80. - (void)setHeaderFontSize:(CGFloat)headerFontSize {
  81. if (headerFontSize < 1) headerFontSize = 20;
  82. _headerFontSize = headerFontSize;
  83. [self _updateFonts];
  84. }
  85. - (void)_updateFonts {
  86. _font = [UIFont systemFontOfSize:_fontSize];
  87. _headerFonts = [NSMutableArray new];
  88. for (int i = 0; i < 6; i++) {
  89. CGFloat size = _headerFontSize - (_headerFontSize - _fontSize) / 5.0 * i;
  90. [_headerFonts addObject:[UIFont systemFontOfSize:size]];
  91. }
  92. _boldFont = [_font fontWithBold];
  93. _italicFont = [_font fontWithItalic];
  94. _boldItalicFont = [_font fontWithBoldItalic];
  95. _monospaceFont = [UIFont fontWithName:@"Menlo" size:_fontSize]; // Since iOS 7
  96. if (!_monospaceFont) _monospaceFont = [UIFont fontWithName:@"Courier" size:_fontSize]; // Since iOS 3
  97. }
  98. - (void)setColorWithBrightTheme {
  99. _textColor = [UIColor blackColor];
  100. _controlTextColor = [UIColor colorWithWhite:0.749 alpha:1.000];
  101. _headerTextColor = [UIColor colorWithRed:1.000 green:0.502 blue:0.000 alpha:1.000];
  102. _inlineTextColor = [UIColor colorWithWhite:0.150 alpha:1.000];
  103. _codeTextColor = [UIColor colorWithWhite:0.150 alpha:1.000];
  104. _linkTextColor = [UIColor colorWithRed:0.000 green:0.478 blue:0.962 alpha:1.000];
  105. _border = [YYTextBorder new];
  106. _border.lineStyle = YYTextLineStyleSingle;
  107. _border.fillColor = [UIColor colorWithWhite:0.184 alpha:0.090];
  108. _border.strokeColor = [UIColor colorWithWhite:0.546 alpha:0.650];
  109. _border.insets = UIEdgeInsetsMake(-1, 0, -1, 0);
  110. _border.cornerRadius = 2;
  111. _border.strokeWidth = CGFloatFromPixel(1);
  112. }
  113. - (void)setColorWithDarkTheme {
  114. _textColor = [UIColor whiteColor];
  115. _controlTextColor = [UIColor colorWithWhite:0.604 alpha:1.000];
  116. _headerTextColor = [UIColor colorWithRed:0.558 green:1.000 blue:0.502 alpha:1.000];
  117. _inlineTextColor = [UIColor colorWithRed:1.000 green:0.862 blue:0.387 alpha:1.000];
  118. _codeTextColor = [UIColor colorWithWhite:0.906 alpha:1.000];
  119. _linkTextColor = [UIColor colorWithRed:0.000 green:0.646 blue:1.000 alpha:1.000];
  120. _border = [YYTextBorder new];
  121. _border.lineStyle = YYTextLineStyleSingle;
  122. _border.fillColor = [UIColor colorWithWhite:0.820 alpha:0.130];
  123. _border.strokeColor = [UIColor colorWithWhite:1.000 alpha:0.280];
  124. _border.insets = UIEdgeInsetsMake(-1, 0, -1, 0);
  125. _border.cornerRadius = 2;
  126. _border.strokeWidth = CGFloatFromPixel(1);
  127. }
  128. - (NSUInteger)lenghOfBeginWhiteInString:(NSString *)str withRange:(NSRange)range{
  129. for (NSUInteger i = 0; i < range.length; i++) {
  130. unichar c = [str characterAtIndex:i + range.location];
  131. if (c != ' ' && c != '\t' && c != '\n') return i;
  132. }
  133. return str.length;
  134. }
  135. - (NSUInteger)lenghOfEndWhiteInString:(NSString *)str withRange:(NSRange)range{
  136. for (NSInteger i = range.length - 1; i >= 0; i--) {
  137. unichar c = [str characterAtIndex:i + range.location];
  138. if (c != ' ' && c != '\t' && c != '\n') return range.length - i;
  139. }
  140. return str.length;
  141. }
  142. - (NSUInteger)lenghOfBeginChar:(unichar)c inString:(NSString *)str withRange:(NSRange)range{
  143. for (NSUInteger i = 0; i < range.length; i++) {
  144. if ([str characterAtIndex:i + range.location] != c) return i;
  145. }
  146. return str.length;
  147. }
  148. - (BOOL)parseText:(NSMutableAttributedString *)text selectedRange:(NSRangePointer)range {
  149. if (text.length == 0) return NO;
  150. [text removeAttributesInRange:NSMakeRange(0, text.length)];
  151. text.font = _font;
  152. text.color = _textColor;
  153. NSMutableString *str = text.string.mutableCopy;
  154. [_regexEscape replaceMatchesInString:str options:kNilOptions range:NSMakeRange(0, str.length) withTemplate:@"@@"];
  155. [_regexHeader enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
  156. NSRange r = result.range;
  157. NSUInteger whiteLen = [self lenghOfBeginWhiteInString:str withRange:r];
  158. NSUInteger sharpLen = [self lenghOfBeginChar:'#' inString:str withRange:NSMakeRange(r.location + whiteLen, r.length - whiteLen)];
  159. if (sharpLen > 6) sharpLen = 6;
  160. [text setColor:_controlTextColor range:NSMakeRange(r.location, whiteLen + sharpLen)];
  161. [text setColor:_headerTextColor range:NSMakeRange(r.location + whiteLen + sharpLen, r.length - whiteLen - sharpLen)];
  162. [text setFont:_headerFonts[sharpLen - 1] range:result.range];
  163. }];
  164. [_regexH1 enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
  165. NSRange r = result.range;
  166. NSRange linebreak = [str rangeOfString:@"\n" options:0 range:result.range locale:nil];
  167. if (linebreak.location != NSNotFound) {
  168. [text setColor:_headerTextColor range:NSMakeRange(r.location, linebreak.location - r.location)];
  169. [text setFont:_headerFonts[0] range:NSMakeRange(r.location, linebreak.location - r.location + 1)];
  170. [text setColor:_controlTextColor range:NSMakeRange(linebreak.location + linebreak.length, r.location + r.length - linebreak.location - linebreak.length)];
  171. }
  172. }];
  173. [_regexH2 enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
  174. NSRange r = result.range;
  175. NSRange linebreak = [str rangeOfString:@"\n" options:0 range:result.range locale:nil];
  176. if (linebreak.location != NSNotFound) {
  177. [text setColor:_headerTextColor range:NSMakeRange(r.location, linebreak.location - r.location)];
  178. [text setFont:_headerFonts[1] range:NSMakeRange(r.location, linebreak.location - r.location + 1)];
  179. [text setColor:_controlTextColor range:NSMakeRange(linebreak.location + linebreak.length, r.location + r.length - linebreak.location - linebreak.length)];
  180. }
  181. }];
  182. [_regexBreakline enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
  183. [text setColor:_controlTextColor range:result.range];
  184. }];
  185. [_regexEmphasis enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
  186. NSRange r = result.range;
  187. [text setColor:_controlTextColor range:NSMakeRange(r.location, 1)];
  188. [text setColor:_controlTextColor range:NSMakeRange(r.location + r.length - 1, 1)];
  189. [text setFont:_italicFont range:NSMakeRange(r.location + 1, r.length - 2)];
  190. }];
  191. [_regexStrong enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
  192. NSRange r = result.range;
  193. [text setColor:_controlTextColor range:NSMakeRange(r.location, 2)];
  194. [text setColor:_controlTextColor range:NSMakeRange(r.location + r.length - 2, 2)];
  195. [text setFont:_boldFont range:NSMakeRange(r.location + 2, r.length - 4)];
  196. }];
  197. [_regexStrongEmphasis enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
  198. NSRange r = result.range;
  199. [text setColor:_controlTextColor range:NSMakeRange(r.location, 3)];
  200. [text setColor:_controlTextColor range:NSMakeRange(r.location + r.length - 3, 3)];
  201. [text setFont:_boldItalicFont range:NSMakeRange(r.location + 3, r.length - 6)];
  202. }];
  203. [_regexUnderline enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
  204. NSRange r = result.range;
  205. [text setColor:_controlTextColor range:NSMakeRange(r.location, 2)];
  206. [text setColor:_controlTextColor range:NSMakeRange(r.location + r.length - 2, 2)];
  207. [text setTextUnderline:[YYTextDecoration decorationWithStyle:YYTextLineStyleSingle width:@1 color:nil] range:NSMakeRange(r.location + 2, r.length - 4)];
  208. }];
  209. [_regexStrikethrough enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
  210. NSRange r = result.range;
  211. [text setColor:_controlTextColor range:NSMakeRange(r.location, 2)];
  212. [text setColor:_controlTextColor range:NSMakeRange(r.location + r.length - 2, 2)];
  213. [text setTextStrikethrough:[YYTextDecoration decorationWithStyle:YYTextLineStyleSingle width:@1 color:nil] range:NSMakeRange(r.location + 2, r.length - 4)];
  214. }];
  215. [_regexInlineCode enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
  216. NSRange r = result.range;
  217. NSUInteger len = [self lenghOfBeginChar:'`' inString:str withRange:r];
  218. [text setColor:_controlTextColor range:NSMakeRange(r.location, len)];
  219. [text setColor:_controlTextColor range:NSMakeRange(r.location + r.length - len, len)];
  220. [text setColor:_inlineTextColor range:NSMakeRange(r.location + len, r.length - 2 * len)];
  221. [text setFont:_monospaceFont range:r];
  222. [text setTextBorder:_border.copy range:r];
  223. }];
  224. [_regexLink enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
  225. NSRange r = result.range;
  226. [text setColor:_linkTextColor range:r];
  227. }];
  228. [_regexLinkRefer enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
  229. NSRange r = result.range;
  230. [text setColor:_controlTextColor range:r];
  231. }];
  232. [_regexList enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
  233. NSRange r = result.range;
  234. [text setColor:_controlTextColor range:r];
  235. }];
  236. [_regexBlockQuote enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
  237. NSRange r = result.range;
  238. [text setColor:_controlTextColor range:r];
  239. }];
  240. [_regexCodeBlock enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
  241. NSRange r = result.range;
  242. NSRange firstLineRange = [_regexNotEmptyLine rangeOfFirstMatchInString:str options:kNilOptions range:r];
  243. NSUInteger lenStart = (firstLineRange.location != NSNotFound) ? firstLineRange.location - r.location : 0;
  244. NSUInteger lenEnd = [self lenghOfEndWhiteInString:str withRange:r];
  245. if (lenStart + lenEnd < r.length) {
  246. NSRange codeR = NSMakeRange(r.location + lenStart, r.length - lenStart - lenEnd);
  247. [text setColor:_codeTextColor range:codeR];
  248. [text setFont:_monospaceFont range:codeR];
  249. YYTextBorder *border = [YYTextBorder new];
  250. border.lineStyle = YYTextLineStyleSingle;
  251. border.fillColor = [UIColor colorWithWhite:0.184 alpha:0.090];
  252. border.strokeColor = [UIColor colorWithWhite:0.200 alpha:0.300];
  253. border.insets = UIEdgeInsetsMake(-1, 0, -1, 0);
  254. border.cornerRadius = 3;
  255. border.strokeWidth = CGFloatFromPixel(2);
  256. [text setTextBlockBorder:_border.copy range:codeR];
  257. }
  258. }];
  259. return YES;
  260. }
  261. @end
  262. #pragma mark - Emoticon Parser
  263. #define LOCK(...) dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \
  264. __VA_ARGS__; \
  265. dispatch_semaphore_signal(_lock);
  266. @implementation YYTextSimpleEmoticonParser {
  267. NSRegularExpression *_regex;
  268. NSDictionary *_mapper;
  269. dispatch_semaphore_t _lock;
  270. }
  271. - (instancetype)init {
  272. self = [super init];
  273. _lock = dispatch_semaphore_create(1);
  274. return self;
  275. }
  276. - (NSDictionary *)emoticonMapper {
  277. LOCK(NSDictionary *mapper = _mapper); return mapper;
  278. }
  279. - (void)setEmoticonMapper:(NSDictionary *)emoticonMapper {
  280. LOCK(
  281. _mapper = emoticonMapper.copy;
  282. if (_mapper.count == 0) {
  283. _regex = nil;
  284. } else {
  285. NSMutableString *pattern = @"(".mutableCopy;
  286. NSArray *allKeys = _mapper.allKeys;
  287. NSCharacterSet *charset = [NSCharacterSet characterSetWithCharactersInString:@"$^?+*.,#|{}[]()\\"];
  288. for (NSUInteger i = 0, max = allKeys.count; i < max; i++) {
  289. NSMutableString *one = [allKeys[i] mutableCopy];
  290. // escape regex characters
  291. for (NSUInteger ci = 0, cmax = one.length; ci < cmax; ci++) {
  292. unichar c = [one characterAtIndex:ci];
  293. if ([charset characterIsMember:c]) {
  294. [one insertString:@"\\" atIndex:ci];
  295. ci++;
  296. cmax++;
  297. }
  298. }
  299. [pattern appendString:one];
  300. if (i != max - 1) [pattern appendString:@"|"];
  301. }
  302. [pattern appendString:@")"];
  303. _regex = [[NSRegularExpression alloc] initWithPattern:pattern options:kNilOptions error:nil];
  304. }
  305. );
  306. }
  307. // correct the selected range during text replacement
  308. - (NSRange)_replaceTextInRange:(NSRange)range withLength:(NSUInteger)length selectedRange:(NSRange)selectedRange {
  309. // no change
  310. if (range.length == length) return selectedRange;
  311. // right
  312. if (range.location >= selectedRange.location + selectedRange.length) return selectedRange;
  313. // left
  314. if (selectedRange.location >= range.location + range.length) {
  315. selectedRange.location = selectedRange.location + length - range.length;
  316. return selectedRange;
  317. }
  318. // same
  319. if (NSEqualRanges(range, selectedRange)) {
  320. selectedRange.length = length;
  321. return selectedRange;
  322. }
  323. // one edge same
  324. if ((range.location == selectedRange.location && range.length < selectedRange.length) ||
  325. (range.location + range.length == selectedRange.location + selectedRange.length && range.length < selectedRange.length)) {
  326. selectedRange.length = selectedRange.length + length - range.length;
  327. return selectedRange;
  328. }
  329. selectedRange.location = range.location + length;
  330. selectedRange.length = 0;
  331. return selectedRange;
  332. }
  333. - (BOOL)parseText:(NSMutableAttributedString *)text selectedRange:(NSRangePointer)range {
  334. if (text.length == 0) return NO;
  335. NSDictionary *mapper;
  336. NSRegularExpression *regex;
  337. LOCK(mapper = _mapper; regex = _regex;);
  338. if (mapper.count == 0 || regex == nil) return NO;
  339. NSArray *matches = [regex matchesInString:text.string options:kNilOptions range:NSMakeRange(0, text.length)];
  340. if (matches.count == 0) return NO;
  341. NSRange selectedRange = range ? *range : NSMakeRange(0, 0);
  342. NSUInteger cutLength = 0;
  343. for (NSUInteger i = 0, max = matches.count; i < max; i++) {
  344. NSTextCheckingResult *one = matches[i];
  345. NSRange oneRange = one.range;
  346. if (oneRange.length == 0) continue;
  347. oneRange.location -= cutLength;
  348. NSString *subStr = [text.string substringWithRange:oneRange];
  349. UIImage *emoticon = mapper[subStr];
  350. if (!emoticon) continue;
  351. CGFloat fontSize = 12; // CoreText default value
  352. CTFontRef font = (__bridge CTFontRef)([text attribute:NSFontAttributeName atIndex:oneRange.location]);
  353. if (font) fontSize = CTFontGetSize(font);
  354. NSMutableAttributedString *atr = [NSAttributedString attachmentStringWithEmojiImage:emoticon fontSize:fontSize];
  355. [atr setTextBackedString:[YYTextBackedString stringWithString:subStr] range:NSMakeRange(0, atr.length)];
  356. [text replaceCharactersInRange:oneRange withString:atr.string];
  357. [text removeDiscontinuousAttributesInRange:NSMakeRange(oneRange.location, atr.length)];
  358. [text addAttributes:atr.attributes range:NSMakeRange(oneRange.location, atr.length)];
  359. selectedRange = [self _replaceTextInRange:oneRange withLength:atr.length selectedRange:selectedRange];
  360. cutLength += oneRange.length - 1;
  361. }
  362. if (range) *range = selectedRange;
  363. return YES;
  364. }
  365. @end