react-native/Libraries/Text/RCTTextAttributes.m

288 lines
11 KiB
Objective-C

/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTTextAttributes.h"
#import <React/RCTAssert.h>
#import <React/RCTFont.h>
#import <React/RCTLog.h>
NSString *const RCTTextAttributesIsHighlightedAttributeName = @"RCTTextAttributesIsHighlightedAttributeName";
NSString *const RCTTextAttributesTagAttributeName = @"RCTTextAttributesTagAttributeName";
@implementation RCTTextAttributes
- (instancetype)init
{
if (self = [super init]) {
_fontSize = NAN;
_letterSpacing = NAN;
_lineHeight = NAN;
_textDecorationStyle = NSUnderlineStyleSingle;
_fontSizeMultiplier = NAN;
_alignment = NSTextAlignmentNatural;
_baseWritingDirection = NSWritingDirectionNatural;
_textShadowRadius = NAN;
_opacity = NAN;
_textTransform = RCTTextTransformUndefined;
}
return self;
}
- (void)applyTextAttributes:(RCTTextAttributes *)textAttributes
{
// Note: All lines marked with `*` does not use explicit/correct rules to compare old and new values becuase
// their types do not have special designated value representing undefined/unspecified/inherit meaning.
// We will address this in the future.
// Color
_foregroundColor = textAttributes->_foregroundColor ?: _foregroundColor;
_backgroundColor = textAttributes->_backgroundColor ?: _backgroundColor;
_opacity = !isnan(textAttributes->_opacity) ? (isnan(_opacity) ? 1.0 : _opacity) * textAttributes->_opacity : _opacity;
// Font
_fontFamily = textAttributes->_fontFamily ?: _fontFamily;
_fontSize = !isnan(textAttributes->_fontSize) ? textAttributes->_fontSize : _fontSize;
_fontSizeMultiplier = !isnan(textAttributes->_fontSizeMultiplier) ? textAttributes->_fontSizeMultiplier : _fontSizeMultiplier;
_fontWeight = textAttributes->_fontWeight ?: _fontWeight;
_fontStyle = textAttributes->_fontStyle ?: _fontStyle;
_fontVariant = textAttributes->_fontVariant ?: _fontVariant;
_allowFontScaling = textAttributes->_allowFontScaling || _allowFontScaling; // *
_letterSpacing = !isnan(textAttributes->_letterSpacing) ? textAttributes->_letterSpacing : _letterSpacing;
// Paragraph Styles
_lineHeight = !isnan(textAttributes->_lineHeight) ? textAttributes->_lineHeight : _lineHeight;
_alignment = textAttributes->_alignment != NSTextAlignmentNatural ? textAttributes->_alignment : _alignment; // *
_baseWritingDirection = textAttributes->_baseWritingDirection != NSWritingDirectionNatural ? textAttributes->_baseWritingDirection : _baseWritingDirection; // *
// Decoration
_textDecorationColor = textAttributes->_textDecorationColor ?: _textDecorationColor;
_textDecorationStyle = textAttributes->_textDecorationStyle != NSUnderlineStyleSingle ? textAttributes->_textDecorationStyle : _textDecorationStyle; // *
_textDecorationLine = textAttributes->_textDecorationLine != RCTTextDecorationLineTypeNone ? textAttributes->_textDecorationLine : _textDecorationLine; // *
// Shadow
_textShadowOffset = !CGSizeEqualToSize(textAttributes->_textShadowOffset, CGSizeZero) ? textAttributes->_textShadowOffset : _textShadowOffset; // *
_textShadowRadius = !isnan(textAttributes->_textShadowRadius) ? textAttributes->_textShadowRadius : _textShadowRadius;
_textShadowColor = textAttributes->_textShadowColor ?: _textShadowColor;
// Special
_isHighlighted = textAttributes->_isHighlighted || _isHighlighted; // *
_tag = textAttributes->_tag ?: _tag;
_layoutDirection = textAttributes->_layoutDirection != UIUserInterfaceLayoutDirectionLeftToRight ? textAttributes->_layoutDirection : _layoutDirection;
_textTransform = textAttributes->_textTransform != RCTTextTransformUndefined ? textAttributes->_textTransform : _textTransform;
}
- (NSDictionary<NSAttributedStringKey, id> *)effectiveTextAttributes
{
NSMutableDictionary<NSAttributedStringKey, id> *attributes =
[NSMutableDictionary dictionaryWithCapacity:10];
// Font
UIFont *font = self.effectiveFont;
if (font) {
attributes[NSFontAttributeName] = font;
}
// Colors
UIColor *effectiveForegroundColor = self.effectiveForegroundColor;
if (_foregroundColor || !isnan(_opacity)) {
attributes[NSForegroundColorAttributeName] = effectiveForegroundColor;
}
if (_backgroundColor || !isnan(_opacity)) {
attributes[NSBackgroundColorAttributeName] = self.effectiveBackgroundColor;
}
// Kerning
if (!isnan(_letterSpacing)) {
attributes[NSKernAttributeName] = @(_letterSpacing);
}
// Paragraph Style
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
BOOL isParagraphStyleUsed = NO;
if (_alignment != NSTextAlignmentNatural) {
NSTextAlignment alignment = _alignment;
if (_layoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) {
if (alignment == NSTextAlignmentRight) {
alignment = NSTextAlignmentLeft;
} else if (alignment == NSTextAlignmentLeft) {
alignment = NSTextAlignmentRight;
}
}
paragraphStyle.alignment = alignment;
isParagraphStyleUsed = YES;
}
if (_baseWritingDirection != NSWritingDirectionNatural) {
paragraphStyle.baseWritingDirection = _baseWritingDirection;
isParagraphStyleUsed = YES;
}
if (!isnan(_lineHeight)) {
CGFloat lineHeight = _lineHeight * self.effectiveFontSizeMultiplier;
paragraphStyle.minimumLineHeight = lineHeight;
paragraphStyle.maximumLineHeight = lineHeight;
isParagraphStyleUsed = YES;
}
if (isParagraphStyleUsed) {
attributes[NSParagraphStyleAttributeName] = paragraphStyle;
}
// Decoration
BOOL isTextDecorationEnabled = NO;
if (_textDecorationLine == RCTTextDecorationLineTypeUnderline ||
_textDecorationLine == RCTTextDecorationLineTypeUnderlineStrikethrough) {
isTextDecorationEnabled = YES;
attributes[NSUnderlineStyleAttributeName] = @(_textDecorationStyle);
}
if (_textDecorationLine == RCTTextDecorationLineTypeStrikethrough ||
_textDecorationLine == RCTTextDecorationLineTypeUnderlineStrikethrough){
isTextDecorationEnabled = YES;
attributes[NSStrikethroughStyleAttributeName] = @(_textDecorationStyle);
}
if (_textDecorationColor || isTextDecorationEnabled) {
attributes[NSStrikethroughColorAttributeName] = _textDecorationColor ?: effectiveForegroundColor;
attributes[NSUnderlineColorAttributeName] = _textDecorationColor ?: effectiveForegroundColor;
}
// Shadow
if (!CGSizeEqualToSize(_textShadowOffset, CGSizeZero)) {
NSShadow *shadow = [NSShadow new];
shadow.shadowOffset = _textShadowOffset;
shadow.shadowBlurRadius = _textShadowRadius;
shadow.shadowColor = _textShadowColor;
attributes[NSShadowAttributeName] = shadow;
}
// Special
if (_isHighlighted) {
attributes[RCTTextAttributesIsHighlightedAttributeName] = @YES;
}
if (_tag) {
attributes[RCTTextAttributesTagAttributeName] = _tag;
}
return [attributes copy];
}
- (UIFont *)effectiveFont
{
// FIXME: RCTFont has thread-safety issues and must be rewritten.
return [RCTFont updateFont:nil
withFamily:_fontFamily
size:@(isnan(_fontSize) ? 0 : _fontSize)
weight:_fontWeight
style:_fontStyle
variant:_fontVariant
scaleMultiplier:self.effectiveFontSizeMultiplier];
}
- (CGFloat)effectiveFontSizeMultiplier
{
return !RCTHasFontHandlerSet() && _allowFontScaling && !isnan(_fontSizeMultiplier) ? _fontSizeMultiplier : 1.0;
}
- (UIColor *)effectiveForegroundColor
{
UIColor *effectiveForegroundColor = _foregroundColor ?: [UIColor blackColor];
if (!isnan(_opacity)) {
effectiveForegroundColor = [effectiveForegroundColor colorWithAlphaComponent:CGColorGetAlpha(effectiveForegroundColor.CGColor) * _opacity];
}
return effectiveForegroundColor;
}
- (UIColor *)effectiveBackgroundColor
{
UIColor *effectiveBackgroundColor = _backgroundColor;// ?: [[UIColor whiteColor] colorWithAlphaComponent:0];
if (effectiveBackgroundColor && !isnan(_opacity)) {
effectiveBackgroundColor = [effectiveBackgroundColor colorWithAlphaComponent:CGColorGetAlpha(effectiveBackgroundColor.CGColor) * _opacity];
}
return effectiveBackgroundColor ?: [UIColor clearColor];
}
- (NSString *)applyTextAttributesToText:(NSString *)text
{
switch (_textTransform) {
case RCTTextTransformUndefined:
case RCTTextTransformNone:
return text;
case RCTTextTransformLowercase:
return [text lowercaseString];
case RCTTextTransformUppercase:
return [text uppercaseString];
case RCTTextTransformCapitalize:
return [text capitalizedString];
}
}
- (RCTTextAttributes *)copyWithZone:(NSZone *)zone
{
RCTTextAttributes *textAttributes = [RCTTextAttributes new];
[textAttributes applyTextAttributes:self];
return textAttributes;
}
#pragma mark - NSObject
- (BOOL)isEqual:(RCTTextAttributes *)textAttributes
{
if (self == textAttributes) {
return YES;
}
#define RCTTextAttributesCompareFloats(a) ((a == textAttributes->a) || (isnan(a) && isnan(textAttributes->a)))
#define RCTTextAttributesCompareSize(a) CGSizeEqualToSize(a, textAttributes->a)
#define RCTTextAttributesCompareObjects(a) ((a == textAttributes->a) || [a isEqual:textAttributes->a])
#define RCTTextAttributesCompareStrings(a) ((a == textAttributes->a) || [a isEqualToString:textAttributes->a])
#define RCTTextAttributesCompareOthers(a) (a == textAttributes->a)
return
RCTTextAttributesCompareObjects(_foregroundColor) &&
RCTTextAttributesCompareObjects(_backgroundColor) &&
RCTTextAttributesCompareFloats(_opacity) &&
// Font
RCTTextAttributesCompareObjects(_fontFamily) &&
RCTTextAttributesCompareFloats(_fontSize) &&
RCTTextAttributesCompareFloats(_fontSizeMultiplier) &&
RCTTextAttributesCompareStrings(_fontWeight) &&
RCTTextAttributesCompareObjects(_fontStyle) &&
RCTTextAttributesCompareObjects(_fontVariant) &&
RCTTextAttributesCompareOthers(_allowFontScaling) &&
RCTTextAttributesCompareFloats(_letterSpacing) &&
// Paragraph Styles
RCTTextAttributesCompareFloats(_lineHeight) &&
RCTTextAttributesCompareFloats(_alignment) &&
RCTTextAttributesCompareOthers(_baseWritingDirection) &&
// Decoration
RCTTextAttributesCompareObjects(_textDecorationColor) &&
RCTTextAttributesCompareOthers(_textDecorationStyle) &&
RCTTextAttributesCompareOthers(_textDecorationLine) &&
// Shadow
RCTTextAttributesCompareSize(_textShadowOffset) &&
RCTTextAttributesCompareFloats(_textShadowRadius) &&
RCTTextAttributesCompareObjects(_textShadowColor) &&
// Special
RCTTextAttributesCompareOthers(_isHighlighted) &&
RCTTextAttributesCompareObjects(_tag) &&
RCTTextAttributesCompareOthers(_layoutDirection) &&
RCTTextAttributesCompareOthers(_textTransform);
}
@end