RTLabel.m 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095
  1. //
  2. // RTLabel.m
  3. // RTLabelProject
  4. //
  5. /**
  6. * Copyright (c) 2010 Muh Hon Cheng
  7. * Created by honcheng on 1/6/11.
  8. *
  9. * Permission is hereby granted, free of charge, to any person obtaining
  10. * a copy of this software and associated documentation files (the
  11. * "Software"), to deal in the Software without restriction, including
  12. * without limitation the rights to use, copy, modify, merge, publish,
  13. * distribute, sublicense, and/or sell copies of the Software, and to
  14. * permit persons to whom the Software is furnished to do so, subject
  15. * to the following conditions:
  16. *
  17. * The above copyright notice and this permission notice shall be
  18. * included in all copies or substantial portions of the Software.
  19. *
  20. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT
  21. * WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  22. * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  23. * MERCHANTABILITY, FITNESS FOR A PARTICULAR
  24. * PURPOSE AND NONINFRINGEMENT. IN NO EVENT
  25. * SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  26. * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  27. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  28. * TORT OR OTHERWISE, ARISING FROM, OUT OF OR
  29. * IN CONNECTION WITH THE SOFTWARE OR
  30. * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  31. *
  32. * @author Muh Hon Cheng <honcheng@gmail.com>
  33. * @copyright 2011 Muh Hon Cheng
  34. * @version
  35. *
  36. */
  37. #import "RTLabel.h"
  38. @interface RTLabelButton : UIButton
  39. @property (nonatomic, assign) long componentIndex;
  40. @property (nonatomic) NSURL *url;
  41. @end
  42. @implementation RTLabelButton
  43. @end
  44. @implementation RTLabelComponent
  45. - (id)initWithString:(NSString*)aText tag:(NSString*)aTagLabel attributes:(NSMutableDictionary*)theAttributes
  46. {
  47. self = [super init];
  48. if (self) {
  49. _text = aText;
  50. _tagLabel = aTagLabel;
  51. _attributes = theAttributes;
  52. }
  53. return self;
  54. }
  55. + (id)componentWithString:(NSString*)aText tag:(NSString*)aTagLabel attributes:(NSMutableDictionary*)theAttributes
  56. {
  57. return [[self alloc] initWithString:aText tag:aTagLabel attributes:theAttributes];
  58. }
  59. - (id)initWithTag:(NSString*)aTagLabel position:(int)aPosition attributes:(NSMutableDictionary*)theAttributes
  60. {
  61. self = [super init];
  62. if (self) {
  63. _tagLabel = aTagLabel;
  64. _position = aPosition;
  65. _attributes = theAttributes;
  66. }
  67. return self;
  68. }
  69. +(id)componentWithTag:(NSString*)aTagLabel position:(int)aPosition attributes:(NSMutableDictionary*)theAttributes
  70. {
  71. return [[self alloc] initWithTag:aTagLabel position:aPosition attributes:theAttributes];
  72. }
  73. - (NSString*)description
  74. {
  75. NSMutableString *desc = [NSMutableString string];
  76. [desc appendFormat:@"text: %@", self.text];
  77. [desc appendFormat:@", position: %li", self.position];
  78. if (self.tagLabel) [desc appendFormat:@", tag: %@", self.tagLabel];
  79. if (self.attributes) [desc appendFormat:@", attributes: %@", self.attributes];
  80. return desc;
  81. }
  82. @end
  83. @implementation RTLabelExtractedComponent
  84. + (RTLabelExtractedComponent*)rtLabelExtractComponentsWithTextComponent:(NSMutableArray*)textComponents plainText:(NSString*)plainText
  85. {
  86. RTLabelExtractedComponent *component = [[RTLabelExtractedComponent alloc] init];
  87. [component setTextComponents:textComponents];
  88. [component setPlainText:plainText];
  89. return component;
  90. }
  91. @end
  92. @interface RTLabel()
  93. - (CGFloat)frameHeight:(CTFrameRef)frame;
  94. - (NSArray *)components;
  95. - (void)parse:(NSString *)data valid_tags:(NSArray *)valid_tags;
  96. - (NSArray*) colorForHex:(NSString *)hexColor;
  97. - (void)render;
  98. #pragma mark -
  99. #pragma mark styling
  100. - (void)applyItalicStyleToText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length;
  101. - (void)applyBoldStyleToText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length;
  102. - (void)applyBoldItalicStyleToText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length;
  103. - (void)applyColor:(NSString*)value toText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length;
  104. - (void)applySingleUnderlineText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length;
  105. - (void)applyDoubleUnderlineText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length;
  106. - (void)applyUnderlineColor:(NSString*)value toText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length;
  107. - (void)applyFontAttributes:(NSDictionary*)attributes toText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length;
  108. - (void)applyParagraphStyleToText:(CFMutableAttributedStringRef)text attributes:(NSMutableDictionary*)attributes atPosition:(long)position withLength:(long)length;
  109. @end
  110. @implementation RTLabel
  111. - (id)initWithFrame:(CGRect)_frame
  112. {
  113. self = [super initWithFrame:_frame];
  114. if (self)
  115. {
  116. [self initialize];
  117. }
  118. return self;
  119. }
  120. - (id)initWithCoder:(NSCoder *)aDecoder
  121. {
  122. self = [super initWithCoder:aDecoder];
  123. if (self)
  124. {
  125. [self initialize];
  126. }
  127. return self;
  128. }
  129. - (void)initialize
  130. {
  131. [self setBackgroundColor:[UIColor clearColor]];
  132. _font = [UIFont systemFontOfSize:17];
  133. _textColor = [UIColor blackColor];
  134. _text = @"";
  135. _textAlignment = RTTextAlignmentLeft;
  136. _lineBreakMode = RTTextLineBreakModeWordWrapping;
  137. _lineSpacing = 7;
  138. _currentSelectedButtonComponentIndex = -1;
  139. _paragraphReplacement = @"\n";
  140. [self setMultipleTouchEnabled:YES];
  141. }
  142. - (void)setTextAlignment:(RTTextAlignment)textAlignment
  143. {
  144. _textAlignment = textAlignment;
  145. [self setNeedsDisplay];
  146. }
  147. - (void)setLineBreakMode:(RTTextLineBreakMode)lineBreakMode
  148. {
  149. _lineBreakMode = lineBreakMode;
  150. [self setNeedsDisplay];
  151. }
  152. - (void)drawRect:(CGRect)rect
  153. {
  154. [self render];
  155. }
  156. - (void)render
  157. {
  158. if (self.currentSelectedButtonComponentIndex==-1)
  159. {
  160. for (id view in [self subviews])
  161. {
  162. if ([view isKindOfClass:[UIView class]])
  163. {
  164. [view removeFromSuperview];
  165. }
  166. }
  167. }
  168. if (!self.plainText) return;
  169. CGContextRef context = UIGraphicsGetCurrentContext();
  170. if (context != NULL)
  171. {
  172. // Drawing code.
  173. CGContextSetTextMatrix(context, CGAffineTransformIdentity);
  174. CGAffineTransform flipVertical = CGAffineTransformMake(1,0,0,-1,0,self.frame.size.height);
  175. CGContextConcatCTM(context, flipVertical);
  176. }
  177. // Initialize an attributed string.
  178. CFStringRef string = (__bridge CFStringRef)self.plainText;
  179. CFMutableAttributedStringRef attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
  180. CFAttributedStringReplaceString (attrString, CFRangeMake(0, 0), string);
  181. CFMutableDictionaryRef styleDict1 = ( CFDictionaryCreateMutable( (0), 0, (0), (0) ) );
  182. // Create a color and add it as an attribute to the string.
  183. CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
  184. CGColorSpaceRelease(rgbColorSpace);
  185. CFDictionaryAddValue( styleDict1, kCTForegroundColorAttributeName, [self.textColor CGColor] );
  186. CFAttributedStringSetAttributes( attrString, CFRangeMake( 0, CFAttributedStringGetLength(attrString) ), styleDict1, 0 );
  187. CFMutableDictionaryRef styleDict = ( CFDictionaryCreateMutable( (0), 0, (0), (0) ) );
  188. [self applyParagraphStyleToText:attrString attributes:nil atPosition:0 withLength:CFAttributedStringGetLength(attrString)];
  189. CTFontRef thisFont = CTFontCreateWithName ((__bridge CFStringRef)[self.font fontName], [self.font pointSize], NULL);
  190. CFAttributedStringSetAttribute(attrString, CFRangeMake(0, CFAttributedStringGetLength(attrString)), kCTFontAttributeName, thisFont);
  191. NSMutableArray *links = [NSMutableArray array];
  192. NSMutableArray *textComponents = nil;
  193. if (self.highlighted) textComponents = self.highlightedTextComponents;
  194. else textComponents = self.textComponents;
  195. for (RTLabelComponent *component in textComponents)
  196. {
  197. if(component.text.length==0)
  198. continue;
  199. long index = [textComponents indexOfObject:component];
  200. component.componentIndex = index;
  201. if ([component.tagLabel caseInsensitiveCompare:@"i"] == NSOrderedSame)
  202. {
  203. // make font italic
  204. [self applyItalicStyleToText:attrString atPosition:component.position withLength:[component.text length]];
  205. }
  206. else if ([component.tagLabel caseInsensitiveCompare:@"b"] == NSOrderedSame)
  207. {
  208. // make font bold
  209. [self applyBoldStyleToText:attrString atPosition:component.position withLength:[component.text length]];
  210. }
  211. else if ([component.tagLabel caseInsensitiveCompare:@"bi"] == NSOrderedSame)
  212. {
  213. [self applyBoldItalicStyleToText:attrString atPosition:component.position withLength:[component.text length]];
  214. }
  215. else if ([component.tagLabel caseInsensitiveCompare:@"a"] == NSOrderedSame)
  216. {
  217. if (self.currentSelectedButtonComponentIndex==index)
  218. {
  219. if (self.selectedLinkAttributes)
  220. {
  221. [self applyFontAttributes:self.selectedLinkAttributes toText:attrString atPosition:component.position withLength:[component.text length]];
  222. }
  223. else
  224. {
  225. [self applyBoldStyleToText:attrString atPosition:component.position withLength:[component.text length]];
  226. [self applyColor:@"#FF0000" toText:attrString atPosition:component.position withLength:[component.text length]];
  227. }
  228. }
  229. else
  230. {
  231. if (self.linkAttributes)
  232. {
  233. [self applyFontAttributes:self.linkAttributes toText:attrString atPosition:component.position withLength:[component.text length]];
  234. }
  235. else
  236. {
  237. [self applyBoldStyleToText:attrString atPosition:component.position withLength:[component.text length]];
  238. [self applySingleUnderlineText:attrString atPosition:component.position withLength:[component.text length]];
  239. }
  240. }
  241. NSString *value = [component.attributes objectForKey:@"href"];
  242. value = [value stringByReplacingOccurrencesOfString:@"'" withString:@""];
  243. [component.attributes setObject:value forKey:@"href"];
  244. [links addObject:component];
  245. }
  246. else if ([component.tagLabel caseInsensitiveCompare:@"u"] == NSOrderedSame || [component.tagLabel caseInsensitiveCompare:@"uu"] == NSOrderedSame)
  247. {
  248. // underline
  249. if ([component.tagLabel caseInsensitiveCompare:@"u"] == NSOrderedSame)
  250. {
  251. [self applySingleUnderlineText:attrString atPosition:component.position withLength:[component.text length]];
  252. }
  253. else if ([component.tagLabel caseInsensitiveCompare:@"uu"] == NSOrderedSame)
  254. {
  255. [self applyDoubleUnderlineText:attrString atPosition:component.position withLength:[component.text length]];
  256. }
  257. if ([component.attributes objectForKey:@"color"])
  258. {
  259. NSString *value = [component.attributes objectForKey:@"color"];
  260. [self applyUnderlineColor:value toText:attrString atPosition:component.position withLength:[component.text length]];
  261. }
  262. }
  263. else if ([component.tagLabel caseInsensitiveCompare:@"font"] == NSOrderedSame)
  264. {
  265. [self applyFontAttributes:component.attributes toText:attrString atPosition:component.position withLength:[component.text length]];
  266. }
  267. else if ([component.tagLabel caseInsensitiveCompare:@"p"] == NSOrderedSame)
  268. {
  269. [self applyParagraphStyleToText:attrString attributes:component.attributes atPosition:component.position withLength:[component.text length]];
  270. }
  271. else if ([component.tagLabel caseInsensitiveCompare:@"center"] == NSOrderedSame)
  272. {
  273. [self applyCenterStyleToText:attrString attributes:component.attributes atPosition:component.position withLength:[component.text length]];
  274. }
  275. }
  276. // Create the framesetter with the attributed string.
  277. CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString);
  278. CFRelease(attrString);
  279. // Initialize a rectangular path.
  280. CGMutablePathRef path = CGPathCreateMutable();
  281. CGRect bounds = CGRectMake(0.0, 0.0, self.frame.size.width, self.frame.size.height);
  282. CGPathAddRect(path, NULL, bounds);
  283. // Create the frame and draw it into the graphics context
  284. //CTFrameRef
  285. CTFrameRef frame = CTFramesetterCreateFrame(framesetter,CFRangeMake(0, 0), path, NULL);
  286. CFRange range;
  287. CGSize constraint = CGSizeMake(self.frame.size.width, CGFLOAT_MAX);
  288. self.optimumSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, [self.plainText length]), nil, constraint, &range);
  289. if (self.currentSelectedButtonComponentIndex==-1)
  290. {
  291. // only check for linkable items the first time, not when it's being redrawn on button pressed
  292. for (RTLabelComponent *linkableComponents in links)
  293. {
  294. float height = 0.0;
  295. CFArrayRef frameLines = CTFrameGetLines(frame);
  296. for (CFIndex i=0; i<CFArrayGetCount(frameLines); i++)
  297. {
  298. CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(frameLines, i);
  299. CFRange lineRange = CTLineGetStringRange(line);
  300. CGFloat ascent;
  301. CGFloat descent;
  302. CGFloat leading;
  303. CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
  304. CGPoint origin;
  305. CTFrameGetLineOrigins(frame, CFRangeMake(i, 1), &origin);
  306. if ( (linkableComponents.position<lineRange.location && linkableComponents.position+linkableComponents.text.length>(u_int16_t)(lineRange.location)) || (linkableComponents.position>=lineRange.location && linkableComponents.position<lineRange.location+lineRange.length))
  307. {
  308. CGFloat secondaryOffset;
  309. CGFloat primaryOffset = CTLineGetOffsetForStringIndex(CFArrayGetValueAtIndex(frameLines,i), linkableComponents.position, &secondaryOffset);
  310. CGFloat primaryOffset2 = CTLineGetOffsetForStringIndex(CFArrayGetValueAtIndex(frameLines,i), linkableComponents.position+linkableComponents.text.length, NULL);
  311. CGFloat button_width = primaryOffset2 - primaryOffset;
  312. RTLabelButton *button = [[RTLabelButton alloc] initWithFrame:CGRectMake(primaryOffset+origin.x, height, button_width, ascent+descent)];
  313. [button setBackgroundColor:[UIColor colorWithWhite:0 alpha:0]];
  314. [button setComponentIndex:linkableComponents.componentIndex];
  315. [button setUrl:[NSURL URLWithString:[linkableComponents.attributes objectForKey:@"href"]]];
  316. [button addTarget:self action:@selector(onButtonTouchDown:) forControlEvents:UIControlEventTouchDown];
  317. [button addTarget:self action:@selector(onButtonTouchUpOutside:) forControlEvents:UIControlEventTouchUpOutside];
  318. [button addTarget:self action:@selector(onButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
  319. [self addSubview:button];
  320. }
  321. origin.y = self.frame.size.height - origin.y;
  322. height = origin.y + descent + _lineSpacing;
  323. }
  324. }
  325. }
  326. self.visibleRange = CTFrameGetVisibleStringRange(frame);
  327. CFRelease(thisFont);
  328. CFRelease(path);
  329. CFRelease(styleDict1);
  330. CFRelease(styleDict);
  331. CFRelease(framesetter);
  332. CTFrameDraw(frame, context);
  333. CFRelease(frame);
  334. }
  335. #pragma mark -
  336. #pragma mark styling
  337. - (void)applyParagraphStyleToText:(CFMutableAttributedStringRef)text attributes:(NSMutableDictionary*)attributes atPosition:(long)position withLength:(long)length
  338. {
  339. CFMutableDictionaryRef styleDict = ( CFDictionaryCreateMutable( (0), 0, (0), (0) ) );
  340. // direction
  341. CTWritingDirection direction = kCTWritingDirectionLeftToRight;
  342. // leading
  343. CGFloat firstLineIndent = 0.0;
  344. CGFloat headIndent = 0.0;
  345. CGFloat tailIndent = 0.0;
  346. CGFloat lineHeightMultiple = 1.0;
  347. CGFloat maxLineHeight = 0;
  348. CGFloat minLineHeight = 0;
  349. CGFloat paragraphSpacing = 0.0;
  350. CGFloat paragraphSpacingBefore = 0.0;
  351. CTTextAlignment textAlignment = (CTTextAlignment)_textAlignment;
  352. CTLineBreakMode lineBreakMode = (CTLineBreakMode)_lineBreakMode;
  353. CGFloat lineSpacing = _lineSpacing;
  354. for (NSUInteger i=0; i<[[attributes allKeys] count]; i++)
  355. {
  356. NSString *key = [[attributes allKeys] objectAtIndex:i];
  357. id value = [attributes objectForKey:key];
  358. if ([key caseInsensitiveCompare:@"align"] == NSOrderedSame)
  359. {
  360. if ([value caseInsensitiveCompare:@"left"] == NSOrderedSame)
  361. {
  362. textAlignment = kCTTextAlignmentLeft;
  363. }
  364. else if ([value caseInsensitiveCompare:@"right"] == NSOrderedSame)
  365. {
  366. textAlignment = kCTTextAlignmentRight;
  367. }
  368. else if ([value caseInsensitiveCompare:@"justify"] == NSOrderedSame)
  369. {
  370. textAlignment = kCTTextAlignmentJustified;
  371. }
  372. else if ([value caseInsensitiveCompare:@"center"] == NSOrderedSame)
  373. {
  374. textAlignment = kCTTextAlignmentCenter;
  375. }
  376. }
  377. else if ([key caseInsensitiveCompare:@"indent"] == NSOrderedSame)
  378. {
  379. firstLineIndent = [value floatValue];
  380. }
  381. else if ([key caseInsensitiveCompare:@"linebreakmode"] == NSOrderedSame)
  382. {
  383. if ([value caseInsensitiveCompare:@"wordwrap"] == NSOrderedSame)
  384. {
  385. lineBreakMode = kCTLineBreakByWordWrapping;
  386. }
  387. else if ([value caseInsensitiveCompare:@"charwrap"] == NSOrderedSame)
  388. {
  389. lineBreakMode = kCTLineBreakByCharWrapping;
  390. }
  391. else if ([value caseInsensitiveCompare:@"clipping"] == NSOrderedSame)
  392. {
  393. lineBreakMode = kCTLineBreakByClipping;
  394. }
  395. else if ([value caseInsensitiveCompare:@"truncatinghead"] == NSOrderedSame)
  396. {
  397. lineBreakMode = kCTLineBreakByTruncatingHead;
  398. }
  399. else if ([value caseInsensitiveCompare:@"truncatingtail"] == NSOrderedSame)
  400. {
  401. lineBreakMode = kCTLineBreakByTruncatingTail;
  402. }
  403. else if ([value caseInsensitiveCompare:@"truncatingmiddle"] == NSOrderedSame)
  404. {
  405. lineBreakMode = kCTLineBreakByTruncatingMiddle;
  406. }
  407. }
  408. }
  409. CTParagraphStyleSetting theSettings[] =
  410. {
  411. { kCTParagraphStyleSpecifierAlignment, sizeof(CTTextAlignment), &textAlignment },
  412. { kCTParagraphStyleSpecifierLineBreakMode, sizeof(CTLineBreakMode), &lineBreakMode },
  413. { kCTParagraphStyleSpecifierBaseWritingDirection, sizeof(CTWritingDirection), &direction },
  414. { kCTParagraphStyleSpecifierMinimumLineSpacing, sizeof(CGFloat), &lineSpacing }, // leading
  415. { kCTParagraphStyleSpecifierMaximumLineSpacing, sizeof(CGFloat), &lineSpacing }, // leading
  416. { kCTParagraphStyleSpecifierFirstLineHeadIndent, sizeof(CGFloat), &firstLineIndent },
  417. { kCTParagraphStyleSpecifierHeadIndent, sizeof(CGFloat), &headIndent },
  418. { kCTParagraphStyleSpecifierTailIndent, sizeof(CGFloat), &tailIndent },
  419. { kCTParagraphStyleSpecifierLineHeightMultiple, sizeof(CGFloat), &lineHeightMultiple },
  420. { kCTParagraphStyleSpecifierMaximumLineHeight, sizeof(CGFloat), &maxLineHeight },
  421. { kCTParagraphStyleSpecifierMinimumLineHeight, sizeof(CGFloat), &minLineHeight },
  422. { kCTParagraphStyleSpecifierParagraphSpacing, sizeof(CGFloat), &paragraphSpacing },
  423. { kCTParagraphStyleSpecifierParagraphSpacingBefore, sizeof(CGFloat), &paragraphSpacingBefore }
  424. };
  425. CTParagraphStyleRef theParagraphRef = CTParagraphStyleCreate(theSettings, sizeof(theSettings) / sizeof(CTParagraphStyleSetting));
  426. CFDictionaryAddValue( styleDict, kCTParagraphStyleAttributeName, theParagraphRef );
  427. CFAttributedStringSetAttributes( text, CFRangeMake(position, length), styleDict, 0 );
  428. CFRelease(theParagraphRef);
  429. CFRelease(styleDict);
  430. }
  431. - (void)applyCenterStyleToText:(CFMutableAttributedStringRef)text attributes:(NSMutableDictionary*)attributes atPosition:(long)position withLength:(long)length
  432. {
  433. CFMutableDictionaryRef styleDict = ( CFDictionaryCreateMutable( (0), 0, (0), (0) ) );
  434. // direction
  435. CTWritingDirection direction = kCTWritingDirectionLeftToRight;
  436. // leading
  437. CGFloat firstLineIndent = 0.0;
  438. CGFloat headIndent = 0.0;
  439. CGFloat tailIndent = 0.0;
  440. CGFloat lineHeightMultiple = 1.0;
  441. CGFloat maxLineHeight = 0;
  442. CGFloat minLineHeight = 0;
  443. CGFloat paragraphSpacing = 0.0;
  444. CGFloat paragraphSpacingBefore = 0.0;
  445. int textAlignment = _textAlignment;
  446. int lineBreakMode = _lineBreakMode;
  447. int lineSpacing = (int)_lineSpacing;
  448. textAlignment = kCTTextAlignmentCenter;
  449. CTParagraphStyleSetting theSettings[] =
  450. {
  451. { kCTParagraphStyleSpecifierAlignment, sizeof(CTTextAlignment), &textAlignment },
  452. { kCTParagraphStyleSpecifierLineBreakMode, sizeof(CTLineBreakMode), &lineBreakMode },
  453. { kCTParagraphStyleSpecifierBaseWritingDirection, sizeof(CTWritingDirection), &direction },
  454. // { kCTParagraphStyleSpecifierLineSpacing, sizeof(CGFloat), &lineSpacing },
  455. { kCTParagraphStyleSpecifierMaximumLineSpacing, sizeof(CGFloat), &lineSpacing },
  456. { kCTParagraphStyleSpecifierMinimumLineSpacing, sizeof(CGFloat), &lineSpacing },
  457. { kCTParagraphStyleSpecifierFirstLineHeadIndent, sizeof(CGFloat), &firstLineIndent },
  458. { kCTParagraphStyleSpecifierHeadIndent, sizeof(CGFloat), &headIndent },
  459. { kCTParagraphStyleSpecifierTailIndent, sizeof(CGFloat), &tailIndent },
  460. { kCTParagraphStyleSpecifierLineHeightMultiple, sizeof(CGFloat), &lineHeightMultiple },
  461. { kCTParagraphStyleSpecifierMaximumLineHeight, sizeof(CGFloat), &maxLineHeight },
  462. { kCTParagraphStyleSpecifierMinimumLineHeight, sizeof(CGFloat), &minLineHeight },
  463. { kCTParagraphStyleSpecifierParagraphSpacing, sizeof(CGFloat), &paragraphSpacing },
  464. { kCTParagraphStyleSpecifierParagraphSpacingBefore, sizeof(CGFloat), &paragraphSpacingBefore }
  465. };
  466. CTParagraphStyleRef theParagraphRef = CTParagraphStyleCreate(theSettings, sizeof(theSettings) / sizeof(CTParagraphStyleSetting));
  467. CFDictionaryAddValue( styleDict, kCTParagraphStyleAttributeName, theParagraphRef );
  468. CFAttributedStringSetAttributes( text, CFRangeMake(position, length), styleDict, 0 );
  469. CFRelease(theParagraphRef);
  470. CFRelease(styleDict);
  471. }
  472. - (void)applySingleUnderlineText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length
  473. {
  474. CFAttributedStringSetAttribute(text, CFRangeMake(position, length), kCTUnderlineStyleAttributeName, (__bridge CFNumberRef)[NSNumber numberWithInt:kCTUnderlineStyleSingle]);
  475. }
  476. - (void)applyDoubleUnderlineText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length
  477. {
  478. CFAttributedStringSetAttribute(text, CFRangeMake(position, length), kCTUnderlineStyleAttributeName, (__bridge CFNumberRef)[NSNumber numberWithInt:kCTUnderlineStyleDouble]);
  479. }
  480. - (void)applyItalicStyleToText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length
  481. {
  482. CFTypeRef actualFontRef = CFAttributedStringGetAttribute(text, position, kCTFontAttributeName, NULL);
  483. CTFontRef italicFontRef = CTFontCreateCopyWithSymbolicTraits(actualFontRef, 0.0, NULL, kCTFontItalicTrait, kCTFontItalicTrait);
  484. if (!italicFontRef) {
  485. //fallback to system italic font
  486. UIFont *font = [UIFont italicSystemFontOfSize:CTFontGetSize(actualFontRef)];
  487. italicFontRef = CTFontCreateWithName ((__bridge CFStringRef)[font fontName], [font pointSize], NULL);
  488. }
  489. CFAttributedStringSetAttribute(text, CFRangeMake(position, length), kCTFontAttributeName, italicFontRef);
  490. CFRelease(italicFontRef);
  491. }
  492. - (void)applyFontAttributes:(NSDictionary*)attributes toText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length
  493. {
  494. for (NSString *key in attributes)
  495. {
  496. NSString *value = [attributes objectForKey:key];
  497. value = [value stringByReplacingOccurrencesOfString:@"'" withString:@""];
  498. if ([key caseInsensitiveCompare:@"color"] == NSOrderedSame)
  499. {
  500. [self applyColor:value toText:text atPosition:position withLength:length];
  501. }
  502. else if ([key caseInsensitiveCompare:@"stroke"] == NSOrderedSame)
  503. {
  504. CFAttributedStringSetAttribute(text, CFRangeMake(position, length), kCTStrokeWidthAttributeName, (__bridge CFTypeRef)([NSNumber numberWithFloat:[[attributes objectForKey:@"stroke"] intValue]]));
  505. }
  506. else if ([key caseInsensitiveCompare:@"kern"] == NSOrderedSame)
  507. {
  508. CFAttributedStringSetAttribute(text, CFRangeMake(position, length), kCTKernAttributeName, (__bridge CFTypeRef)([NSNumber numberWithFloat:[[attributes objectForKey:@"kern"] intValue]]));
  509. }
  510. else if ([key caseInsensitiveCompare:@"underline"] == NSOrderedSame)
  511. {
  512. int numberOfLines = [value intValue];
  513. if (numberOfLines==1)
  514. {
  515. [self applySingleUnderlineText:text atPosition:position withLength:length];
  516. }
  517. else if (numberOfLines==2)
  518. {
  519. [self applyDoubleUnderlineText:text atPosition:position withLength:length];
  520. }
  521. }
  522. else if ([key caseInsensitiveCompare:@"style"] == NSOrderedSame)
  523. {
  524. if ([value caseInsensitiveCompare:@"bold"] == NSOrderedSame)
  525. {
  526. [self applyBoldStyleToText:text atPosition:position withLength:length];
  527. }
  528. else if ([value caseInsensitiveCompare:@"italic"] == NSOrderedSame)
  529. {
  530. [self applyItalicStyleToText:text atPosition:position withLength:length];
  531. }
  532. }
  533. }
  534. UIFont *font = nil;
  535. if ([attributes objectForKey:@"face"] && [attributes objectForKey:@"size"])
  536. {
  537. NSString *fontName = [attributes objectForKey:@"face"];
  538. fontName = [fontName stringByReplacingOccurrencesOfString:@"'" withString:@""];
  539. font = [UIFont fontWithName:fontName size:[[attributes objectForKey:@"size"] intValue]];
  540. }
  541. else if ([attributes objectForKey:@"face"] && ![attributes objectForKey:@"size"])
  542. {
  543. NSString *fontName = [attributes objectForKey:@"face"];
  544. fontName = [fontName stringByReplacingOccurrencesOfString:@"'" withString:@""];
  545. font = [UIFont fontWithName:fontName size:self.font.pointSize];
  546. }
  547. else if (![attributes objectForKey:@"face"] && [attributes objectForKey:@"size"])
  548. {
  549. font = [UIFont fontWithName:[self.font fontName] size:[[attributes objectForKey:@"size"] intValue]];
  550. }
  551. if (font)
  552. {
  553. CTFontRef customFont = CTFontCreateWithName ((__bridge CFStringRef)[font fontName], [font pointSize], NULL);
  554. CFAttributedStringSetAttribute(text, CFRangeMake(position, length), kCTFontAttributeName, customFont);
  555. CFRelease(customFont);
  556. }
  557. }
  558. - (void)applyBoldStyleToText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length
  559. {
  560. // if(text==nil)
  561. // return;
  562. // if(text==NULL)
  563. // return;
  564. CFTypeRef actualFontRef = CFAttributedStringGetAttribute(text, position, kCTFontAttributeName, NULL);
  565. CTFontRef boldFontRef = CTFontCreateCopyWithSymbolicTraits(actualFontRef, 0.0, NULL, kCTFontBoldTrait, kCTFontBoldTrait);
  566. if (!boldFontRef) {
  567. //fallback to system bold font
  568. UIFont *font = [UIFont boldSystemFontOfSize:CTFontGetSize(actualFontRef)];
  569. boldFontRef = CTFontCreateWithName ((__bridge CFStringRef)[font fontName], [font pointSize], NULL);
  570. }
  571. CFAttributedStringSetAttribute(text, CFRangeMake(position, length), kCTFontAttributeName, boldFontRef);
  572. CFRelease(boldFontRef);
  573. }
  574. - (void)applyBoldItalicStyleToText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length
  575. {
  576. CFTypeRef actualFontRef = CFAttributedStringGetAttribute(text, position, kCTFontAttributeName, NULL);
  577. CTFontRef boldItalicFontRef = CTFontCreateCopyWithSymbolicTraits(actualFontRef, 0.0, NULL, kCTFontBoldTrait | kCTFontItalicTrait , kCTFontBoldTrait | kCTFontItalicTrait);
  578. if (!boldItalicFontRef) {
  579. //try fallback to system boldItalic font
  580. NSString *fontName = [NSString stringWithFormat:@"%@-BoldOblique", self.font.fontName];
  581. boldItalicFontRef = CTFontCreateWithName ((__bridge CFStringRef)fontName, [self.font pointSize], NULL);
  582. }
  583. if (boldItalicFontRef) {
  584. CFAttributedStringSetAttribute(text, CFRangeMake(position, length), kCTFontAttributeName, boldItalicFontRef);
  585. CFRelease(boldItalicFontRef);
  586. }
  587. }
  588. - (void)applyColor:(NSString*)value toText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length
  589. {
  590. if ([value rangeOfString:@"#"].location==0)
  591. {
  592. CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
  593. value = [value stringByReplacingOccurrencesOfString:@"#" withString:@""];
  594. NSArray *colorComponents = [self colorForHex:value];
  595. CGFloat components[] = { [[colorComponents objectAtIndex:0] floatValue] , [[colorComponents objectAtIndex:1] floatValue] , [[colorComponents objectAtIndex:2] floatValue] , [[colorComponents objectAtIndex:3] floatValue] };
  596. CGColorRef color = CGColorCreate(rgbColorSpace, components);
  597. CFAttributedStringSetAttribute(text, CFRangeMake(position, length),kCTForegroundColorAttributeName, color);
  598. CFRelease(color);
  599. CGColorSpaceRelease(rgbColorSpace);
  600. } else {
  601. value = [value stringByAppendingString:@"Color"];
  602. SEL colorSel = NSSelectorFromString(value);
  603. UIColor *_color = nil;
  604. if ([UIColor respondsToSelector:colorSel]) {
  605. _color = [UIColor performSelector:colorSel];
  606. CGColorRef color = [_color CGColor];
  607. CFAttributedStringSetAttribute(text, CFRangeMake(position, length),kCTForegroundColorAttributeName, color);
  608. }
  609. }
  610. }
  611. - (void)applyUnderlineColor:(NSString*)value toText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length
  612. {
  613. value = [value stringByReplacingOccurrencesOfString:@"'" withString:@""];
  614. if ([value rangeOfString:@"#"].location==0) {
  615. CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
  616. value = [value stringByReplacingOccurrencesOfString:@"#" withString:@"0x"];
  617. NSArray *colorComponents = [self colorForHex:value];
  618. CGFloat components[] = { [[colorComponents objectAtIndex:0] floatValue] , [[colorComponents objectAtIndex:1] floatValue] , [[colorComponents objectAtIndex:2] floatValue] , [[colorComponents objectAtIndex:3] floatValue] };
  619. CGColorRef color = CGColorCreate(rgbColorSpace, components);
  620. CFAttributedStringSetAttribute(text, CFRangeMake(position, length),kCTUnderlineColorAttributeName, color);
  621. CGColorRelease(color);
  622. CGColorSpaceRelease(rgbColorSpace);
  623. }
  624. else
  625. {
  626. value = [value stringByAppendingString:@"Color"];
  627. SEL colorSel = NSSelectorFromString(value);
  628. if ([UIColor respondsToSelector:colorSel]) {
  629. UIColor *_color = [UIColor performSelector:colorSel];
  630. CGColorRef color = [_color CGColor];
  631. CFAttributedStringSetAttribute(text, CFRangeMake(position, length),kCTUnderlineColorAttributeName, color);
  632. //CGColorRelease(color);
  633. }
  634. }
  635. }
  636. #pragma mark -
  637. #pragma mark button
  638. - (void)onButtonTouchDown:(id)sender
  639. {
  640. RTLabelButton *button = (RTLabelButton*)sender;
  641. [self setCurrentSelectedButtonComponentIndex:button.componentIndex];
  642. [self setNeedsDisplay];
  643. }
  644. - (void)onButtonTouchUpOutside:(id)sender
  645. {
  646. [self setCurrentSelectedButtonComponentIndex:-1];
  647. [self setNeedsDisplay];
  648. }
  649. - (void)onButtonPressed:(id)sender
  650. {
  651. RTLabelButton *button = (RTLabelButton*)sender;
  652. [self setCurrentSelectedButtonComponentIndex:-1];
  653. [self setNeedsDisplay];
  654. if ([self.delegate respondsToSelector:@selector(rtLabel:didSelectLinkWithURL:)])
  655. {
  656. [self.delegate rtLabel:self didSelectLinkWithURL:button.url];
  657. }
  658. }
  659. - (CGSize)optimumSize
  660. {
  661. [self render];
  662. return _optimumSize;
  663. }
  664. - (void)setLineSpacing:(CGFloat)lineSpacing
  665. {
  666. _lineSpacing = lineSpacing;
  667. [self setNeedsDisplay];
  668. }
  669. - (void)setHighlighted:(BOOL)highlighted
  670. {
  671. if (highlighted!=_highlighted)
  672. {
  673. _highlighted = highlighted;
  674. [self setNeedsDisplay];
  675. }
  676. }
  677. - (void)setHighlightedText:(NSString *)text
  678. {
  679. _highlightedText = [text stringByReplacingOccurrencesOfString:@"<br>" withString:@"\n"];
  680. RTLabelExtractedComponent *component = [RTLabel extractTextStyleFromText:_highlightedText paragraphReplacement:self.paragraphReplacement];
  681. [self setHighlightedTextComponents:component.textComponents];
  682. }
  683. - (void)setText:(NSString *)text
  684. {
  685. _text = [text stringByReplacingOccurrencesOfString:@"<br>" withString:@"\n"];
  686. RTLabelExtractedComponent *component = [RTLabel extractTextStyleFromText:_text paragraphReplacement:self.paragraphReplacement];
  687. [self setTextComponents:component.textComponents];
  688. [self setPlainText:component.plainText];
  689. [self setNeedsDisplay];
  690. }
  691. - (void)setText:(NSString *)text extractedTextComponent:(RTLabelExtractedComponent*)extractedComponent
  692. {
  693. _text = [text stringByReplacingOccurrencesOfString:@"<br>" withString:@"\n"];
  694. [self setTextComponents:extractedComponent.textComponents];
  695. [self setPlainText:extractedComponent.plainText];
  696. [self setNeedsDisplay];
  697. }
  698. - (void)setHighlightedText:(NSString *)text extractedTextComponent:(RTLabelExtractedComponent*)extractedComponent
  699. {
  700. _highlightedText = [text stringByReplacingOccurrencesOfString:@"<br>" withString:@"\n"];
  701. [self setHighlightedTextComponents:extractedComponent.textComponents];
  702. }
  703. // http://forums.macrumors.com/showthread.php?t=925312
  704. // not accurate
  705. - (CGFloat)frameHeight:(CTFrameRef)theFrame
  706. {
  707. CFArrayRef lines = CTFrameGetLines(theFrame);
  708. CGFloat height = 0.0;
  709. CGFloat ascent, descent, leading;
  710. for (CFIndex index = 0; index < CFArrayGetCount(lines); index++) {
  711. CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, index);
  712. CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
  713. height += (ascent + fabs(descent) + leading);
  714. }
  715. return ceilf(height);
  716. }
  717. - (void)dealloc
  718. {
  719. self.delegate = nil;
  720. }
  721. - (NSArray *)components
  722. {
  723. NSScanner *scanner = [NSScanner scannerWithString:self.text];
  724. [scanner setCharactersToBeSkipped:nil];
  725. NSMutableArray *components = [NSMutableArray array];
  726. while (![scanner isAtEnd])
  727. {
  728. NSString *currentComponent;
  729. BOOL foundComponent = [scanner scanUpToString:@"http" intoString:&currentComponent];
  730. if (foundComponent)
  731. {
  732. [components addObject:currentComponent];
  733. NSString *string;
  734. BOOL foundURLComponent = [scanner scanUpToString:@" " intoString:&string];
  735. if (foundURLComponent)
  736. {
  737. // if last character of URL is punctuation, its probably not part of the URL
  738. NSCharacterSet *punctuationSet = [NSCharacterSet punctuationCharacterSet];
  739. NSInteger lastCharacterIndex = string.length - 1;
  740. if ([punctuationSet characterIsMember:[string characterAtIndex:lastCharacterIndex]])
  741. {
  742. // remove the punctuation from the URL string and move the scanner back
  743. string = [string substringToIndex:lastCharacterIndex];
  744. [scanner setScanLocation:scanner.scanLocation - 1];
  745. }
  746. [components addObject:string];
  747. }
  748. }
  749. else
  750. { // first string is a link
  751. NSString *string;
  752. BOOL foundURLComponent = [scanner scanUpToString:@" " intoString:&string];
  753. if (foundURLComponent)
  754. {
  755. [components addObject:string];
  756. }
  757. }
  758. }
  759. return [components copy];
  760. }
  761. + (RTLabelExtractedComponent*)extractTextStyleFromText:(NSString*)data paragraphReplacement:(NSString*)paragraphReplacement
  762. {
  763. NSScanner *scanner = nil;
  764. NSString *text = nil;
  765. NSString *tag = nil;
  766. NSMutableArray *components = [NSMutableArray array];
  767. long last_position = 0;
  768. scanner = [NSScanner scannerWithString:data];
  769. while (![scanner isAtEnd])
  770. {
  771. [scanner scanUpToString:@"<" intoString:NULL];
  772. [scanner scanUpToString:@">" intoString:&text];
  773. NSString *delimiter = [NSString stringWithFormat:@"%@>", text];
  774. NSRange range =[data rangeOfString:delimiter];
  775. NSUInteger position = range.location;
  776. if (position!=NSNotFound)
  777. {
  778. if ([delimiter rangeOfString:@"<p"].location==0)
  779. {
  780. data = [data stringByReplacingOccurrencesOfString:delimiter withString:paragraphReplacement options:NSCaseInsensitiveSearch range:NSMakeRange(last_position, position+delimiter.length-last_position)];
  781. }
  782. else
  783. {
  784. data = [data stringByReplacingOccurrencesOfString:delimiter withString:@"" options:NSCaseInsensitiveSearch range:NSMakeRange(last_position, position+delimiter.length-last_position)];
  785. }
  786. data = [data stringByReplacingOccurrencesOfString:@"&lt;" withString:@"<"];
  787. data = [data stringByReplacingOccurrencesOfString:@"&gt;" withString:@">"];
  788. }
  789. if ([text rangeOfString:@"</"].location==0)
  790. {
  791. // end of tag
  792. tag = [text substringFromIndex:2];
  793. if (position!=NSNotFound)
  794. {
  795. for (long i=[components count]-1; i>=0; i--)
  796. {
  797. RTLabelComponent *component = [components objectAtIndex:i];
  798. if (component.text==nil && [component.tagLabel isEqualToString:tag])
  799. {
  800. NSString *text2 = [data substringWithRange:NSMakeRange(component.position, position-component.position)];
  801. component.text = text2;
  802. break;
  803. }
  804. }
  805. }
  806. }
  807. else
  808. {
  809. // start of tag
  810. NSArray *textComponents = [[text substringFromIndex:1] componentsSeparatedByString:@" "];
  811. tag = [textComponents objectAtIndex:0];
  812. NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
  813. for (NSUInteger i=1; i<[textComponents count]; i++)
  814. {
  815. NSArray *pair = [[textComponents objectAtIndex:i] componentsSeparatedByString:@"="];
  816. if ([pair count] > 0) {
  817. NSString *key = [[pair objectAtIndex:0] lowercaseString];
  818. if ([pair count]>=2) {
  819. // Trim " charactere
  820. NSString *value = [[pair subarrayWithRange:NSMakeRange(1, [pair count] - 1)] componentsJoinedByString:@"="];
  821. value = [value stringByReplacingOccurrencesOfString:@"\"" withString:@"" options:NSLiteralSearch range:NSMakeRange(0, 1)];
  822. value = [value stringByReplacingOccurrencesOfString:@"\"" withString:@"" options:NSLiteralSearch range:NSMakeRange([value length]-1, 1)];
  823. [attributes setObject:value forKey:key];
  824. } else if ([pair count]==1) {
  825. [attributes setObject:key forKey:key];
  826. }
  827. }
  828. }
  829. RTLabelComponent *component = [RTLabelComponent componentWithString:nil tag:tag attributes:attributes];
  830. component.position = position;
  831. [components addObject:component];
  832. }
  833. last_position = position;
  834. }
  835. return [RTLabelExtractedComponent rtLabelExtractComponentsWithTextComponent:components plainText:data];
  836. }
  837. - (void)parse:(NSString *)data valid_tags:(NSArray *)valid_tags
  838. {
  839. //use to strip the HTML tags from the data
  840. NSScanner *scanner = nil;
  841. NSString *text = nil;
  842. NSString *tag = nil;
  843. NSMutableArray *components = [NSMutableArray array];
  844. //set up the scanner
  845. scanner = [NSScanner scannerWithString:data];
  846. NSMutableDictionary *lastAttributes = nil;
  847. NSUInteger last_position = 0;
  848. while([scanner isAtEnd] == NO)
  849. {
  850. //find start of tag
  851. [scanner scanUpToString:@"<" intoString:NULL];
  852. //find end of tag
  853. [scanner scanUpToString:@">" intoString:&text];
  854. NSMutableDictionary *attributes = nil;
  855. //get the name of the tag
  856. if([text rangeOfString:@"</"].location != NSNotFound)
  857. tag = [text substringFromIndex:2]; //remove </
  858. else
  859. {
  860. tag = [text substringFromIndex:1]; //remove <
  861. //find out if there is a space in the tag
  862. if([tag rangeOfString:@" "].location != NSNotFound)
  863. {
  864. attributes = [NSMutableDictionary dictionary];
  865. NSArray *rawAttributes = [tag componentsSeparatedByString:@" "];
  866. for (NSUInteger i=1; i<[rawAttributes count]; i++)
  867. {
  868. NSArray *pair = [[rawAttributes objectAtIndex:i] componentsSeparatedByString:@"="];
  869. if ([pair count]==2)
  870. {
  871. [attributes setObject:[pair objectAtIndex:1] forKey:[pair objectAtIndex:0]];
  872. }
  873. }
  874. //remove text after a space
  875. tag = [tag substringToIndex:[tag rangeOfString:@" "].location];
  876. }
  877. }
  878. //if not a valid tag, replace the tag with a space
  879. if([valid_tags containsObject:tag] == NO)
  880. {
  881. NSString *delimiter = [NSString stringWithFormat:@"%@>", text];
  882. NSUInteger position = [data rangeOfString:delimiter].location;
  883. BOOL isEnd = [delimiter rangeOfString:@"</"].location!=NSNotFound;
  884. if (position!=NSNotFound)
  885. {
  886. NSString *text2 = [data substringWithRange:NSMakeRange(last_position, position-last_position)];
  887. if (isEnd)
  888. {
  889. // is inside a tag
  890. [components addObject:[RTLabelComponent componentWithString:text2 tag:tag attributes:lastAttributes]];
  891. }
  892. else
  893. {
  894. // is outside a tag
  895. [components addObject:[RTLabelComponent componentWithString:text2 tag:nil attributes:lastAttributes]];
  896. }
  897. data = [data stringByReplacingOccurrencesOfString:delimiter withString:@"" options:NSCaseInsensitiveSearch range:NSMakeRange(last_position, position+delimiter.length-last_position)];
  898. last_position = position;
  899. }
  900. else
  901. {
  902. NSString *text2 = [data substringFromIndex:last_position];
  903. // is outside a tag
  904. [components addObject:[RTLabelComponent componentWithString:text2 tag:nil attributes:lastAttributes]];
  905. }
  906. lastAttributes = attributes;
  907. }
  908. }
  909. [self setTextComponents:components];
  910. [self setPlainText:data];
  911. }
  912. - (NSArray*)colorForHex:(NSString *)hexColor
  913. {
  914. hexColor = [[hexColor stringByTrimmingCharactersInSet:
  915. [NSCharacterSet whitespaceAndNewlineCharacterSet]
  916. ] uppercaseString];
  917. NSRange range;
  918. range.location = 0;
  919. range.length = 2;
  920. NSString *rString = [hexColor substringWithRange:range];
  921. range.location = 2;
  922. NSString *gString = [hexColor substringWithRange:range];
  923. range.location = 4;
  924. NSString *bString = [hexColor substringWithRange:range];
  925. // Scan values
  926. unsigned int r, g, b;
  927. [[NSScanner scannerWithString:rString] scanHexInt:&r];
  928. [[NSScanner scannerWithString:gString] scanHexInt:&g];
  929. [[NSScanner scannerWithString:bString] scanHexInt:&b];
  930. NSArray *components = [NSArray arrayWithObjects:[NSNumber numberWithFloat:((float) r / 255.0f)],[NSNumber numberWithFloat:((float) g / 255.0f)],[NSNumber numberWithFloat:((float) b / 255.0f)],[NSNumber numberWithFloat:1.0],nil];
  931. return components;
  932. }
  933. - (NSString*)visibleText
  934. {
  935. [self render];
  936. NSString *text = [self.text substringWithRange:NSMakeRange(self.visibleRange.location, self.visibleRange.length)];
  937. return text;
  938. }
  939. #pragma mark deprecated methods
  940. - (void)setText:(NSString *)text extractedTextStyle:(NSDictionary*)extractTextStyle
  941. {
  942. _text = [text stringByReplacingOccurrencesOfString:@"<br>" withString:@"\n"];
  943. [self setTextComponents:[extractTextStyle objectForKey:@"textComponents"]];
  944. [self setPlainText:[extractTextStyle objectForKey:@"plainText"]];
  945. [self setNeedsDisplay];
  946. }
  947. + (NSDictionary*)preExtractTextStyle:(NSString*)data
  948. {
  949. NSString* paragraphReplacement = @"\n";
  950. RTLabelExtractedComponent *component = [RTLabel extractTextStyleFromText:data paragraphReplacement:paragraphReplacement];
  951. return [NSDictionary dictionaryWithObjectsAndKeys:component.textComponents, @"textComponents", component.plainText, @"plainText", nil];
  952. }
  953. @end