ASTextKitShadower.mm 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. //
  2. // ASTextKitShadower.mm
  3. // AsyncDisplayKit
  4. //
  5. // Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
  6. // This source code is licensed under the BSD-style license found in the
  7. // LICENSE file in the root directory of this source tree. An additional grant
  8. // of patent rights can be found in the PATENTS file in the same directory.
  9. //
  10. #import <AsyncDisplayKit/ASTextKitShadower.h>
  11. #import <tgmath.h>
  12. static inline CGSize _insetSize(CGSize size, UIEdgeInsets insets)
  13. {
  14. size.width -= (insets.left + insets.right);
  15. size.height -= (insets.top + insets.bottom);
  16. return size;
  17. }
  18. static inline UIEdgeInsets _invertInsets(UIEdgeInsets insets)
  19. {
  20. return {
  21. .top = -insets.top,
  22. .left = -insets.left,
  23. .bottom = -insets.bottom,
  24. .right = -insets.right
  25. };
  26. }
  27. @implementation ASTextKitShadower {
  28. UIEdgeInsets _calculatedShadowPadding;
  29. }
  30. + (ASTextKitShadower *)shadowerWithShadowOffset:(CGSize)shadowOffset
  31. shadowColor:(UIColor *)shadowColor
  32. shadowOpacity:(CGFloat)shadowOpacity
  33. shadowRadius:(CGFloat)shadowRadius
  34. {
  35. /**
  36. * For all cases where no shadow is drawn, we share this singleton shadower to save resources.
  37. */
  38. static dispatch_once_t onceToken;
  39. static ASTextKitShadower *sharedNonShadower;
  40. dispatch_once(&onceToken, ^{
  41. sharedNonShadower = [[ASTextKitShadower alloc] initWithShadowOffset:CGSizeZero shadowColor:nil shadowOpacity:0 shadowRadius:0];
  42. });
  43. BOOL hasShadow = shadowOpacity > 0 && (shadowRadius > 0 || CGSizeEqualToSize(shadowOffset, CGSizeZero) == NO) && CGColorGetAlpha(shadowColor.CGColor) > 0;
  44. if (hasShadow == NO) {
  45. return sharedNonShadower;
  46. } else {
  47. return [[ASTextKitShadower alloc] initWithShadowOffset:shadowOffset shadowColor:shadowColor shadowOpacity:shadowOpacity shadowRadius:shadowRadius];
  48. }
  49. }
  50. - (instancetype)initWithShadowOffset:(CGSize)shadowOffset
  51. shadowColor:(UIColor *)shadowColor
  52. shadowOpacity:(CGFloat)shadowOpacity
  53. shadowRadius:(CGFloat)shadowRadius
  54. {
  55. if (self = [super init]) {
  56. _shadowOffset = shadowOffset;
  57. _shadowColor = shadowColor;
  58. _shadowOpacity = shadowOpacity;
  59. _shadowRadius = shadowRadius;
  60. _calculatedShadowPadding = UIEdgeInsetsMake(-INFINITY, -INFINITY, INFINITY, INFINITY);
  61. }
  62. return self;
  63. }
  64. /*
  65. * This method is duplicated here because it gets called frequently, and we were
  66. * wasting valuable time constructing a state object to ask it.
  67. */
  68. - (BOOL)_shouldDrawShadow
  69. {
  70. return _shadowOpacity != 0.0 && (_shadowRadius != 0 || !CGSizeEqualToSize(_shadowOffset, CGSizeZero)) && CGColorGetAlpha(_shadowColor.CGColor) > 0;
  71. }
  72. - (void)setShadowInContext:(CGContextRef)context
  73. {
  74. if ([self _shouldDrawShadow]) {
  75. CGColorRef textShadowColor = CGColorRetain(_shadowColor.CGColor);
  76. CGSize textShadowOffset = _shadowOffset;
  77. CGFloat textShadowOpacity = _shadowOpacity;
  78. CGFloat textShadowRadius = _shadowRadius;
  79. if (textShadowOpacity != 1.0) {
  80. CGFloat inherentAlpha = CGColorGetAlpha(textShadowColor);
  81. CGColorRef oldTextShadowColor = textShadowColor;
  82. textShadowColor = CGColorCreateCopyWithAlpha(textShadowColor, inherentAlpha * textShadowOpacity);
  83. CGColorRelease(oldTextShadowColor);
  84. }
  85. CGContextSetShadowWithColor(context, textShadowOffset, textShadowRadius, textShadowColor);
  86. CGColorRelease(textShadowColor);
  87. }
  88. }
  89. - (UIEdgeInsets)shadowPadding
  90. {
  91. if (_calculatedShadowPadding.top == -INFINITY) {
  92. if (![self _shouldDrawShadow]) {
  93. return UIEdgeInsetsZero;
  94. }
  95. UIEdgeInsets shadowPadding = UIEdgeInsetsZero;
  96. // min values are expected to be negative for most typical shadowOffset and
  97. // blurRadius settings:
  98. shadowPadding.top = std::fmin(0.0f, _shadowOffset.height - _shadowRadius);
  99. shadowPadding.left = std::fmin(0.0f, _shadowOffset.width - _shadowRadius);
  100. shadowPadding.bottom = std::fmin(0.0f, -_shadowOffset.height - _shadowRadius);
  101. shadowPadding.right = std::fmin(0.0f, -_shadowOffset.width - _shadowRadius);
  102. _calculatedShadowPadding = shadowPadding;
  103. }
  104. return _calculatedShadowPadding;
  105. }
  106. - (CGSize)insetSizeWithConstrainedSize:(CGSize)constrainedSize
  107. {
  108. return _insetSize(constrainedSize, _invertInsets([self shadowPadding]));
  109. }
  110. - (CGRect)insetRectWithConstrainedRect:(CGRect)constrainedRect
  111. {
  112. return UIEdgeInsetsInsetRect(constrainedRect, _invertInsets([self shadowPadding]));
  113. }
  114. - (CGSize)outsetSizeWithInsetSize:(CGSize)insetSize
  115. {
  116. return _insetSize(insetSize, [self shadowPadding]);
  117. }
  118. - (CGRect)outsetRectWithInsetRect:(CGRect)insetRect
  119. {
  120. return UIEdgeInsetsInsetRect(insetRect, [self shadowPadding]);
  121. }
  122. - (CGRect)offsetRectWithInternalRect:(CGRect)internalRect
  123. {
  124. return (CGRect){
  125. .origin = [self offsetPointWithInternalPoint:internalRect.origin],
  126. .size = internalRect.size
  127. };
  128. }
  129. - (CGPoint)offsetPointWithInternalPoint:(CGPoint)internalPoint
  130. {
  131. UIEdgeInsets shadowPadding = [self shadowPadding];
  132. return (CGPoint){
  133. internalPoint.x + shadowPadding.left,
  134. internalPoint.y + shadowPadding.top
  135. };
  136. }
  137. - (CGPoint)offsetPointWithExternalPoint:(CGPoint)externalPoint
  138. {
  139. UIEdgeInsets shadowPadding = [self shadowPadding];
  140. return (CGPoint){
  141. externalPoint.x - shadowPadding.left,
  142. externalPoint.y - shadowPadding.top
  143. };
  144. }
  145. @end