RTLabel.m 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091
  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 = kCTLeftTextAlignment;
  363. }
  364. else if ([value caseInsensitiveCompare:@"right"] == NSOrderedSame)
  365. {
  366. textAlignment = kCTRightTextAlignment;
  367. }
  368. else if ([value caseInsensitiveCompare:@"justify"] == NSOrderedSame)
  369. {
  370. textAlignment = kCTJustifiedTextAlignment;
  371. }
  372. else if ([value caseInsensitiveCompare:@"center"] == NSOrderedSame)
  373. {
  374. textAlignment = kCTCenterTextAlignment;
  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 = kCTCenterTextAlignment;
  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. { kCTParagraphStyleSpecifierFirstLineHeadIndent, sizeof(CGFloat), &firstLineIndent },
  456. { kCTParagraphStyleSpecifierHeadIndent, sizeof(CGFloat), &headIndent },
  457. { kCTParagraphStyleSpecifierTailIndent, sizeof(CGFloat), &tailIndent },
  458. { kCTParagraphStyleSpecifierLineHeightMultiple, sizeof(CGFloat), &lineHeightMultiple },
  459. { kCTParagraphStyleSpecifierMaximumLineHeight, sizeof(CGFloat), &maxLineHeight },
  460. { kCTParagraphStyleSpecifierMinimumLineHeight, sizeof(CGFloat), &minLineHeight },
  461. { kCTParagraphStyleSpecifierParagraphSpacing, sizeof(CGFloat), &paragraphSpacing },
  462. { kCTParagraphStyleSpecifierParagraphSpacingBefore, sizeof(CGFloat), &paragraphSpacingBefore }
  463. };
  464. CTParagraphStyleRef theParagraphRef = CTParagraphStyleCreate(theSettings, sizeof(theSettings) / sizeof(CTParagraphStyleSetting));
  465. CFDictionaryAddValue( styleDict, kCTParagraphStyleAttributeName, theParagraphRef );
  466. CFAttributedStringSetAttributes( text, CFRangeMake(position, length), styleDict, 0 );
  467. CFRelease(theParagraphRef);
  468. CFRelease(styleDict);
  469. }
  470. - (void)applySingleUnderlineText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length
  471. {
  472. CFAttributedStringSetAttribute(text, CFRangeMake(position, length), kCTUnderlineStyleAttributeName, (__bridge CFNumberRef)[NSNumber numberWithInt:kCTUnderlineStyleSingle]);
  473. }
  474. - (void)applyDoubleUnderlineText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length
  475. {
  476. CFAttributedStringSetAttribute(text, CFRangeMake(position, length), kCTUnderlineStyleAttributeName, (__bridge CFNumberRef)[NSNumber numberWithInt:kCTUnderlineStyleDouble]);
  477. }
  478. - (void)applyItalicStyleToText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length
  479. {
  480. CFTypeRef actualFontRef = CFAttributedStringGetAttribute(text, position, kCTFontAttributeName, NULL);
  481. CTFontRef italicFontRef = CTFontCreateCopyWithSymbolicTraits(actualFontRef, 0.0, NULL, kCTFontItalicTrait, kCTFontItalicTrait);
  482. if (!italicFontRef) {
  483. //fallback to system italic font
  484. UIFont *font = [UIFont italicSystemFontOfSize:CTFontGetSize(actualFontRef)];
  485. italicFontRef = CTFontCreateWithName ((__bridge CFStringRef)[font fontName], [font pointSize], NULL);
  486. }
  487. CFAttributedStringSetAttribute(text, CFRangeMake(position, length), kCTFontAttributeName, italicFontRef);
  488. CFRelease(italicFontRef);
  489. }
  490. - (void)applyFontAttributes:(NSDictionary*)attributes toText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length
  491. {
  492. for (NSString *key in attributes)
  493. {
  494. NSString *value = [attributes objectForKey:key];
  495. value = [value stringByReplacingOccurrencesOfString:@"'" withString:@""];
  496. if ([key caseInsensitiveCompare:@"color"] == NSOrderedSame)
  497. {
  498. [self applyColor:value toText:text atPosition:position withLength:length];
  499. }
  500. else if ([key caseInsensitiveCompare:@"stroke"] == NSOrderedSame)
  501. {
  502. CFAttributedStringSetAttribute(text, CFRangeMake(position, length), kCTStrokeWidthAttributeName, (__bridge CFTypeRef)([NSNumber numberWithFloat:[[attributes objectForKey:@"stroke"] intValue]]));
  503. }
  504. else if ([key caseInsensitiveCompare:@"kern"] == NSOrderedSame)
  505. {
  506. CFAttributedStringSetAttribute(text, CFRangeMake(position, length), kCTKernAttributeName, (__bridge CFTypeRef)([NSNumber numberWithFloat:[[attributes objectForKey:@"kern"] intValue]]));
  507. }
  508. else if ([key caseInsensitiveCompare:@"underline"] == NSOrderedSame)
  509. {
  510. int numberOfLines = [value intValue];
  511. if (numberOfLines==1)
  512. {
  513. [self applySingleUnderlineText:text atPosition:position withLength:length];
  514. }
  515. else if (numberOfLines==2)
  516. {
  517. [self applyDoubleUnderlineText:text atPosition:position withLength:length];
  518. }
  519. }
  520. else if ([key caseInsensitiveCompare:@"style"] == NSOrderedSame)
  521. {
  522. if ([value caseInsensitiveCompare:@"bold"] == NSOrderedSame)
  523. {
  524. [self applyBoldStyleToText:text atPosition:position withLength:length];
  525. }
  526. else if ([value caseInsensitiveCompare:@"italic"] == NSOrderedSame)
  527. {
  528. [self applyItalicStyleToText:text atPosition:position withLength:length];
  529. }
  530. }
  531. }
  532. UIFont *font = nil;
  533. if ([attributes objectForKey:@"face"] && [attributes objectForKey:@"size"])
  534. {
  535. NSString *fontName = [attributes objectForKey:@"face"];
  536. fontName = [fontName stringByReplacingOccurrencesOfString:@"'" withString:@""];
  537. font = [UIFont fontWithName:fontName size:[[attributes objectForKey:@"size"] intValue]];
  538. }
  539. else if ([attributes objectForKey:@"face"] && ![attributes objectForKey:@"size"])
  540. {
  541. NSString *fontName = [attributes objectForKey:@"face"];
  542. fontName = [fontName stringByReplacingOccurrencesOfString:@"'" withString:@""];
  543. font = [UIFont fontWithName:fontName size:self.font.pointSize];
  544. }
  545. else if (![attributes objectForKey:@"face"] && [attributes objectForKey:@"size"])
  546. {
  547. font = [UIFont fontWithName:[self.font fontName] size:[[attributes objectForKey:@"size"] intValue]];
  548. }
  549. if (font)
  550. {
  551. CTFontRef customFont = CTFontCreateWithName ((__bridge CFStringRef)[font fontName], [font pointSize], NULL);
  552. CFAttributedStringSetAttribute(text, CFRangeMake(position, length), kCTFontAttributeName, customFont);
  553. CFRelease(customFont);
  554. }
  555. }
  556. - (void)applyBoldStyleToText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length
  557. {
  558. // if(text==nil)
  559. // return;
  560. // if(text==NULL)
  561. // return;
  562. CFTypeRef actualFontRef = CFAttributedStringGetAttribute(text, position, kCTFontAttributeName, NULL);
  563. CTFontRef boldFontRef = CTFontCreateCopyWithSymbolicTraits(actualFontRef, 0.0, NULL, kCTFontBoldTrait, kCTFontBoldTrait);
  564. if (!boldFontRef) {
  565. //fallback to system bold font
  566. UIFont *font = [UIFont boldSystemFontOfSize:CTFontGetSize(actualFontRef)];
  567. boldFontRef = CTFontCreateWithName ((__bridge CFStringRef)[font fontName], [font pointSize], NULL);
  568. }
  569. CFAttributedStringSetAttribute(text, CFRangeMake(position, length), kCTFontAttributeName, boldFontRef);
  570. CFRelease(boldFontRef);
  571. }
  572. - (void)applyBoldItalicStyleToText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length
  573. {
  574. CFTypeRef actualFontRef = CFAttributedStringGetAttribute(text, position, kCTFontAttributeName, NULL);
  575. CTFontRef boldItalicFontRef = CTFontCreateCopyWithSymbolicTraits(actualFontRef, 0.0, NULL, kCTFontBoldTrait | kCTFontItalicTrait , kCTFontBoldTrait | kCTFontItalicTrait);
  576. if (!boldItalicFontRef) {
  577. //try fallback to system boldItalic font
  578. NSString *fontName = [NSString stringWithFormat:@"%@-BoldOblique", self.font.fontName];
  579. boldItalicFontRef = CTFontCreateWithName ((__bridge CFStringRef)fontName, [self.font pointSize], NULL);
  580. }
  581. if (boldItalicFontRef) {
  582. CFAttributedStringSetAttribute(text, CFRangeMake(position, length), kCTFontAttributeName, boldItalicFontRef);
  583. CFRelease(boldItalicFontRef);
  584. }
  585. }
  586. - (void)applyColor:(NSString*)value toText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length
  587. {
  588. if ([value rangeOfString:@"#"].location==0)
  589. {
  590. CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
  591. value = [value stringByReplacingOccurrencesOfString:@"#" withString:@""];
  592. NSArray *colorComponents = [self colorForHex:value];
  593. CGFloat components[] = { [[colorComponents objectAtIndex:0] floatValue] , [[colorComponents objectAtIndex:1] floatValue] , [[colorComponents objectAtIndex:2] floatValue] , [[colorComponents objectAtIndex:3] floatValue] };
  594. CGColorRef color = CGColorCreate(rgbColorSpace, components);
  595. CFAttributedStringSetAttribute(text, CFRangeMake(position, length),kCTForegroundColorAttributeName, color);
  596. CFRelease(color);
  597. CGColorSpaceRelease(rgbColorSpace);
  598. } else {
  599. value = [value stringByAppendingString:@"Color"];
  600. SEL colorSel = NSSelectorFromString(value);
  601. UIColor *_color = nil;
  602. if ([UIColor respondsToSelector:colorSel]) {
  603. _color = [UIColor performSelector:colorSel];
  604. CGColorRef color = [_color CGColor];
  605. CFAttributedStringSetAttribute(text, CFRangeMake(position, length),kCTForegroundColorAttributeName, color);
  606. }
  607. }
  608. }
  609. - (void)applyUnderlineColor:(NSString*)value toText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length
  610. {
  611. value = [value stringByReplacingOccurrencesOfString:@"'" withString:@""];
  612. if ([value rangeOfString:@"#"].location==0) {
  613. CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
  614. value = [value stringByReplacingOccurrencesOfString:@"#" withString:@"0x"];
  615. NSArray *colorComponents = [self colorForHex:value];
  616. CGFloat components[] = { [[colorComponents objectAtIndex:0] floatValue] , [[colorComponents objectAtIndex:1] floatValue] , [[colorComponents objectAtIndex:2] floatValue] , [[colorComponents objectAtIndex:3] floatValue] };
  617. CGColorRef color = CGColorCreate(rgbColorSpace, components);
  618. CFAttributedStringSetAttribute(text, CFRangeMake(position, length),kCTUnderlineColorAttributeName, color);
  619. CGColorRelease(color);
  620. CGColorSpaceRelease(rgbColorSpace);
  621. }
  622. else
  623. {
  624. value = [value stringByAppendingString:@"Color"];
  625. SEL colorSel = NSSelectorFromString(value);
  626. if ([UIColor respondsToSelector:colorSel]) {
  627. UIColor *_color = [UIColor performSelector:colorSel];
  628. CGColorRef color = [_color CGColor];
  629. CFAttributedStringSetAttribute(text, CFRangeMake(position, length),kCTUnderlineColorAttributeName, color);
  630. //CGColorRelease(color);
  631. }
  632. }
  633. }
  634. #pragma mark -
  635. #pragma mark button
  636. - (void)onButtonTouchDown:(id)sender
  637. {
  638. RTLabelButton *button = (RTLabelButton*)sender;
  639. [self setCurrentSelectedButtonComponentIndex:button.componentIndex];
  640. [self setNeedsDisplay];
  641. }
  642. - (void)onButtonTouchUpOutside:(id)sender
  643. {
  644. [self setCurrentSelectedButtonComponentIndex:-1];
  645. [self setNeedsDisplay];
  646. }
  647. - (void)onButtonPressed:(id)sender
  648. {
  649. RTLabelButton *button = (RTLabelButton*)sender;
  650. [self setCurrentSelectedButtonComponentIndex:-1];
  651. [self setNeedsDisplay];
  652. if ([self.delegate respondsToSelector:@selector(rtLabel:didSelectLinkWithURL:)])
  653. {
  654. [self.delegate rtLabel:self didSelectLinkWithURL:button.url];
  655. }
  656. }
  657. - (CGSize)optimumSize
  658. {
  659. [self render];
  660. return _optimumSize;
  661. }
  662. - (void)setLineSpacing:(CGFloat)lineSpacing
  663. {
  664. _lineSpacing = lineSpacing;
  665. [self setNeedsDisplay];
  666. }
  667. - (void)setHighlighted:(BOOL)highlighted
  668. {
  669. if (highlighted!=_highlighted)
  670. {
  671. _highlighted = highlighted;
  672. [self setNeedsDisplay];
  673. }
  674. }
  675. - (void)setHighlightedText:(NSString *)text
  676. {
  677. _highlightedText = [text stringByReplacingOccurrencesOfString:@"<br>" withString:@"\n"];
  678. RTLabelExtractedComponent *component = [RTLabel extractTextStyleFromText:_highlightedText paragraphReplacement:self.paragraphReplacement];
  679. [self setHighlightedTextComponents:component.textComponents];
  680. }
  681. - (void)setText:(NSString *)text
  682. {
  683. _text = [text stringByReplacingOccurrencesOfString:@"<br>" withString:@"\n"];
  684. RTLabelExtractedComponent *component = [RTLabel extractTextStyleFromText:_text paragraphReplacement:self.paragraphReplacement];
  685. [self setTextComponents:component.textComponents];
  686. [self setPlainText:component.plainText];
  687. [self setNeedsDisplay];
  688. }
  689. - (void)setText:(NSString *)text extractedTextComponent:(RTLabelExtractedComponent*)extractedComponent
  690. {
  691. _text = [text stringByReplacingOccurrencesOfString:@"<br>" withString:@"\n"];
  692. [self setTextComponents:extractedComponent.textComponents];
  693. [self setPlainText:extractedComponent.plainText];
  694. [self setNeedsDisplay];
  695. }
  696. - (void)setHighlightedText:(NSString *)text extractedTextComponent:(RTLabelExtractedComponent*)extractedComponent
  697. {
  698. _highlightedText = [text stringByReplacingOccurrencesOfString:@"<br>" withString:@"\n"];
  699. [self setHighlightedTextComponents:extractedComponent.textComponents];
  700. }
  701. // http://forums.macrumors.com/showthread.php?t=925312
  702. // not accurate
  703. - (CGFloat)frameHeight:(CTFrameRef)theFrame
  704. {
  705. CFArrayRef lines = CTFrameGetLines(theFrame);
  706. CGFloat height = 0.0;
  707. CGFloat ascent, descent, leading;
  708. for (CFIndex index = 0; index < CFArrayGetCount(lines); index++) {
  709. CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, index);
  710. CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
  711. height += (ascent + fabs(descent) + leading);
  712. }
  713. return ceilf(height);
  714. }
  715. - (void)dealloc
  716. {
  717. self.delegate = nil;
  718. }
  719. - (NSArray *)components
  720. {
  721. NSScanner *scanner = [NSScanner scannerWithString:self.text];
  722. [scanner setCharactersToBeSkipped:nil];
  723. NSMutableArray *components = [NSMutableArray array];
  724. while (![scanner isAtEnd])
  725. {
  726. NSString *currentComponent;
  727. BOOL foundComponent = [scanner scanUpToString:@"http" intoString:&currentComponent];
  728. if (foundComponent)
  729. {
  730. [components addObject:currentComponent];
  731. NSString *string;
  732. BOOL foundURLComponent = [scanner scanUpToString:@" " intoString:&string];
  733. if (foundURLComponent)
  734. {
  735. // if last character of URL is punctuation, its probably not part of the URL
  736. NSCharacterSet *punctuationSet = [NSCharacterSet punctuationCharacterSet];
  737. NSInteger lastCharacterIndex = string.length - 1;
  738. if ([punctuationSet characterIsMember:[string characterAtIndex:lastCharacterIndex]])
  739. {
  740. // remove the punctuation from the URL string and move the scanner back
  741. string = [string substringToIndex:lastCharacterIndex];
  742. [scanner setScanLocation:scanner.scanLocation - 1];
  743. }
  744. [components addObject:string];
  745. }
  746. }
  747. else
  748. { // first string is a link
  749. NSString *string;
  750. BOOL foundURLComponent = [scanner scanUpToString:@" " intoString:&string];
  751. if (foundURLComponent)
  752. {
  753. [components addObject:string];
  754. }
  755. }
  756. }
  757. return [components copy];
  758. }
  759. + (RTLabelExtractedComponent*)extractTextStyleFromText:(NSString*)data paragraphReplacement:(NSString*)paragraphReplacement
  760. {
  761. NSScanner *scanner = nil;
  762. NSString *text = nil;
  763. NSString *tag = nil;
  764. NSMutableArray *components = [NSMutableArray array];
  765. long last_position = 0;
  766. scanner = [NSScanner scannerWithString:data];
  767. while (![scanner isAtEnd])
  768. {
  769. [scanner scanUpToString:@"<" intoString:NULL];
  770. [scanner scanUpToString:@">" intoString:&text];
  771. NSString *delimiter = [NSString stringWithFormat:@"%@>", text];
  772. NSRange range =[data rangeOfString:delimiter];
  773. NSUInteger position = range.location;
  774. if (position!=NSNotFound)
  775. {
  776. if ([delimiter rangeOfString:@"<p"].location==0)
  777. {
  778. data = [data stringByReplacingOccurrencesOfString:delimiter withString:paragraphReplacement options:NSCaseInsensitiveSearch range:NSMakeRange(last_position, position+delimiter.length-last_position)];
  779. }
  780. else
  781. {
  782. data = [data stringByReplacingOccurrencesOfString:delimiter withString:@"" options:NSCaseInsensitiveSearch range:NSMakeRange(last_position, position+delimiter.length-last_position)];
  783. }
  784. data = [data stringByReplacingOccurrencesOfString:@"&lt;" withString:@"<"];
  785. data = [data stringByReplacingOccurrencesOfString:@"&gt;" withString:@">"];
  786. }
  787. if ([text rangeOfString:@"</"].location==0)
  788. {
  789. // end of tag
  790. tag = [text substringFromIndex:2];
  791. if (position!=NSNotFound)
  792. {
  793. for (long i=[components count]-1; i>=0; i--)
  794. {
  795. RTLabelComponent *component = [components objectAtIndex:i];
  796. if (component.text==nil && [component.tagLabel isEqualToString:tag])
  797. {
  798. NSString *text2 = [data substringWithRange:NSMakeRange(component.position, position-component.position)];
  799. component.text = text2;
  800. break;
  801. }
  802. }
  803. }
  804. }
  805. else
  806. {
  807. // start of tag
  808. NSArray *textComponents = [[text substringFromIndex:1] componentsSeparatedByString:@" "];
  809. tag = [textComponents objectAtIndex:0];
  810. NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
  811. for (NSUInteger i=1; i<[textComponents count]; i++)
  812. {
  813. NSArray *pair = [[textComponents objectAtIndex:i] componentsSeparatedByString:@"="];
  814. if ([pair count] > 0) {
  815. NSString *key = [[pair objectAtIndex:0] lowercaseString];
  816. if ([pair count]>=2) {
  817. // Trim " charactere
  818. NSString *value = [[pair subarrayWithRange:NSMakeRange(1, [pair count] - 1)] componentsJoinedByString:@"="];
  819. value = [value stringByReplacingOccurrencesOfString:@"\"" withString:@"" options:NSLiteralSearch range:NSMakeRange(0, 1)];
  820. value = [value stringByReplacingOccurrencesOfString:@"\"" withString:@"" options:NSLiteralSearch range:NSMakeRange([value length]-1, 1)];
  821. [attributes setObject:value forKey:key];
  822. } else if ([pair count]==1) {
  823. [attributes setObject:key forKey:key];
  824. }
  825. }
  826. }
  827. RTLabelComponent *component = [RTLabelComponent componentWithString:nil tag:tag attributes:attributes];
  828. component.position = position;
  829. [components addObject:component];
  830. }
  831. last_position = position;
  832. }
  833. return [RTLabelExtractedComponent rtLabelExtractComponentsWithTextComponent:components plainText:data];
  834. }
  835. - (void)parse:(NSString *)data valid_tags:(NSArray *)valid_tags
  836. {
  837. //use to strip the HTML tags from the data
  838. NSScanner *scanner = nil;
  839. NSString *text = nil;
  840. NSString *tag = nil;
  841. NSMutableArray *components = [NSMutableArray array];
  842. //set up the scanner
  843. scanner = [NSScanner scannerWithString:data];
  844. NSMutableDictionary *lastAttributes = nil;
  845. NSUInteger last_position = 0;
  846. while([scanner isAtEnd] == NO)
  847. {
  848. //find start of tag
  849. [scanner scanUpToString:@"<" intoString:NULL];
  850. //find end of tag
  851. [scanner scanUpToString:@">" intoString:&text];
  852. NSMutableDictionary *attributes = nil;
  853. //get the name of the tag
  854. if([text rangeOfString:@"</"].location != NSNotFound)
  855. tag = [text substringFromIndex:2]; //remove </
  856. else
  857. {
  858. tag = [text substringFromIndex:1]; //remove <
  859. //find out if there is a space in the tag
  860. if([tag rangeOfString:@" "].location != NSNotFound)
  861. {
  862. attributes = [NSMutableDictionary dictionary];
  863. NSArray *rawAttributes = [tag componentsSeparatedByString:@" "];
  864. for (NSUInteger i=1; i<[rawAttributes count]; i++)
  865. {
  866. NSArray *pair = [[rawAttributes objectAtIndex:i] componentsSeparatedByString:@"="];
  867. if ([pair count]==2)
  868. {
  869. [attributes setObject:[pair objectAtIndex:1] forKey:[pair objectAtIndex:0]];
  870. }
  871. }
  872. //remove text after a space
  873. tag = [tag substringToIndex:[tag rangeOfString:@" "].location];
  874. }
  875. }
  876. //if not a valid tag, replace the tag with a space
  877. if([valid_tags containsObject:tag] == NO)
  878. {
  879. NSString *delimiter = [NSString stringWithFormat:@"%@>", text];
  880. NSUInteger position = [data rangeOfString:delimiter].location;
  881. BOOL isEnd = [delimiter rangeOfString:@"</"].location!=NSNotFound;
  882. if (position!=NSNotFound)
  883. {
  884. NSString *text2 = [data substringWithRange:NSMakeRange(last_position, position-last_position)];
  885. if (isEnd)
  886. {
  887. // is inside a tag
  888. [components addObject:[RTLabelComponent componentWithString:text2 tag:tag attributes:lastAttributes]];
  889. }
  890. else
  891. {
  892. // is outside a tag
  893. [components addObject:[RTLabelComponent componentWithString:text2 tag:nil attributes:lastAttributes]];
  894. }
  895. data = [data stringByReplacingOccurrencesOfString:delimiter withString:@"" options:NSCaseInsensitiveSearch range:NSMakeRange(last_position, position+delimiter.length-last_position)];
  896. last_position = position;
  897. }
  898. else
  899. {
  900. NSString *text2 = [data substringFromIndex:last_position];
  901. // is outside a tag
  902. [components addObject:[RTLabelComponent componentWithString:text2 tag:nil attributes:lastAttributes]];
  903. }
  904. lastAttributes = attributes;
  905. }
  906. }
  907. [self setTextComponents:components];
  908. [self setPlainText:data];
  909. }
  910. - (NSArray*)colorForHex:(NSString *)hexColor
  911. {
  912. hexColor = [[hexColor stringByTrimmingCharactersInSet:
  913. [NSCharacterSet whitespaceAndNewlineCharacterSet]
  914. ] uppercaseString];
  915. NSRange range;
  916. range.location = 0;
  917. range.length = 2;
  918. NSString *rString = [hexColor substringWithRange:range];
  919. range.location = 2;
  920. NSString *gString = [hexColor substringWithRange:range];
  921. range.location = 4;
  922. NSString *bString = [hexColor substringWithRange:range];
  923. // Scan values
  924. unsigned int r, g, b;
  925. [[NSScanner scannerWithString:rString] scanHexInt:&r];
  926. [[NSScanner scannerWithString:gString] scanHexInt:&g];
  927. [[NSScanner scannerWithString:bString] scanHexInt:&b];
  928. 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];
  929. return components;
  930. }
  931. - (NSString*)visibleText
  932. {
  933. [self render];
  934. NSString *text = [self.text substringWithRange:NSMakeRange(self.visibleRange.location, self.visibleRange.length)];
  935. return text;
  936. }
  937. #pragma mark deprecated methods
  938. - (void)setText:(NSString *)text extractedTextStyle:(NSDictionary*)extractTextStyle
  939. {
  940. _text = [text stringByReplacingOccurrencesOfString:@"<br>" withString:@"\n"];
  941. [self setTextComponents:[extractTextStyle objectForKey:@"textComponents"]];
  942. [self setPlainText:[extractTextStyle objectForKey:@"plainText"]];
  943. [self setNeedsDisplay];
  944. }
  945. + (NSDictionary*)preExtractTextStyle:(NSString*)data
  946. {
  947. NSString* paragraphReplacement = @"\n";
  948. RTLabelExtractedComponent *component = [RTLabel extractTextStyleFromText:data paragraphReplacement:paragraphReplacement];
  949. return [NSDictionary dictionaryWithObjectsAndKeys:component.textComponents, @"textComponents", component.plainText, @"plainText", nil];
  950. }
  951. @end