THLabel.m 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. //
  2. // THLabel.m
  3. //
  4. // Version 1.4.8
  5. //
  6. // Created by Tobias Hagemann on 11/25/12.
  7. // Copyright (c) 2012-2016 tobiha.de. All rights reserved.
  8. //
  9. // Original source and inspiration from:
  10. // FXLabel by Nick Lockwood,
  11. // https://github.com/nicklockwood/FXLabel
  12. // KSLabel by Kai Schweiger,
  13. // https://github.com/vigorouscoding/KSLabel
  14. // GTMFadeTruncatingLabel by Google,
  15. // https://github.com/google/google-toolbox-for-mac/tree/master/iPhone
  16. //
  17. // Big thanks to Jason Miller for showing me sample code of his implementation
  18. // using Core Text! It inspired me to dig deeper and move away from drawing
  19. // with NSAttributedString on iOS 7, which caused a lot of problems.
  20. //
  21. // Distributed under the permissive zlib license
  22. // Get the latest version from here:
  23. //
  24. // https://github.com/tobihagemann/THLabel
  25. //
  26. // This software is provided 'as-is', without any express or implied
  27. // warranty. In no event will the authors be held liable for any damages
  28. // arising from the use of this software.
  29. //
  30. // Permission is granted to anyone to use this software for any purpose,
  31. // including commercial applications, and to alter it and redistribute it
  32. // freely, subject to the following restrictions:
  33. //
  34. // 1. The origin of this software must not be misrepresented; you must not
  35. // claim that you wrote the original software. If you use this software
  36. // in a product, an acknowledgment in the product documentation would be
  37. // appreciated but is not required.
  38. //
  39. // 2. Altered source versions must be plainly marked as such, and must not be
  40. // misrepresented as being the original software.
  41. //
  42. // 3. This notice may not be removed or altered from any source distribution.
  43. //
  44. #import <Availability.h>
  45. #if !__has_feature(objc_arc)
  46. #error This class requires automatic reference counting.
  47. #endif
  48. #import <CoreText/CoreText.h>
  49. #import "THLabel.h"
  50. @implementation THLabel
  51. - (instancetype)initWithFrame:(CGRect)frame {
  52. if (self = [super initWithFrame:frame]) {
  53. self.backgroundColor = [UIColor clearColor];
  54. [self setDefaults];
  55. }
  56. return self;
  57. }
  58. - (instancetype)initWithCoder:(NSCoder *)aDecoder {
  59. if (self = [super initWithCoder:aDecoder]) {
  60. [self setDefaults];
  61. }
  62. return self;
  63. }
  64. - (void)awakeFromNib
  65. {
  66. [super awakeFromNib];
  67. }
  68. - (void)setDefaults {
  69. self.clipsToBounds = YES;
  70. self.letterSpacing = 0.0;
  71. self.gradientStartPoint = CGPointMake(0.5, 0.2);
  72. self.gradientEndPoint = CGPointMake(0.5, 0.8);
  73. self.automaticallyAdjustTextInsets = YES;
  74. }
  75. - (BOOL)hasShadow {
  76. return self.shadowColor && ![self.shadowColor isEqual:[UIColor clearColor]] && (self.shadowBlur > 0.0 || !CGSizeEqualToSize(self.shadowOffset, CGSizeZero));
  77. }
  78. - (BOOL)hasInnerShadow {
  79. return self.innerShadowColor && ![self.innerShadowColor isEqual:[UIColor clearColor]] && (self.innerShadowBlur > 0.0 || !CGSizeEqualToSize(self.innerShadowOffset, CGSizeZero));
  80. }
  81. - (BOOL)hasStroke {
  82. return self.strokeSize > 0.0 && ![self.strokeColor isEqual:[UIColor clearColor]];
  83. }
  84. - (BOOL)hasGradient {
  85. return self.gradientColors.count > 1;
  86. }
  87. - (BOOL)hasFadeTruncating {
  88. return self.fadeTruncatingMode != THLabelFadeTruncatingModeNone;
  89. }
  90. - (CGSize)sizeThatFits:(CGSize)size {
  91. return [self intrinsicContentSize];
  92. }
  93. - (CGSize)intrinsicContentSize {
  94. if (!self.text || [self.text isEqualToString:@""]) {
  95. return CGSizeZero;
  96. }
  97. CGRect textRect;
  98. CTFrameRef frameRef = [self frameRefFromSize:CGSizeMake(self.preferredMaxLayoutWidth, CGFLOAT_MAX) textRectOutput:&textRect];
  99. CFRelease(frameRef);
  100. return CGSizeMake(ceilf(CGRectGetWidth(textRect) + self.textInsets.left + self.textInsets.right),
  101. ceilf(CGRectGetHeight(textRect) + self.textInsets.top + self.textInsets.bottom));
  102. }
  103. #pragma mark - Accessors and Mutators
  104. - (void)setShadowBlur:(CGFloat)shadowBlur {
  105. _shadowBlur = fmaxf(shadowBlur, 0.0);
  106. }
  107. - (UIColor *)gradientStartColor {
  108. return self.gradientColors.count ? self.gradientColors.firstObject : nil;
  109. }
  110. - (void)setGradientStartColor:(UIColor *)gradientStartColor {
  111. if (gradientStartColor == nil) {
  112. self.gradientColors = nil;
  113. } else if (self.gradientColors.count < 2) {
  114. self.gradientColors = @[gradientStartColor, gradientStartColor];
  115. } else if (![self.gradientColors.firstObject isEqual:gradientStartColor]) {
  116. NSMutableArray *colors = [self.gradientColors mutableCopy];
  117. colors[0] = gradientStartColor;
  118. self.gradientColors = colors;
  119. }
  120. }
  121. - (UIColor *)gradientEndColor {
  122. return self.gradientColors.lastObject;
  123. }
  124. - (void)setGradientEndColor:(UIColor *)gradientEndColor {
  125. if (gradientEndColor == nil) {
  126. self.gradientColors = nil;
  127. } else if (self.gradientColors.count < 2) {
  128. self.gradientColors = @[gradientEndColor, gradientEndColor];
  129. } else if (![self.gradientColors.lastObject isEqual:gradientEndColor]) {
  130. NSMutableArray *colors = [self.gradientColors mutableCopy];
  131. colors[colors.count - 1] = gradientEndColor;
  132. self.gradientColors = colors;
  133. }
  134. }
  135. - (void)setGradientColors:(NSArray *)gradientColors {
  136. if (self.gradientColors != gradientColors) {
  137. _gradientColors = [gradientColors copy];
  138. [self setNeedsDisplay];
  139. }
  140. }
  141. - (void)setTextInsets:(UIEdgeInsets)textInsets {
  142. if (!UIEdgeInsetsEqualToEdgeInsets(self.textInsets, textInsets)) {
  143. _textInsets = textInsets;
  144. [self setNeedsDisplay];
  145. }
  146. }
  147. - (CGFloat)strokeSizeDependentOnStrokePosition {
  148. switch (self.strokePosition) {
  149. case THLabelStrokePositionCenter:
  150. return self.strokeSize;
  151. default:
  152. // Stroke width times 2, because CG draws a centered stroke. We cut the rest into halves.
  153. return self.strokeSize * 2.0;
  154. }
  155. }
  156. #pragma mark - Drawing
  157. - (void)drawRect:(CGRect)rect {
  158. // Don't draw anything, if there is no text.
  159. if (!self.text || [self.text isEqualToString:@""]) {
  160. return;
  161. }
  162. // -------
  163. // Determine what has to be drawn.
  164. // -------
  165. BOOL hasShadow = [self hasShadow];
  166. BOOL hasInnerShadow = [self hasInnerShadow];
  167. BOOL hasStroke = [self hasStroke];
  168. BOOL hasGradient = [self hasGradient];
  169. BOOL hasFadeTruncating = [self hasFadeTruncating];
  170. BOOL needsMask = hasGradient || (hasStroke && self.strokePosition == THLabelStrokePositionInside) || hasInnerShadow;
  171. // -------
  172. // Step 1: Begin new drawing context, where we will apply all our styles.
  173. // -------
  174. UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0);
  175. CGContextRef context = UIGraphicsGetCurrentContext();
  176. CGImageRef alphaMask = NULL;
  177. CGRect textRect;
  178. CTFrameRef frameRef = [self frameRefFromSize:self.bounds.size textRectOutput:&textRect];
  179. // Invert everything, because CG works with an inverted coordinate system.
  180. CGContextTranslateCTM(context, 0.0, CGRectGetHeight(rect));
  181. CGContextScaleCTM(context, 1.0, -1.0);
  182. // -------
  183. // Step 2: Prepare mask.
  184. // -------
  185. if (needsMask) {
  186. CGContextSaveGState(context);
  187. // Draw alpha mask.
  188. if (hasStroke) {
  189. // Text needs invisible stroke for consistent character glyph widths.
  190. CGContextSetTextDrawingMode(context, kCGTextFillStroke);
  191. CGContextSetLineWidth(context, [self strokeSizeDependentOnStrokePosition]);
  192. CGContextSetLineJoin(context, kCGLineJoinRound);
  193. [[UIColor clearColor] setStroke];
  194. } else {
  195. CGContextSetTextDrawingMode(context, kCGTextFill);
  196. }
  197. [[UIColor whiteColor] setFill];
  198. CTFrameDraw(frameRef, context);
  199. // Save alpha mask.
  200. alphaMask = CGBitmapContextCreateImage(context);
  201. // Clear the content.
  202. CGContextClearRect(context, rect);
  203. CGContextRestoreGState(context);
  204. }
  205. // -------
  206. // Step 3: Draw text normally, or with gradient.
  207. // -------
  208. CGContextSaveGState(context);
  209. if (!hasGradient) {
  210. // Draw text.
  211. if (hasStroke) {
  212. // Text needs invisible stroke for consistent character glyph widths.
  213. CGContextSetTextDrawingMode(context, kCGTextFillStroke);
  214. CGContextSetLineWidth(context, [self strokeSizeDependentOnStrokePosition]);
  215. CGContextSetLineJoin(context, kCGLineJoinRound);
  216. [[UIColor clearColor] setStroke];
  217. } else {
  218. CGContextSetTextDrawingMode(context, kCGTextFill);
  219. }
  220. CTFrameDraw(frameRef, context);
  221. } else {
  222. // Clip the current context to alpha mask.
  223. CGContextClipToMask(context, rect, alphaMask);
  224. // Invert back to draw the gradient correctly.
  225. CGContextTranslateCTM(context, 0.0, CGRectGetHeight(rect));
  226. CGContextScaleCTM(context, 1.0, -1.0);
  227. // Get gradient colors as CGColor.
  228. NSMutableArray *gradientColors = [NSMutableArray arrayWithCapacity:self.gradientColors.count];
  229. for (UIColor *color in self.gradientColors) {
  230. [gradientColors addObject:(__bridge id)color.CGColor];
  231. }
  232. // Create gradient.
  233. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  234. CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)gradientColors, NULL);
  235. CGPoint startPoint = CGPointMake(textRect.origin.x + self.gradientStartPoint.x * CGRectGetWidth(textRect),
  236. textRect.origin.y + self.gradientStartPoint.y * CGRectGetHeight(textRect));
  237. CGPoint endPoint = CGPointMake(textRect.origin.x + self.gradientEndPoint.x * CGRectGetWidth(textRect),
  238. textRect.origin.y + self.gradientEndPoint.y * CGRectGetHeight(textRect));
  239. // Draw gradient.
  240. CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
  241. // Clean up.
  242. CGColorSpaceRelease(colorSpace);
  243. CGGradientRelease(gradient);
  244. }
  245. CGContextRestoreGState(context);
  246. // -------
  247. // Step 4: Draw inner shadow.
  248. // -------
  249. if (hasInnerShadow) {
  250. CGContextSaveGState(context);
  251. // Clip the current context to alpha mask.
  252. CGContextClipToMask(context, rect, alphaMask);
  253. // Invert to draw the inner shadow correctly.
  254. CGContextTranslateCTM(context, 0.0, CGRectGetHeight(rect));
  255. CGContextScaleCTM(context, 1.0, -1.0);
  256. // Draw inner shadow.
  257. CGImageRef shadowImage = [self inverseMaskFromAlphaMask:alphaMask withRect:rect];
  258. CGContextSetShadowWithColor(context, self.innerShadowOffset, self.innerShadowBlur, self.innerShadowColor.CGColor);
  259. CGContextSetBlendMode(context, kCGBlendModeDarken);
  260. CGContextDrawImage(context, rect, shadowImage);
  261. // Clean up.
  262. CGImageRelease(shadowImage);
  263. CGContextRestoreGState(context);
  264. }
  265. // -------
  266. // Step 5: Draw stroke.
  267. // -------
  268. if (hasStroke) {
  269. CGContextSaveGState(context);
  270. CGContextSetTextDrawingMode(context, kCGTextStroke);
  271. CGImageRef image = NULL;
  272. if (self.strokePosition == THLabelStrokePositionOutside) {
  273. // Create an image from the text.
  274. image = CGBitmapContextCreateImage(context);
  275. } else if (self.strokePosition == THLabelStrokePositionInside) {
  276. // Clip the current context to alpha mask.
  277. CGContextClipToMask(context, rect, alphaMask);
  278. }
  279. // Draw stroke.
  280. CGImageRef strokeImage = [self strokeImageWithRect:rect frameRef:frameRef strokeSize:[self strokeSizeDependentOnStrokePosition] strokeColor:self.strokeColor];
  281. CGContextDrawImage(context, rect, strokeImage);
  282. if (self.strokePosition == THLabelStrokePositionOutside) {
  283. // Draw the saved image over half of the stroke.
  284. CGContextDrawImage(context, rect, image);
  285. }
  286. // Clean up.
  287. CGImageRelease(strokeImage);
  288. CGImageRelease(image);
  289. CGContextRestoreGState(context);
  290. }
  291. // -------
  292. // Step 6: Draw shadow.
  293. // -------
  294. if (hasShadow) {
  295. CGContextSaveGState(context);
  296. // Create an image from the text.
  297. CGImageRef image = CGBitmapContextCreateImage(context);
  298. // Clear the content.
  299. CGContextClearRect(context, rect);
  300. // Set shadow attributes.
  301. CGContextSetShadowWithColor(context, self.shadowOffset, self.shadowBlur, self.shadowColor.CGColor);
  302. // Draw the saved image, which throws off a shadow.
  303. CGContextDrawImage(context, rect, image);
  304. // Clean up.
  305. CGImageRelease(image);
  306. CGContextRestoreGState(context);
  307. }
  308. // -------
  309. // Step 7: Draw fade truncating.
  310. // -------
  311. if (hasFadeTruncating) {
  312. CGContextSaveGState(context);
  313. // Create an image from the text.
  314. CGImageRef image = CGBitmapContextCreateImage(context);
  315. // Clear the content.
  316. CGContextClearRect(context, rect);
  317. // Clip the current context to linear gradient mask.
  318. CGImageRef linearGradientImage = [self linearGradientImageWithRect:rect fadeHead:self.fadeTruncatingMode & THLabelFadeTruncatingModeHead fadeTail:self.fadeTruncatingMode & THLabelFadeTruncatingModeTail];
  319. CGContextClipToMask(context, self.bounds, linearGradientImage);
  320. // Draw the saved image, which is clipped by the linear gradient mask.
  321. CGContextDrawImage(context, rect, image);
  322. // Clean up.
  323. CGImageRelease(linearGradientImage);
  324. CGImageRelease(image);
  325. CGContextRestoreGState(context);
  326. }
  327. // -------
  328. // Step 8: End drawing context and finally draw the text with all styles.
  329. // -------
  330. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  331. UIGraphicsEndImageContext();
  332. [image drawInRect:rect];
  333. // -------
  334. // Clean up.
  335. // -------
  336. if (needsMask) {
  337. CGImageRelease(alphaMask);
  338. }
  339. CFRelease(frameRef);
  340. }
  341. - (CTFrameRef)frameRefFromSize:(CGSize)size textRectOutput:(CGRect *)textRectOutput CF_RETURNS_RETAINED {
  342. // Set up font.
  343. CTFontRef fontRef = CTFontCreateWithName((__bridge CFStringRef)self.font.fontName, self.font.pointSize, NULL);
  344. #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_6_0
  345. CTTextAlignment alignment = NSTextAlignmentToCTTextAlignment(self.textAlignment);
  346. #else
  347. CTTextAlignment alignment = NSTextAlignmentToCTTextAlignment ? NSTextAlignmentToCTTextAlignment(self.textAlignment) : [self CTTextAlignmentFromNSTextAlignment:self.textAlignment];
  348. #endif
  349. CTLineBreakMode lineBreakMode = (CTLineBreakMode)self.lineBreakMode;
  350. CGFloat lineSpacing = self.lineSpacing;
  351. CTParagraphStyleSetting paragraphStyleSettings[] = {
  352. {kCTParagraphStyleSpecifierAlignment, sizeof(CTTextAlignment), &alignment},
  353. {kCTParagraphStyleSpecifierLineBreakMode, sizeof(CTLineBreakMode), &lineBreakMode},
  354. {kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof(CGFloat), &lineSpacing}
  355. };
  356. CTParagraphStyleRef paragraphStyleRef = CTParagraphStyleCreate(paragraphStyleSettings, 3);
  357. CFNumberRef kernRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &_letterSpacing);
  358. // Set up attributed string.
  359. CFStringRef keys[] = {kCTFontAttributeName, kCTParagraphStyleAttributeName, kCTForegroundColorAttributeName, kCTKernAttributeName};
  360. CFTypeRef values[] = {fontRef, paragraphStyleRef, self.textColor.CGColor, kernRef};
  361. CFDictionaryRef attributes = CFDictionaryCreate(kCFAllocatorDefault, (const void **)&keys, (const void **)&values, sizeof(keys) / sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
  362. CFRelease(fontRef);
  363. CFRelease(paragraphStyleRef);
  364. CFRelease(kernRef);
  365. CFStringRef stringRef = (CFStringRef)CFBridgingRetain(self.text);
  366. CFAttributedStringRef attributedStringRef = CFAttributedStringCreate(kCFAllocatorDefault, stringRef, attributes);
  367. CFRelease(stringRef);
  368. CFRelease(attributes);
  369. // Set up frame.
  370. CTFramesetterRef framesetterRef = CTFramesetterCreateWithAttributedString(attributedStringRef);
  371. CFRelease(attributedStringRef);
  372. if (self.automaticallyAdjustTextInsets) {
  373. self.textInsets = [self fittingTextInsets];
  374. }
  375. CGRect contentRect = [self contentRectFromSize:size withInsets:self.textInsets];
  376. CGRect textRect = [self textRectFromContentRect:contentRect framesetterRef:framesetterRef];
  377. if (textRectOutput) {
  378. *textRectOutput = textRect;
  379. }
  380. CGMutablePathRef pathRef = CGPathCreateMutable();
  381. CGPathAddRect(pathRef, NULL, textRect);
  382. CTFrameRef frameRef = CTFramesetterCreateFrame(framesetterRef, CFRangeMake(0, self.text.length), pathRef, NULL);
  383. CFRelease(framesetterRef);
  384. CGPathRelease(pathRef);
  385. return frameRef;
  386. }
  387. // Workaround for < iOS 6.
  388. - (CTTextAlignment)CTTextAlignmentFromNSTextAlignment:(NSTextAlignment)nsTextAlignment {
  389. switch (nsTextAlignment) {
  390. case NSTextAlignmentLeft:
  391. return kCTTextAlignmentLeft;
  392. case NSTextAlignmentCenter:
  393. return kCTTextAlignmentCenter;
  394. case NSTextAlignmentRight:
  395. return kCTTextAlignmentRight;
  396. case NSTextAlignmentJustified:
  397. return kCTTextAlignmentJustified;
  398. case NSTextAlignmentNatural:
  399. return kCTTextAlignmentNatural;
  400. default:
  401. return 0;
  402. }
  403. }
  404. - (CGRect)contentRectFromSize:(CGSize)size withInsets:(UIEdgeInsets)insets {
  405. CGRect contentRect = CGRectMake(0.0, 0.0, size.width, size.height);
  406. // Apply insets.
  407. contentRect.origin.x += insets.left;
  408. contentRect.origin.y += insets.top;
  409. contentRect.size.width -= insets.left + insets.right;
  410. contentRect.size.height -= insets.top + insets.bottom;
  411. return contentRect;
  412. }
  413. - (CGRect)textRectFromContentRect:(CGRect)contentRect framesetterRef:(CTFramesetterRef)framesetterRef {
  414. CGSize suggestedTextRectSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetterRef, CFRangeMake(0, self.text.length), NULL, contentRect.size, NULL);
  415. if (CGSizeEqualToSize(suggestedTextRectSize, CGSizeZero)) {
  416. suggestedTextRectSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetterRef, CFRangeMake(0, self.text.length), NULL, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX), NULL);
  417. }
  418. CGRect textRect = CGRectMake(0.0, 0.0, ceilf(suggestedTextRectSize.width), ceilf(suggestedTextRectSize.height));
  419. // Horizontal alignment.
  420. switch (self.textAlignment) {
  421. case NSTextAlignmentCenter:
  422. textRect.origin.x = floorf(CGRectGetMinX(contentRect) + (CGRectGetWidth(contentRect) - CGRectGetWidth(textRect)) / 2.0);
  423. break;
  424. case NSTextAlignmentRight:
  425. textRect.origin.x = floorf(CGRectGetMinX(contentRect) + CGRectGetWidth(contentRect) - CGRectGetWidth(textRect));
  426. break;
  427. default:
  428. textRect.origin.x = floorf(CGRectGetMinX(contentRect));
  429. break;
  430. }
  431. // Vertical alignment. Top and bottom are upside down, because of inverted drawing.
  432. switch (self.contentMode) {
  433. case UIViewContentModeTop:
  434. case UIViewContentModeTopLeft:
  435. case UIViewContentModeTopRight:
  436. textRect.origin.y = floorf(CGRectGetMinY(contentRect) + CGRectGetHeight(contentRect) - CGRectGetHeight(textRect));
  437. break;
  438. case UIViewContentModeBottom:
  439. case UIViewContentModeBottomLeft:
  440. case UIViewContentModeBottomRight:
  441. textRect.origin.y = floorf(CGRectGetMinY(contentRect));
  442. break;
  443. default:
  444. textRect.origin.y = floorf(CGRectGetMinY(contentRect) + floorf((CGRectGetHeight(contentRect) - CGRectGetHeight(textRect)) / 2.0));
  445. break;
  446. }
  447. return textRect;
  448. }
  449. - (UIEdgeInsets)fittingTextInsets {
  450. BOOL hasShadow = [self hasShadow];
  451. BOOL hasStroke = [self hasStroke];
  452. UIEdgeInsets edgeInsets = UIEdgeInsetsZero;
  453. if (hasStroke) {
  454. switch (self.strokePosition) {
  455. case THLabelStrokePositionOutside:
  456. edgeInsets = UIEdgeInsetsMake(self.strokeSize, self.strokeSize, self.strokeSize, self.strokeSize);
  457. break;
  458. case THLabelStrokePositionCenter:
  459. edgeInsets = UIEdgeInsetsMake(self.strokeSize / 2.0, self.strokeSize / 2.0, self.strokeSize / 2.0, self.strokeSize / 2.0);
  460. break;
  461. default:
  462. break;
  463. }
  464. }
  465. if (hasShadow) {
  466. edgeInsets.top = fmaxf(edgeInsets.top + self.shadowBlur + self.shadowOffset.height, edgeInsets.top);
  467. edgeInsets.left = fmaxf(edgeInsets.left + self.shadowBlur + self.shadowOffset.width, edgeInsets.left);
  468. edgeInsets.bottom = fmaxf(edgeInsets.bottom + self.shadowBlur - self.shadowOffset.height, edgeInsets.bottom);
  469. edgeInsets.right = fmaxf(edgeInsets.right + self.shadowBlur - self.shadowOffset.width, edgeInsets.right);
  470. }
  471. return edgeInsets;
  472. }
  473. #pragma mark - Image Functions
  474. - (CGImageRef)inverseMaskFromAlphaMask:(CGImageRef)alphaMask withRect:(CGRect)rect CF_RETURNS_RETAINED {
  475. // Create context.
  476. UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0);
  477. CGContextRef context = UIGraphicsGetCurrentContext();
  478. // Fill rect, clip to alpha mask and clear.
  479. [[UIColor whiteColor] setFill];
  480. UIRectFill(rect);
  481. CGContextClipToMask(context, rect, alphaMask);
  482. CGContextClearRect(context, rect);
  483. // Return image.
  484. CGImageRef image = CGBitmapContextCreateImage(context);
  485. UIGraphicsEndImageContext();
  486. return image;
  487. }
  488. - (CGImageRef)strokeImageWithRect:(CGRect)rect frameRef:(CTFrameRef)frameRef strokeSize:(CGFloat)strokeSize strokeColor:(UIColor *)strokeColor CF_RETURNS_RETAINED {
  489. // Create context.
  490. UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0);
  491. CGContextRef context = UIGraphicsGetCurrentContext();
  492. CGContextSetTextDrawingMode(context, kCGTextStroke);
  493. // Draw clipping mask.
  494. CGContextSetLineWidth(context, strokeSize);
  495. CGContextSetLineJoin(context, kCGLineJoinRound);
  496. [[UIColor whiteColor] setStroke];
  497. CTFrameDraw(frameRef, context);
  498. // Save clipping mask.
  499. CGImageRef clippingMask = CGBitmapContextCreateImage(context);
  500. // Clear the content.
  501. CGContextClearRect(context, rect);
  502. // Draw stroke.
  503. CGContextClipToMask(context, rect, clippingMask);
  504. CGContextTranslateCTM(context, 0.0, CGRectGetHeight(rect));
  505. CGContextScaleCTM(context, 1.0, -1.0);
  506. [strokeColor setFill];
  507. UIRectFill(rect);
  508. // Clean up and return image.
  509. CGImageRelease(clippingMask);
  510. CGImageRef image = CGBitmapContextCreateImage(context);
  511. UIGraphicsEndImageContext();
  512. return image;
  513. }
  514. - (CGImageRef)linearGradientImageWithRect:(CGRect)rect fadeHead:(BOOL)fadeHead fadeTail:(BOOL)fadeTail CF_RETURNS_RETAINED {
  515. // Create an opaque context.
  516. UIGraphicsBeginImageContextWithOptions(rect.size, YES, 0.0);
  517. CGContextRef context = UIGraphicsGetCurrentContext();
  518. // White background will mask opaque, black gradient will mask transparent.
  519. [[UIColor whiteColor] setFill];
  520. UIRectFill(rect);
  521. // Create gradient from white to black.
  522. CGFloat locs[2] = {0.0, 1.0};
  523. CGFloat components[4] = {1.0, 1.0, 0.0, 1.0};
  524. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
  525. CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, locs, 2);
  526. // Draw head and/or tail gradient.
  527. CGFloat fadeWidth = fminf(CGRectGetHeight(rect) * 2.0, floorf(CGRectGetWidth(rect) / 4.0));
  528. CGFloat minX = CGRectGetMinX(rect);
  529. CGFloat maxX = CGRectGetMaxX(rect);
  530. if (fadeTail) {
  531. CGFloat startX = maxX - fadeWidth;
  532. CGPoint startPoint = CGPointMake(startX, CGRectGetMidY(rect));
  533. CGPoint endPoint = CGPointMake(maxX, CGRectGetMidY(rect));
  534. CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
  535. }
  536. if (fadeHead) {
  537. CGFloat startX = minX + fadeWidth;
  538. CGPoint startPoint = CGPointMake(startX, CGRectGetMidY(rect));
  539. CGPoint endPoint = CGPointMake(minX, CGRectGetMidY(rect));
  540. CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
  541. }
  542. // Clean up and return image.
  543. CGColorSpaceRelease(colorSpace);
  544. CGGradientRelease(gradient);
  545. CGImageRef image = CGBitmapContextCreateImage(context);
  546. UIGraphicsEndImageContext();
  547. return image;
  548. }
  549. @end