澎湃iPad上线版本

RTLabel.m 41KB

    // // RTLabel.m // RTLabelProject // /** * Copyright (c) 2010 Muh Hon Cheng * Created by honcheng on 1/6/11. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject * to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT * WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE AND NONINFRINGEMENT. IN NO EVENT * SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR * IN CONNECTION WITH THE SOFTWARE OR * THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * @author Muh Hon Cheng <honcheng@gmail.com> * @copyright 2011 Muh Hon Cheng * @version * */ #import "RTLabel.h" @interface RTLabelButton : UIButton @property (nonatomic, assign) long componentIndex; @property (nonatomic) NSURL *url; @end @implementation RTLabelButton @end @implementation RTLabelComponent - (id)initWithString:(NSString*)aText tag:(NSString*)aTagLabel attributes:(NSMutableDictionary*)theAttributes { self = [super init]; if (self) { _text = aText; _tagLabel = aTagLabel; _attributes = theAttributes; } return self; } + (id)componentWithString:(NSString*)aText tag:(NSString*)aTagLabel attributes:(NSMutableDictionary*)theAttributes { return [[self alloc] initWithString:aText tag:aTagLabel attributes:theAttributes]; } - (id)initWithTag:(NSString*)aTagLabel position:(int)aPosition attributes:(NSMutableDictionary*)theAttributes { self = [super init]; if (self) { _tagLabel = aTagLabel; _position = aPosition; _attributes = theAttributes; } return self; } +(id)componentWithTag:(NSString*)aTagLabel position:(int)aPosition attributes:(NSMutableDictionary*)theAttributes { return [[self alloc] initWithTag:aTagLabel position:aPosition attributes:theAttributes]; } - (NSString*)description { NSMutableString *desc = [NSMutableString string]; [desc appendFormat:@"text: %@", self.text]; [desc appendFormat:@", position: %ld", (long)self.position]; if (self.tagLabel) [desc appendFormat:@", tag: %@", self.tagLabel]; if (self.attributes) [desc appendFormat:@", attributes: %@", self.attributes]; return desc; } @end @implementation RTLabelExtractedComponent + (RTLabelExtractedComponent*)rtLabelExtractComponentsWithTextComponent:(NSMutableArray*)textComponents plainText:(NSString*)plainText { RTLabelExtractedComponent *component = [[RTLabelExtractedComponent alloc] init]; [component setTextComponents:textComponents]; [component setPlainText:plainText]; return component; } @end @interface RTLabel() - (CGFloat)frameHeight:(CTFrameRef)frame; - (NSArray *)components; - (void)parse:(NSString *)data valid_tags:(NSArray *)valid_tags; - (NSArray*) colorForHex:(NSString *)hexColor; - (void)render; #pragma mark - #pragma mark styling - (void)applyItalicStyleToText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length; - (void)applyBoldStyleToText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length; - (void)applyBoldItalicStyleToText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length; - (void)applyColor:(NSString*)value toText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length; - (void)applySingleUnderlineText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length; - (void)applyDoubleUnderlineText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length; - (void)applyUnderlineColor:(NSString*)value toText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length; - (void)applyFontAttributes:(NSDictionary*)attributes toText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length; - (void)applyParagraphStyleToText:(CFMutableAttributedStringRef)text attributes:(NSMutableDictionary*)attributes atPosition:(long)position withLength:(long)length; @end @implementation RTLabel - (id)initWithFrame:(CGRect)_frame { self = [super initWithFrame:_frame]; if (self) { [self initialize]; } return self; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { [self initialize]; } return self; } - (void)initialize { [self setBackgroundColor:[UIColor clearColor]]; _font = [UIFont systemFontOfSize:15]; _textColor = [UIColor blackColor]; _text = @""; _textAlignment = RTTextAlignmentLeft; _lineBreakMode = RTTextLineBreakModeWordWrapping; _lineSpacing = 3; _currentSelectedButtonComponentIndex = -1; _paragraphReplacement = @"\n"; [self setMultipleTouchEnabled:YES]; } - (void)setTextAlignment:(RTTextAlignment)textAlignment { _textAlignment = textAlignment; [self setNeedsDisplay]; } - (void)setLineBreakMode:(RTTextLineBreakMode)lineBreakMode { _lineBreakMode = lineBreakMode; [self setNeedsDisplay]; } - (void)drawRect:(CGRect)rect { [self render]; } - (void)render { if (self.currentSelectedButtonComponentIndex==-1) { for (id view in [self subviews]) { if ([view isKindOfClass:[UIView class]]) { [view removeFromSuperview]; } } } if (!self.plainText) return; CGContextRef context = UIGraphicsGetCurrentContext(); if (context != NULL) { // Drawing code. CGContextSetTextMatrix(context, CGAffineTransformIdentity); CGAffineTransform flipVertical = CGAffineTransformMake(1,0,0,-1,0,self.frame.size.height); CGContextConcatCTM(context, flipVertical); } // Initialize an attributed string. CFStringRef string = (__bridge CFStringRef)self.plainText; CFMutableAttributedStringRef attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0); CFAttributedStringReplaceString (attrString, CFRangeMake(0, 0), string); CFMutableDictionaryRef styleDict1 = ( CFDictionaryCreateMutable( (0), 0, (0), (0) ) ); // Create a color and add it as an attribute to the string. CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); CGColorSpaceRelease(rgbColorSpace); CFDictionaryAddValue( styleDict1, kCTForegroundColorAttributeName, [self.textColor CGColor] ); CFAttributedStringSetAttributes( attrString, CFRangeMake( 0, CFAttributedStringGetLength(attrString) ), styleDict1, 0 ); CFMutableDictionaryRef styleDict = ( CFDictionaryCreateMutable( (0), 0, (0), (0) ) ); [self applyParagraphStyleToText:attrString attributes:nil atPosition:0 withLength:CFAttributedStringGetLength(attrString)]; CTFontRef thisFont = CTFontCreateWithName ((__bridge CFStringRef)[self.font fontName], [self.font pointSize], NULL); CFAttributedStringSetAttribute(attrString, CFRangeMake(0, CFAttributedStringGetLength(attrString)), kCTFontAttributeName, thisFont); NSMutableArray *links = [NSMutableArray array]; NSMutableArray *textComponents = nil; if (self.highlighted) textComponents = self.highlightedTextComponents; else textComponents = self.textComponents; for (RTLabelComponent *component in textComponents) { long index = [textComponents indexOfObject:component]; component.componentIndex = index; if ([component.tagLabel caseInsensitiveCompare:@"i"] == NSOrderedSame) { // make font italic [self applyItalicStyleToText:attrString atPosition:component.position withLength:[component.text length]]; } else if ([component.tagLabel caseInsensitiveCompare:@"b"] == NSOrderedSame) { // make font bold [self applyBoldStyleToText:attrString atPosition:component.position withLength:[component.text length]]; } else if ([component.tagLabel caseInsensitiveCompare:@"bi"] == NSOrderedSame) { [self applyBoldItalicStyleToText:attrString atPosition:component.position withLength:[component.text length]]; } else if ([component.tagLabel caseInsensitiveCompare:@"a"] == NSOrderedSame) { if (self.currentSelectedButtonComponentIndex==index) { if (self.selectedLinkAttributes) { [self applyFontAttributes:self.selectedLinkAttributes toText:attrString atPosition:component.position withLength:[component.text length]]; } else { [self applyBoldStyleToText:attrString atPosition:component.position withLength:[component.text length]]; [self applyColor:@"#FF0000" toText:attrString atPosition:component.position withLength:[component.text length]]; } } else { if (self.linkAttributes) { [self applyFontAttributes:self.linkAttributes toText:attrString atPosition:component.position withLength:[component.text length]]; } else { [self applyBoldStyleToText:attrString atPosition:component.position withLength:[component.text length]]; [self applySingleUnderlineText:attrString atPosition:component.position withLength:[component.text length]]; } } NSString *value = [component.attributes objectForKey:@"href"]; value = [value stringByReplacingOccurrencesOfString:@"'" withString:@""]; [component.attributes setObject:value forKey:@"href"]; [links addObject:component]; } else if ([component.tagLabel caseInsensitiveCompare:@"u"] == NSOrderedSame || [component.tagLabel caseInsensitiveCompare:@"uu"] == NSOrderedSame) { // underline if ([component.tagLabel caseInsensitiveCompare:@"u"] == NSOrderedSame) { [self applySingleUnderlineText:attrString atPosition:component.position withLength:[component.text length]]; } else if ([component.tagLabel caseInsensitiveCompare:@"uu"] == NSOrderedSame) { [self applyDoubleUnderlineText:attrString atPosition:component.position withLength:[component.text length]]; } if ([component.attributes objectForKey:@"color"]) { NSString *value = [component.attributes objectForKey:@"color"]; [self applyUnderlineColor:value toText:attrString atPosition:component.position withLength:[component.text length]]; } } else if ([component.tagLabel caseInsensitiveCompare:@"font"] == NSOrderedSame) { [self applyFontAttributes:component.attributes toText:attrString atPosition:component.position withLength:[component.text length]]; } else if ([component.tagLabel caseInsensitiveCompare:@"p"] == NSOrderedSame) { [self applyParagraphStyleToText:attrString attributes:component.attributes atPosition:component.position withLength:[component.text length]]; } else if ([component.tagLabel caseInsensitiveCompare:@"center"] == NSOrderedSame) { [self applyCenterStyleToText:attrString attributes:component.attributes atPosition:component.position withLength:[component.text length]]; } } // Create the framesetter with the attributed string. CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString); CFRelease(attrString); // Initialize a rectangular path. CGMutablePathRef path = CGPathCreateMutable(); CGRect bounds = CGRectMake(0.0, 0.0, self.frame.size.width, self.frame.size.height); CGPathAddRect(path, NULL, bounds); // Create the frame and draw it into the graphics context //CTFrameRef CTFrameRef frame = CTFramesetterCreateFrame(framesetter,CFRangeMake(0, 0), path, NULL); CFRange range; CGSize constraint = CGSizeMake(self.frame.size.width, CGFLOAT_MAX); self.optimumSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, [self.plainText length]), nil, constraint, &range); if (self.currentSelectedButtonComponentIndex==-1) { // only check for linkable items the first time, not when it's being redrawn on button pressed for (RTLabelComponent *linkableComponents in links) { float height = 0.0; CFArrayRef frameLines = CTFrameGetLines(frame); for (CFIndex i=0; i<CFArrayGetCount(frameLines); i++) { CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(frameLines, i); CFRange lineRange = CTLineGetStringRange(line); CGFloat ascent; CGFloat descent; CGFloat leading; CTLineGetTypographicBounds(line, &ascent, &descent, &leading); CGPoint origin; CTFrameGetLineOrigins(frame, CFRangeMake(i, 1), &origin); 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)) { CGFloat secondaryOffset; CGFloat primaryOffset = CTLineGetOffsetForStringIndex(CFArrayGetValueAtIndex(frameLines,i), linkableComponents.position, &secondaryOffset); CGFloat primaryOffset2 = CTLineGetOffsetForStringIndex(CFArrayGetValueAtIndex(frameLines,i), linkableComponents.position+linkableComponents.text.length, NULL); CGFloat button_width = primaryOffset2 - primaryOffset; RTLabelButton *button = [[RTLabelButton alloc] initWithFrame:CGRectMake(primaryOffset+origin.x, height, button_width, ascent+descent)]; [button setBackgroundColor:[UIColor colorWithWhite:0 alpha:0]]; [button setComponentIndex:linkableComponents.componentIndex]; [button setUrl:[NSURL URLWithString:[linkableComponents.attributes objectForKey:@"href"]]]; [button addTarget:self action:@selector(onButtonTouchDown:) forControlEvents:UIControlEventTouchDown]; [button addTarget:self action:@selector(onButtonTouchUpOutside:) forControlEvents:UIControlEventTouchUpOutside]; [button addTarget:self action:@selector(onButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; [self addSubview:button]; } origin.y = self.frame.size.height - origin.y; height = origin.y + descent + _lineSpacing; } } } self.visibleRange = CTFrameGetVisibleStringRange(frame); CFRelease(thisFont); CFRelease(path); CFRelease(styleDict1); CFRelease(styleDict); CFRelease(framesetter); CTFrameDraw(frame, context); CFRelease(frame); } #pragma mark - #pragma mark styling - (void)applyParagraphStyleToText:(CFMutableAttributedStringRef)text attributes:(NSMutableDictionary*)attributes atPosition:(long)position withLength:(long)length { CFMutableDictionaryRef styleDict = ( CFDictionaryCreateMutable( (0), 0, (0), (0) ) ); // direction CTWritingDirection direction = kCTWritingDirectionLeftToRight; // leading CGFloat firstLineIndent = 0.0; CGFloat headIndent = 0.0; CGFloat tailIndent = 0.0; CGFloat lineHeightMultiple = 1.0; CGFloat maxLineHeight = 0; CGFloat minLineHeight = 0; CGFloat paragraphSpacing = 0.0; CGFloat paragraphSpacingBefore = 0.0; CTTextAlignment textAlignment = (CTTextAlignment)_textAlignment; CTLineBreakMode lineBreakMode = (CTLineBreakMode)_lineBreakMode; CGFloat lineSpacing = _lineSpacing; for (NSUInteger i=0; i<[[attributes allKeys] count]; i++) { NSString *key = [[attributes allKeys] objectAtIndex:i]; id value = [attributes objectForKey:key]; if ([key caseInsensitiveCompare:@"align"] == NSOrderedSame) { if ([value caseInsensitiveCompare:@"left"] == NSOrderedSame) { textAlignment = kCTLeftTextAlignment; } else if ([value caseInsensitiveCompare:@"right"] == NSOrderedSame) { textAlignment = kCTRightTextAlignment; } else if ([value caseInsensitiveCompare:@"justify"] == NSOrderedSame) { textAlignment = kCTJustifiedTextAlignment; } else if ([value caseInsensitiveCompare:@"center"] == NSOrderedSame) { textAlignment = kCTCenterTextAlignment; } } else if ([key caseInsensitiveCompare:@"indent"] == NSOrderedSame) { firstLineIndent = [value floatValue]; } else if ([key caseInsensitiveCompare:@"linebreakmode"] == NSOrderedSame) { if ([value caseInsensitiveCompare:@"wordwrap"] == NSOrderedSame) { lineBreakMode = kCTLineBreakByWordWrapping; } else if ([value caseInsensitiveCompare:@"charwrap"] == NSOrderedSame) { lineBreakMode = kCTLineBreakByCharWrapping; } else if ([value caseInsensitiveCompare:@"clipping"] == NSOrderedSame) { lineBreakMode = kCTLineBreakByClipping; } else if ([value caseInsensitiveCompare:@"truncatinghead"] == NSOrderedSame) { lineBreakMode = kCTLineBreakByTruncatingHead; } else if ([value caseInsensitiveCompare:@"truncatingtail"] == NSOrderedSame) { lineBreakMode = kCTLineBreakByTruncatingTail; } else if ([value caseInsensitiveCompare:@"truncatingmiddle"] == NSOrderedSame) { lineBreakMode = kCTLineBreakByTruncatingMiddle; } } } CTParagraphStyleSetting theSettings[] = { { kCTParagraphStyleSpecifierAlignment, sizeof(CTTextAlignment), &textAlignment }, { kCTParagraphStyleSpecifierLineBreakMode, sizeof(CTLineBreakMode), &lineBreakMode }, { kCTParagraphStyleSpecifierBaseWritingDirection, sizeof(CTWritingDirection), &direction }, { kCTParagraphStyleSpecifierMinimumLineSpacing, sizeof(CGFloat), &lineSpacing }, // leading { kCTParagraphStyleSpecifierMaximumLineSpacing, sizeof(CGFloat), &lineSpacing }, // leading { kCTParagraphStyleSpecifierFirstLineHeadIndent, sizeof(CGFloat), &firstLineIndent }, { kCTParagraphStyleSpecifierHeadIndent, sizeof(CGFloat), &headIndent }, { kCTParagraphStyleSpecifierTailIndent, sizeof(CGFloat), &tailIndent }, { kCTParagraphStyleSpecifierLineHeightMultiple, sizeof(CGFloat), &lineHeightMultiple }, { kCTParagraphStyleSpecifierMaximumLineHeight, sizeof(CGFloat), &maxLineHeight }, { kCTParagraphStyleSpecifierMinimumLineHeight, sizeof(CGFloat), &minLineHeight }, { kCTParagraphStyleSpecifierParagraphSpacing, sizeof(CGFloat), &paragraphSpacing }, { kCTParagraphStyleSpecifierParagraphSpacingBefore, sizeof(CGFloat), &paragraphSpacingBefore } }; CTParagraphStyleRef theParagraphRef = CTParagraphStyleCreate(theSettings, sizeof(theSettings) / sizeof(CTParagraphStyleSetting)); CFDictionaryAddValue( styleDict, kCTParagraphStyleAttributeName, theParagraphRef ); CFAttributedStringSetAttributes( text, CFRangeMake(position, length), styleDict, 0 ); CFRelease(theParagraphRef); CFRelease(styleDict); } - (void)applyCenterStyleToText:(CFMutableAttributedStringRef)text attributes:(NSMutableDictionary*)attributes atPosition:(long)position withLength:(long)length { CFMutableDictionaryRef styleDict = ( CFDictionaryCreateMutable( (0), 0, (0), (0) ) ); // direction CTWritingDirection direction = kCTWritingDirectionLeftToRight; // leading CGFloat firstLineIndent = 0.0; CGFloat headIndent = 0.0; CGFloat tailIndent = 0.0; CGFloat lineHeightMultiple = 1.0; CGFloat maxLineHeight = 0; CGFloat minLineHeight = 0; CGFloat paragraphSpacing = 0.0; CGFloat paragraphSpacingBefore = 0.0; int textAlignment = _textAlignment; int lineBreakMode = _lineBreakMode; int lineSpacing = (int)_lineSpacing; textAlignment = kCTCenterTextAlignment; CTParagraphStyleSetting theSettings[] = { { kCTParagraphStyleSpecifierAlignment, sizeof(CTTextAlignment), &textAlignment }, { kCTParagraphStyleSpecifierLineBreakMode, sizeof(CTLineBreakMode), &lineBreakMode }, { kCTParagraphStyleSpecifierBaseWritingDirection, sizeof(CTWritingDirection), &direction }, { kCTParagraphStyleSpecifierLineSpacing, sizeof(CGFloat), &lineSpacing }, { kCTParagraphStyleSpecifierFirstLineHeadIndent, sizeof(CGFloat), &firstLineIndent }, { kCTParagraphStyleSpecifierHeadIndent, sizeof(CGFloat), &headIndent }, { kCTParagraphStyleSpecifierTailIndent, sizeof(CGFloat), &tailIndent }, { kCTParagraphStyleSpecifierLineHeightMultiple, sizeof(CGFloat), &lineHeightMultiple }, { kCTParagraphStyleSpecifierMaximumLineHeight, sizeof(CGFloat), &maxLineHeight }, { kCTParagraphStyleSpecifierMinimumLineHeight, sizeof(CGFloat), &minLineHeight }, { kCTParagraphStyleSpecifierParagraphSpacing, sizeof(CGFloat), &paragraphSpacing }, { kCTParagraphStyleSpecifierParagraphSpacingBefore, sizeof(CGFloat), &paragraphSpacingBefore } }; CTParagraphStyleRef theParagraphRef = CTParagraphStyleCreate(theSettings, sizeof(theSettings) / sizeof(CTParagraphStyleSetting)); CFDictionaryAddValue( styleDict, kCTParagraphStyleAttributeName, theParagraphRef ); CFAttributedStringSetAttributes( text, CFRangeMake(position, length), styleDict, 0 ); CFRelease(theParagraphRef); CFRelease(styleDict); } - (void)applySingleUnderlineText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length { CFAttributedStringSetAttribute(text, CFRangeMake(position, length), kCTUnderlineStyleAttributeName, (__bridge CFNumberRef)[NSNumber numberWithInt:kCTUnderlineStyleSingle]); } - (void)applyDoubleUnderlineText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length { CFAttributedStringSetAttribute(text, CFRangeMake(position, length), kCTUnderlineStyleAttributeName, (__bridge CFNumberRef)[NSNumber numberWithInt:kCTUnderlineStyleDouble]); } - (void)applyItalicStyleToText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length { CFTypeRef actualFontRef = CFAttributedStringGetAttribute(text, position, kCTFontAttributeName, NULL); CTFontRef italicFontRef = CTFontCreateCopyWithSymbolicTraits(actualFontRef, 0.0, NULL, kCTFontItalicTrait, kCTFontItalicTrait); if (!italicFontRef) { //fallback to system italic font UIFont *font = [UIFont italicSystemFontOfSize:CTFontGetSize(actualFontRef)]; italicFontRef = CTFontCreateWithName ((__bridge CFStringRef)[font fontName], [font pointSize], NULL); } CFAttributedStringSetAttribute(text, CFRangeMake(position, length), kCTFontAttributeName, italicFontRef); CFRelease(italicFontRef); } - (void)applyFontAttributes:(NSDictionary*)attributes toText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length { for (NSString *key in attributes) { NSString *value = [attributes objectForKey:key]; value = [value stringByReplacingOccurrencesOfString:@"'" withString:@""]; if ([key caseInsensitiveCompare:@"color"] == NSOrderedSame) { [self applyColor:value toText:text atPosition:position withLength:length]; } else if ([key caseInsensitiveCompare:@"stroke"] == NSOrderedSame) { CFAttributedStringSetAttribute(text, CFRangeMake(position, length), kCTStrokeWidthAttributeName, (__bridge CFTypeRef)([NSNumber numberWithFloat:[[attributes objectForKey:@"stroke"] intValue]])); } else if ([key caseInsensitiveCompare:@"kern"] == NSOrderedSame) { CFAttributedStringSetAttribute(text, CFRangeMake(position, length), kCTKernAttributeName, (__bridge CFTypeRef)([NSNumber numberWithFloat:[[attributes objectForKey:@"kern"] intValue]])); } else if ([key caseInsensitiveCompare:@"underline"] == NSOrderedSame) { int numberOfLines = [value intValue]; if (numberOfLines==1) { [self applySingleUnderlineText:text atPosition:position withLength:length]; } else if (numberOfLines==2) { [self applyDoubleUnderlineText:text atPosition:position withLength:length]; } } else if ([key caseInsensitiveCompare:@"style"] == NSOrderedSame) { if ([value caseInsensitiveCompare:@"bold"] == NSOrderedSame) { [self applyBoldStyleToText:text atPosition:position withLength:length]; } else if ([value caseInsensitiveCompare:@"italic"] == NSOrderedSame) { [self applyItalicStyleToText:text atPosition:position withLength:length]; } } } UIFont *font = nil; if ([attributes objectForKey:@"face"] && [attributes objectForKey:@"size"]) { NSString *fontName = [attributes objectForKey:@"face"]; fontName = [fontName stringByReplacingOccurrencesOfString:@"'" withString:@""]; font = [UIFont fontWithName:fontName size:[[attributes objectForKey:@"size"] intValue]]; } else if ([attributes objectForKey:@"face"] && ![attributes objectForKey:@"size"]) { NSString *fontName = [attributes objectForKey:@"face"]; fontName = [fontName stringByReplacingOccurrencesOfString:@"'" withString:@""]; font = [UIFont fontWithName:fontName size:self.font.pointSize]; } else if (![attributes objectForKey:@"face"] && [attributes objectForKey:@"size"]) { font = [UIFont fontWithName:[self.font fontName] size:[[attributes objectForKey:@"size"] intValue]]; } if (font) { CTFontRef customFont = CTFontCreateWithName ((__bridge CFStringRef)[font fontName], [font pointSize], NULL); CFAttributedStringSetAttribute(text, CFRangeMake(position, length), kCTFontAttributeName, customFont); CFRelease(customFont); } } - (void)applyBoldStyleToText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length { if (length == 0 ) { // ShowTextMessage(@"文章格式错误"); return; } CFTypeRef actualFontRef = CFAttributedStringGetAttribute(text, position, kCTFontAttributeName, NULL); CTFontRef boldFontRef = CTFontCreateCopyWithSymbolicTraits(actualFontRef, 0.0, NULL, kCTFontBoldTrait, kCTFontBoldTrait); if (!boldFontRef) { //fallback to system bold font UIFont *font = [UIFont boldSystemFontOfSize:CTFontGetSize(actualFontRef)]; boldFontRef = CTFontCreateWithName ((__bridge CFStringRef)[font fontName], [font pointSize], NULL); } CFAttributedStringSetAttribute(text, CFRangeMake(position, length), kCTFontAttributeName, boldFontRef); CFRelease(boldFontRef); } - (void)applyBoldItalicStyleToText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length { CFTypeRef actualFontRef = CFAttributedStringGetAttribute(text, position, kCTFontAttributeName, NULL); CTFontRef boldItalicFontRef = CTFontCreateCopyWithSymbolicTraits(actualFontRef, 0.0, NULL, kCTFontBoldTrait | kCTFontItalicTrait , kCTFontBoldTrait | kCTFontItalicTrait); if (!boldItalicFontRef) { //try fallback to system boldItalic font NSString *fontName = [NSString stringWithFormat:@"%@-BoldOblique", self.font.fontName]; boldItalicFontRef = CTFontCreateWithName ((__bridge CFStringRef)fontName, [self.font pointSize], NULL); } if (boldItalicFontRef) { CFAttributedStringSetAttribute(text, CFRangeMake(position, length), kCTFontAttributeName, boldItalicFontRef); CFRelease(boldItalicFontRef); } } - (void)applyColor:(NSString*)value toText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length { if ([value rangeOfString:@"#"].location==0) { CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); value = [value stringByReplacingOccurrencesOfString:@"#" withString:@""]; NSArray *colorComponents = [self colorForHex:value]; CGFloat components[] = { [[colorComponents objectAtIndex:0] floatValue] , [[colorComponents objectAtIndex:1] floatValue] , [[colorComponents objectAtIndex:2] floatValue] , [[colorComponents objectAtIndex:3] floatValue] }; CGColorRef color = CGColorCreate(rgbColorSpace, components); CFAttributedStringSetAttribute(text, CFRangeMake(position, length),kCTForegroundColorAttributeName, color); CFRelease(color); CGColorSpaceRelease(rgbColorSpace); } else { value = [value stringByAppendingString:@"Color"]; SEL colorSel = NSSelectorFromString(value); UIColor *_color = nil; if ([UIColor respondsToSelector:colorSel]) { _color = [UIColor performSelector:colorSel]; CGColorRef color = [_color CGColor]; CFAttributedStringSetAttribute(text, CFRangeMake(position, length),kCTForegroundColorAttributeName, color); } } } - (void)applyUnderlineColor:(NSString*)value toText:(CFMutableAttributedStringRef)text atPosition:(long)position withLength:(long)length { value = [value stringByReplacingOccurrencesOfString:@"'" withString:@""]; if ([value rangeOfString:@"#"].location==0) { CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); value = [value stringByReplacingOccurrencesOfString:@"#" withString:@"0x"]; NSArray *colorComponents = [self colorForHex:value]; CGFloat components[] = { [[colorComponents objectAtIndex:0] floatValue] , [[colorComponents objectAtIndex:1] floatValue] , [[colorComponents objectAtIndex:2] floatValue] , [[colorComponents objectAtIndex:3] floatValue] }; CGColorRef color = CGColorCreate(rgbColorSpace, components); CFAttributedStringSetAttribute(text, CFRangeMake(position, length),kCTUnderlineColorAttributeName, color); CGColorRelease(color); CGColorSpaceRelease(rgbColorSpace); } else { value = [value stringByAppendingString:@"Color"]; SEL colorSel = NSSelectorFromString(value); if ([UIColor respondsToSelector:colorSel]) { UIColor *_color = [UIColor performSelector:colorSel]; CGColorRef color = [_color CGColor]; CFAttributedStringSetAttribute(text, CFRangeMake(position, length),kCTUnderlineColorAttributeName, color); //CGColorRelease(color); } } } #pragma mark - #pragma mark button - (void)onButtonTouchDown:(id)sender { RTLabelButton *button = (RTLabelButton*)sender; [self setCurrentSelectedButtonComponentIndex:button.componentIndex]; [self setNeedsDisplay]; } - (void)onButtonTouchUpOutside:(id)sender { [self setCurrentSelectedButtonComponentIndex:-1]; [self setNeedsDisplay]; } - (void)onButtonPressed:(id)sender { RTLabelButton *button = (RTLabelButton*)sender; [self setCurrentSelectedButtonComponentIndex:-1]; [self setNeedsDisplay]; if ([self.delegate respondsToSelector:@selector(rtLabel:didSelectLinkWithURL:)]) { [self.delegate rtLabel:self didSelectLinkWithURL:button.url]; } } - (CGSize)optimumSize { [self render]; return _optimumSize; } - (void)setLineSpacing:(CGFloat)lineSpacing { _lineSpacing = lineSpacing; [self setNeedsDisplay]; } - (void)setHighlighted:(BOOL)highlighted { if (highlighted!=_highlighted) { _highlighted = highlighted; [self setNeedsDisplay]; } } - (void)setHighlightedText:(NSString *)text { _highlightedText = [text stringByReplacingOccurrencesOfString:@"<br>" withString:@"\n"]; RTLabelExtractedComponent *component = [RTLabel extractTextStyleFromText:_highlightedText paragraphReplacement:self.paragraphReplacement]; [self setHighlightedTextComponents:component.textComponents]; } - (void)setText:(NSString *)text { _text = [text stringByReplacingOccurrencesOfString:@"<br>" withString:@"\n"]; RTLabelExtractedComponent *component = [RTLabel extractTextStyleFromText:_text paragraphReplacement:self.paragraphReplacement]; [self setTextComponents:component.textComponents]; [self setPlainText:component.plainText]; [self setNeedsDisplay]; } - (void)setText:(NSString *)text extractedTextComponent:(RTLabelExtractedComponent*)extractedComponent { _text = [text stringByReplacingOccurrencesOfString:@"<br>" withString:@"\n"]; [self setTextComponents:extractedComponent.textComponents]; [self setPlainText:extractedComponent.plainText]; [self setNeedsDisplay]; } - (void)setHighlightedText:(NSString *)text extractedTextComponent:(RTLabelExtractedComponent*)extractedComponent { _highlightedText = [text stringByReplacingOccurrencesOfString:@"<br>" withString:@"\n"]; [self setHighlightedTextComponents:extractedComponent.textComponents]; } // http://forums.macrumors.com/showthread.php?t=925312 // not accurate - (CGFloat)frameHeight:(CTFrameRef)theFrame { CFArrayRef lines = CTFrameGetLines(theFrame); CGFloat height = 0.0; CGFloat ascent, descent, leading; for (CFIndex index = 0; index < CFArrayGetCount(lines); index++) { CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, index); CTLineGetTypographicBounds(line, &ascent, &descent, &leading); height += (ascent + fabsf(descent) + leading); } return ceilf(height); } - (void)dealloc { self.delegate = nil; } - (NSArray *)components { NSScanner *scanner = [NSScanner scannerWithString:self.text]; [scanner setCharactersToBeSkipped:nil]; NSMutableArray *components = [NSMutableArray array]; while (![scanner isAtEnd]) { NSString *currentComponent; BOOL foundComponent = [scanner scanUpToString:@"http" intoString:&currentComponent]; if (foundComponent) { [components addObject:currentComponent]; NSString *string; BOOL foundURLComponent = [scanner scanUpToString:@" " intoString:&string]; if (foundURLComponent) { // if last character of URL is punctuation, its probably not part of the URL NSCharacterSet *punctuationSet = [NSCharacterSet punctuationCharacterSet]; NSInteger lastCharacterIndex = string.length - 1; if ([punctuationSet characterIsMember:[string characterAtIndex:lastCharacterIndex]]) { // remove the punctuation from the URL string and move the scanner back string = [string substringToIndex:lastCharacterIndex]; [scanner setScanLocation:scanner.scanLocation - 1]; } [components addObject:string]; } } else { // first string is a link NSString *string; BOOL foundURLComponent = [scanner scanUpToString:@" " intoString:&string]; if (foundURLComponent) { [components addObject:string]; } } } return [components copy]; } + (RTLabelExtractedComponent*)extractTextStyleFromText:(NSString*)data paragraphReplacement:(NSString*)paragraphReplacement { NSScanner *scanner = nil; NSString *text = nil; NSString *tag = nil; NSMutableArray *components = [NSMutableArray array]; long last_position = 0; scanner = [NSScanner scannerWithString:data]; while (![scanner isAtEnd]) { [scanner scanUpToString:@"<" intoString:NULL]; [scanner scanUpToString:@">" intoString:&text]; NSString *delimiter = [NSString stringWithFormat:@"%@>", text]; long position = (long)[data rangeOfString:delimiter].location; if (position!=NSNotFound) { if ([delimiter rangeOfString:@"<p"].location==0) { data = [data stringByReplacingOccurrencesOfString:delimiter withString:paragraphReplacement options:NSCaseInsensitiveSearch range:NSMakeRange(last_position, position+delimiter.length-last_position)]; } else { data = [data stringByReplacingOccurrencesOfString:delimiter withString:@"" options:NSCaseInsensitiveSearch range:NSMakeRange(last_position, position+delimiter.length-last_position)]; } } data = [data stringByReplacingOccurrencesOfString:@"&lt;" withString:@"<"]; data = [data stringByReplacingOccurrencesOfString:@"&gt;" withString:@">"]; if ([text rangeOfString:@"</"].location==0) { // end of tag tag = [text substringFromIndex:2]; if (position!=NSNotFound) { for (long i= (long)[components count]-1; i>=0; i--) { RTLabelComponent *component = [components objectAtIndex:i]; if (component.text==nil && [component.tagLabel isEqualToString:tag]) { NSString *text2 = [data substringWithRange:NSMakeRange(component.position, position-component.position)]; component.text = text2; break; } } } } else { // start of tag NSArray *textComponents = [[text substringFromIndex:1] componentsSeparatedByString:@" "]; tag = [textComponents objectAtIndex:0]; //TPLOG(@"start of tag: %@", tag); NSMutableDictionary *attributes = [NSMutableDictionary dictionary]; for (NSUInteger i=1; i<[textComponents count]; i++) { NSArray *pair = [[textComponents objectAtIndex:i] componentsSeparatedByString:@"="]; if ([pair count] > 0) { NSString *key = [[pair objectAtIndex:0] lowercaseString]; if ([pair count]>=2) { // Trim " charactere NSString *value = [[pair subarrayWithRange:NSMakeRange(1, [pair count] - 1)] componentsJoinedByString:@"="]; value = [value stringByReplacingOccurrencesOfString:@"\"" withString:@"" options:NSLiteralSearch range:NSMakeRange(0, 1)]; value = [value stringByReplacingOccurrencesOfString:@"\"" withString:@"" options:NSLiteralSearch range:NSMakeRange([value length]-1, 1)]; [attributes setObject:value forKey:key]; } else if ([pair count]==1) { [attributes setObject:key forKey:key]; } } } RTLabelComponent *component = [RTLabelComponent componentWithString:nil tag:tag attributes:attributes]; component.position = position; [components addObject:component]; } last_position = position; } return [RTLabelExtractedComponent rtLabelExtractComponentsWithTextComponent:components plainText:data]; } - (void)parse:(NSString *)data valid_tags:(NSArray *)valid_tags { //use to strip the HTML tags from the data NSScanner *scanner = nil; NSString *text = nil; NSString *tag = nil; NSMutableArray *components = [NSMutableArray array]; //set up the scanner scanner = [NSScanner scannerWithString:data]; NSMutableDictionary *lastAttributes = nil; long last_position = 0; while([scanner isAtEnd] == NO) { //find start of tag [scanner scanUpToString:@"<" intoString:NULL]; //find end of tag [scanner scanUpToString:@">" intoString:&text]; NSMutableDictionary *attributes = nil; //get the name of the tag if([text rangeOfString:@"</"].location != NSNotFound) tag = [text substringFromIndex:2]; //remove </ else { tag = [text substringFromIndex:1]; //remove < //find out if there is a space in the tag if([tag rangeOfString:@" "].location != NSNotFound) { attributes = [NSMutableDictionary dictionary]; NSArray *rawAttributes = [tag componentsSeparatedByString:@" "]; for (NSUInteger i=1; i<[rawAttributes count]; i++) { NSArray *pair = [[rawAttributes objectAtIndex:i] componentsSeparatedByString:@"="]; if ([pair count]==2) { [attributes setObject:[pair objectAtIndex:1] forKey:[pair objectAtIndex:0]]; } } //remove text after a space tag = [tag substringToIndex:[tag rangeOfString:@" "].location]; } } //if not a valid tag, replace the tag with a space if([valid_tags containsObject:tag] == NO) { NSString *delimiter = [NSString stringWithFormat:@"%@>", text]; long position = (long)[data rangeOfString:delimiter].location; BOOL isEnd = [delimiter rangeOfString:@"</"].location!=NSNotFound; if (position!=NSNotFound) { NSString *text2 = [data substringWithRange:NSMakeRange(last_position, position-last_position)]; if (isEnd) { // is inside a tag [components addObject:[RTLabelComponent componentWithString:text2 tag:tag attributes:lastAttributes]]; } else { // is outside a tag [components addObject:[RTLabelComponent componentWithString:text2 tag:nil attributes:lastAttributes]]; } data = [data stringByReplacingOccurrencesOfString:delimiter withString:@"" options:NSCaseInsensitiveSearch range:NSMakeRange(last_position, position+delimiter.length-last_position)]; last_position = position; } else { NSString *text2 = [data substringFromIndex:last_position]; // is outside a tag [components addObject:[RTLabelComponent componentWithString:text2 tag:nil attributes:lastAttributes]]; } lastAttributes = attributes; } } [self setTextComponents:components]; [self setPlainText:data]; } - (NSArray*)colorForHex:(NSString *)hexColor { hexColor = [[hexColor stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet] ] uppercaseString]; NSRange range; range.location = 0; range.length = 2; NSString *rString = [hexColor substringWithRange:range]; range.location = 2; NSString *gString = [hexColor substringWithRange:range]; range.location = 4; NSString *bString = [hexColor substringWithRange:range]; // Scan values unsigned int r, g, b; [[NSScanner scannerWithString:rString] scanHexInt:&r]; [[NSScanner scannerWithString:gString] scanHexInt:&g]; [[NSScanner scannerWithString:bString] scanHexInt:&b]; 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]; return components; } - (NSString*)visibleText { [self render]; NSString *text = [self.text substringWithRange:NSMakeRange(self.visibleRange.location, self.visibleRange.length)]; return text; } #pragma mark deprecated methods - (void)setText:(NSString *)text extractedTextStyle:(NSDictionary*)extractTextStyle { _text = [text stringByReplacingOccurrencesOfString:@"<br>" withString:@"\n"]; [self setTextComponents:[extractTextStyle objectForKey:@"textComponents"]]; [self setPlainText:[extractTextStyle objectForKey:@"plainText"]]; [self setNeedsDisplay]; } + (NSDictionary*)preExtractTextStyle:(NSString*)data { NSString* paragraphReplacement = @"\n"; RTLabelExtractedComponent *component = [RTLabel extractTextStyleFromText:data paragraphReplacement:paragraphReplacement]; return [NSDictionary dictionaryWithObjectsAndKeys:component.textComponents, @"textComponents", component.plainText, @"plainText", nil]; } @end