RCTTextAttributes.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. /*
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. #import <React/RCTTextAttributes.h>
  8. #import <React/RCTAssert.h>
  9. #import <React/RCTFont.h>
  10. #import <React/RCTLog.h>
  11. NSString *const RCTTextAttributesIsHighlightedAttributeName = @"RCTTextAttributesIsHighlightedAttributeName";
  12. NSString *const RCTTextAttributesTagAttributeName = @"RCTTextAttributesTagAttributeName";
  13. @implementation RCTTextAttributes
  14. - (instancetype)init
  15. {
  16. if (self = [super init]) {
  17. _fontSize = NAN;
  18. _letterSpacing = NAN;
  19. _lineHeight = NAN;
  20. _textDecorationStyle = NSUnderlineStyleSingle;
  21. _fontSizeMultiplier = NAN;
  22. _maxFontSizeMultiplier = NAN;
  23. _alignment = NSTextAlignmentNatural;
  24. _baseWritingDirection = NSWritingDirectionNatural;
  25. _textShadowRadius = NAN;
  26. _opacity = NAN;
  27. _textTransform = RCTTextTransformUndefined;
  28. }
  29. return self;
  30. }
  31. - (void)applyTextAttributes:(RCTTextAttributes *)textAttributes
  32. {
  33. // Note: All lines marked with `*` does not use explicit/correct rules to compare old and new values because
  34. // their types do not have special designated value representing undefined/unspecified/inherit meaning.
  35. // We will address this in the future.
  36. // Color
  37. _foregroundColor = textAttributes->_foregroundColor ?: _foregroundColor;
  38. _backgroundColor = textAttributes->_backgroundColor ?: _backgroundColor;
  39. _opacity = !isnan(textAttributes->_opacity) ? (isnan(_opacity) ? 1.0 : _opacity) * textAttributes->_opacity : _opacity;
  40. // Font
  41. _fontFamily = textAttributes->_fontFamily ?: _fontFamily;
  42. _fontSize = !isnan(textAttributes->_fontSize) ? textAttributes->_fontSize : _fontSize;
  43. _fontSizeMultiplier = !isnan(textAttributes->_fontSizeMultiplier) ? textAttributes->_fontSizeMultiplier : _fontSizeMultiplier;
  44. _maxFontSizeMultiplier = !isnan(textAttributes->_maxFontSizeMultiplier) ? textAttributes->_maxFontSizeMultiplier : _maxFontSizeMultiplier;
  45. _fontWeight = textAttributes->_fontWeight ?: _fontWeight;
  46. _fontStyle = textAttributes->_fontStyle ?: _fontStyle;
  47. _fontVariant = textAttributes->_fontVariant ?: _fontVariant;
  48. _allowFontScaling = textAttributes->_allowFontScaling || _allowFontScaling; // *
  49. _letterSpacing = !isnan(textAttributes->_letterSpacing) ? textAttributes->_letterSpacing : _letterSpacing;
  50. // Paragraph Styles
  51. _lineHeight = !isnan(textAttributes->_lineHeight) ? textAttributes->_lineHeight : _lineHeight;
  52. _alignment = textAttributes->_alignment != NSTextAlignmentNatural ? textAttributes->_alignment : _alignment; // *
  53. _baseWritingDirection = textAttributes->_baseWritingDirection != NSWritingDirectionNatural ? textAttributes->_baseWritingDirection : _baseWritingDirection; // *
  54. // Decoration
  55. _textDecorationColor = textAttributes->_textDecorationColor ?: _textDecorationColor;
  56. _textDecorationStyle = textAttributes->_textDecorationStyle != NSUnderlineStyleSingle ? textAttributes->_textDecorationStyle : _textDecorationStyle; // *
  57. _textDecorationLine = textAttributes->_textDecorationLine != RCTTextDecorationLineTypeNone ? textAttributes->_textDecorationLine : _textDecorationLine; // *
  58. // Shadow
  59. _textShadowOffset = !CGSizeEqualToSize(textAttributes->_textShadowOffset, CGSizeZero) ? textAttributes->_textShadowOffset : _textShadowOffset; // *
  60. _textShadowRadius = !isnan(textAttributes->_textShadowRadius) ? textAttributes->_textShadowRadius : _textShadowRadius;
  61. _textShadowColor = textAttributes->_textShadowColor ?: _textShadowColor;
  62. // Special
  63. _isHighlighted = textAttributes->_isHighlighted || _isHighlighted; // *
  64. _tag = textAttributes->_tag ?: _tag;
  65. _layoutDirection = textAttributes->_layoutDirection != UIUserInterfaceLayoutDirectionLeftToRight ? textAttributes->_layoutDirection : _layoutDirection;
  66. _textTransform = textAttributes->_textTransform != RCTTextTransformUndefined ? textAttributes->_textTransform : _textTransform;
  67. }
  68. - (NSParagraphStyle *)effectiveParagraphStyle
  69. {
  70. // Paragraph Style
  71. NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
  72. BOOL isParagraphStyleUsed = NO;
  73. if (_alignment != NSTextAlignmentNatural) {
  74. NSTextAlignment alignment = _alignment;
  75. if (_layoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) {
  76. if (alignment == NSTextAlignmentRight) {
  77. alignment = NSTextAlignmentLeft;
  78. } else if (alignment == NSTextAlignmentLeft) {
  79. alignment = NSTextAlignmentRight;
  80. }
  81. }
  82. paragraphStyle.alignment = alignment;
  83. isParagraphStyleUsed = YES;
  84. }
  85. if (_baseWritingDirection != NSWritingDirectionNatural) {
  86. paragraphStyle.baseWritingDirection = _baseWritingDirection;
  87. isParagraphStyleUsed = YES;
  88. }
  89. if (!isnan(_lineHeight)) {
  90. CGFloat lineHeight = _lineHeight * self.effectiveFontSizeMultiplier;
  91. paragraphStyle.minimumLineHeight = lineHeight;
  92. paragraphStyle.maximumLineHeight = lineHeight;
  93. isParagraphStyleUsed = YES;
  94. }
  95. if (isParagraphStyleUsed) {
  96. return [paragraphStyle copy];
  97. }
  98. return nil;
  99. }
  100. - (NSDictionary<NSAttributedStringKey, id> *)effectiveTextAttributes
  101. {
  102. NSMutableDictionary<NSAttributedStringKey, id> *attributes =
  103. [NSMutableDictionary dictionaryWithCapacity:10];
  104. // Font
  105. UIFont *font = self.effectiveFont;
  106. if (font) {
  107. attributes[NSFontAttributeName] = font;
  108. }
  109. // Colors
  110. UIColor *effectiveForegroundColor = self.effectiveForegroundColor;
  111. if (_foregroundColor || !isnan(_opacity)) {
  112. attributes[NSForegroundColorAttributeName] = effectiveForegroundColor;
  113. }
  114. if (_backgroundColor || !isnan(_opacity)) {
  115. attributes[NSBackgroundColorAttributeName] = self.effectiveBackgroundColor;
  116. }
  117. // Kerning
  118. if (!isnan(_letterSpacing)) {
  119. attributes[NSKernAttributeName] = @(_letterSpacing);
  120. }
  121. // Paragraph Style
  122. NSParagraphStyle *paragraphStyle = [self effectiveParagraphStyle];
  123. if (paragraphStyle) {
  124. attributes[NSParagraphStyleAttributeName] = paragraphStyle;
  125. }
  126. // Decoration
  127. BOOL isTextDecorationEnabled = NO;
  128. if (_textDecorationLine == RCTTextDecorationLineTypeUnderline ||
  129. _textDecorationLine == RCTTextDecorationLineTypeUnderlineStrikethrough) {
  130. isTextDecorationEnabled = YES;
  131. attributes[NSUnderlineStyleAttributeName] = @(_textDecorationStyle);
  132. }
  133. if (_textDecorationLine == RCTTextDecorationLineTypeStrikethrough ||
  134. _textDecorationLine == RCTTextDecorationLineTypeUnderlineStrikethrough){
  135. isTextDecorationEnabled = YES;
  136. attributes[NSStrikethroughStyleAttributeName] = @(_textDecorationStyle);
  137. }
  138. if (_textDecorationColor || isTextDecorationEnabled) {
  139. attributes[NSStrikethroughColorAttributeName] = _textDecorationColor ?: effectiveForegroundColor;
  140. attributes[NSUnderlineColorAttributeName] = _textDecorationColor ?: effectiveForegroundColor;
  141. }
  142. // Shadow
  143. if (!isnan(_textShadowRadius)) {
  144. NSShadow *shadow = [NSShadow new];
  145. shadow.shadowOffset = _textShadowOffset;
  146. shadow.shadowBlurRadius = _textShadowRadius;
  147. shadow.shadowColor = _textShadowColor;
  148. attributes[NSShadowAttributeName] = shadow;
  149. }
  150. // Special
  151. if (_isHighlighted) {
  152. attributes[RCTTextAttributesIsHighlightedAttributeName] = @YES;
  153. }
  154. if (_tag) {
  155. attributes[RCTTextAttributesTagAttributeName] = _tag;
  156. }
  157. return [attributes copy];
  158. }
  159. - (UIFont *)effectiveFont
  160. {
  161. // FIXME: RCTFont has thread-safety issues and must be rewritten.
  162. return [RCTFont updateFont:nil
  163. withFamily:_fontFamily
  164. size:@(isnan(_fontSize) ? 0 : _fontSize)
  165. weight:_fontWeight
  166. style:_fontStyle
  167. variant:_fontVariant
  168. scaleMultiplier:self.effectiveFontSizeMultiplier];
  169. }
  170. - (CGFloat)effectiveFontSizeMultiplier
  171. {
  172. bool fontScalingEnabled = !RCTHasFontHandlerSet() && _allowFontScaling;
  173. if (fontScalingEnabled) {
  174. CGFloat fontSizeMultiplier = !isnan(_fontSizeMultiplier) ? _fontSizeMultiplier : 1.0;
  175. CGFloat maxFontSizeMultiplier = !isnan(_maxFontSizeMultiplier) ? _maxFontSizeMultiplier : 0.0;
  176. return maxFontSizeMultiplier >= 1.0 ? fminf(maxFontSizeMultiplier, fontSizeMultiplier) : fontSizeMultiplier;
  177. } else {
  178. return 1.0;
  179. }
  180. }
  181. - (UIColor *)effectiveForegroundColor
  182. {
  183. UIColor *effectiveForegroundColor = _foregroundColor ?: [UIColor blackColor];
  184. if (!isnan(_opacity)) {
  185. effectiveForegroundColor = [effectiveForegroundColor colorWithAlphaComponent:CGColorGetAlpha(effectiveForegroundColor.CGColor) * _opacity];
  186. }
  187. return effectiveForegroundColor;
  188. }
  189. - (UIColor *)effectiveBackgroundColor
  190. {
  191. UIColor *effectiveBackgroundColor = _backgroundColor;// ?: [[UIColor whiteColor] colorWithAlphaComponent:0];
  192. if (effectiveBackgroundColor && !isnan(_opacity)) {
  193. effectiveBackgroundColor = [effectiveBackgroundColor colorWithAlphaComponent:CGColorGetAlpha(effectiveBackgroundColor.CGColor) * _opacity];
  194. }
  195. return effectiveBackgroundColor ?: [UIColor clearColor];
  196. }
  197. - (NSString *)applyTextAttributesToText:(NSString *)text
  198. {
  199. switch (_textTransform) {
  200. case RCTTextTransformUndefined:
  201. case RCTTextTransformNone:
  202. return text;
  203. case RCTTextTransformLowercase:
  204. return [text lowercaseString];
  205. case RCTTextTransformUppercase:
  206. return [text uppercaseString];
  207. case RCTTextTransformCapitalize:
  208. return [text capitalizedString];
  209. }
  210. }
  211. - (RCTTextAttributes *)copyWithZone:(NSZone *)zone
  212. {
  213. RCTTextAttributes *textAttributes = [RCTTextAttributes new];
  214. [textAttributes applyTextAttributes:self];
  215. return textAttributes;
  216. }
  217. #pragma mark - NSObject
  218. - (BOOL)isEqual:(RCTTextAttributes *)textAttributes
  219. {
  220. if (!textAttributes) {
  221. return NO;
  222. }
  223. if (self == textAttributes) {
  224. return YES;
  225. }
  226. #define RCTTextAttributesCompareFloats(a) ((a == textAttributes->a) || (isnan(a) && isnan(textAttributes->a)))
  227. #define RCTTextAttributesCompareSize(a) CGSizeEqualToSize(a, textAttributes->a)
  228. #define RCTTextAttributesCompareObjects(a) ((a == textAttributes->a) || [a isEqual:textAttributes->a])
  229. #define RCTTextAttributesCompareStrings(a) ((a == textAttributes->a) || [a isEqualToString:textAttributes->a])
  230. #define RCTTextAttributesCompareOthers(a) (a == textAttributes->a)
  231. return
  232. RCTTextAttributesCompareObjects(_foregroundColor) &&
  233. RCTTextAttributesCompareObjects(_backgroundColor) &&
  234. RCTTextAttributesCompareFloats(_opacity) &&
  235. // Font
  236. RCTTextAttributesCompareObjects(_fontFamily) &&
  237. RCTTextAttributesCompareFloats(_fontSize) &&
  238. RCTTextAttributesCompareFloats(_fontSizeMultiplier) &&
  239. RCTTextAttributesCompareFloats(_maxFontSizeMultiplier) &&
  240. RCTTextAttributesCompareStrings(_fontWeight) &&
  241. RCTTextAttributesCompareObjects(_fontStyle) &&
  242. RCTTextAttributesCompareObjects(_fontVariant) &&
  243. RCTTextAttributesCompareOthers(_allowFontScaling) &&
  244. RCTTextAttributesCompareFloats(_letterSpacing) &&
  245. // Paragraph Styles
  246. RCTTextAttributesCompareFloats(_lineHeight) &&
  247. RCTTextAttributesCompareFloats(_alignment) &&
  248. RCTTextAttributesCompareOthers(_baseWritingDirection) &&
  249. // Decoration
  250. RCTTextAttributesCompareObjects(_textDecorationColor) &&
  251. RCTTextAttributesCompareOthers(_textDecorationStyle) &&
  252. RCTTextAttributesCompareOthers(_textDecorationLine) &&
  253. // Shadow
  254. RCTTextAttributesCompareSize(_textShadowOffset) &&
  255. RCTTextAttributesCompareFloats(_textShadowRadius) &&
  256. RCTTextAttributesCompareObjects(_textShadowColor) &&
  257. // Special
  258. RCTTextAttributesCompareOthers(_isHighlighted) &&
  259. RCTTextAttributesCompareObjects(_tag) &&
  260. RCTTextAttributesCompareOthers(_layoutDirection) &&
  261. RCTTextAttributesCompareOthers(_textTransform);
  262. }
  263. @end