react-native/ReactCommon/fabric/textlayoutmanager/RCTAttributedTextUtils.mm

228 lines
8.8 KiB
Plaintext

/**
* 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 "RCTAttributedTextUtils.h"
#include <fabric/core/LayoutableShadowNode.h>
#include <fabric/textlayoutmanager/RCTFontProperties.h>
#include <fabric/textlayoutmanager/RCTFontUtils.h>
#include <fabric/textlayoutmanager/RCTTextPrimitivesConversions.h>
inline static UIFont *RCTEffectiveFontFromTextAttributes(const TextAttributes &textAttributes) {
NSString *fontFamily = [NSString stringWithCString:textAttributes.fontFamily.c_str()
encoding:NSASCIIStringEncoding];
RCTFontProperties fontProperties;
fontProperties.family = fontFamily;
fontProperties.size = textAttributes.fontSize;
fontProperties.style = textAttributes.fontStyle.has_value() ? RCTFontStyleFromFontStyle(textAttributes.fontStyle.value()) : RCTFontStyleUndefined;
fontProperties.variant = textAttributes.fontVariant.has_value() ? RCTFontVariantFromFontVariant(textAttributes.fontVariant.value()) : RCTFontVariantDefault;
fontProperties.weight = textAttributes.fontWeight.has_value() ? CGFloat(textAttributes.fontWeight.value()) : NAN;
fontProperties.sizeMultiplier = textAttributes.fontSizeMultiplier;
return RCTFontWithFontProperties(fontProperties);
}
inline static CGFloat RCTEffectiveFontSizeMultiplierFromTextAttributes(const TextAttributes &textAttributes) {
return textAttributes.allowFontScaling.value_or(true) && !isnan(textAttributes.fontSizeMultiplier) ? textAttributes.fontSizeMultiplier : 1.0;
}
inline static UIColor *RCTEffectiveForegroundColorFromTextAttributes(const TextAttributes &textAttributes) {
UIColor *effectiveForegroundColor = RCTUIColorFromSharedColor(textAttributes.foregroundColor) ?: [UIColor blackColor];
if (!isnan(textAttributes.opacity)) {
effectiveForegroundColor =
[effectiveForegroundColor colorWithAlphaComponent:CGColorGetAlpha(effectiveForegroundColor.CGColor) * textAttributes.opacity];
}
return effectiveForegroundColor;
}
inline static UIColor *RCTEffectiveBackgroundColorFromTextAttributes(const TextAttributes &textAttributes) {
UIColor *effectiveBackgroundColor = RCTUIColorFromSharedColor(textAttributes.backgroundColor);
if (effectiveBackgroundColor && !isnan(textAttributes.opacity)) {
effectiveBackgroundColor =
[effectiveBackgroundColor colorWithAlphaComponent:CGColorGetAlpha(effectiveBackgroundColor.CGColor) * textAttributes.opacity];
}
return effectiveBackgroundColor ?: [UIColor clearColor];
}
static NSDictionary<NSAttributedStringKey, id> *RCTNSTextAttributesFromTextAttributes(const TextAttributes &textAttributes) {
NSMutableDictionary<NSAttributedStringKey, id> *attributes =
[NSMutableDictionary dictionaryWithCapacity:10];
// Font
UIFont *font = RCTEffectiveFontFromTextAttributes(textAttributes);
if (font) {
attributes[NSFontAttributeName] = font;
}
// Colors
UIColor *effectiveForegroundColor = RCTEffectiveForegroundColorFromTextAttributes(textAttributes);
if (textAttributes.foregroundColor || !isnan(textAttributes.opacity)) {
attributes[NSForegroundColorAttributeName] = effectiveForegroundColor;
}
if (textAttributes.backgroundColor || !isnan(textAttributes.opacity)) {
attributes[NSBackgroundColorAttributeName] = RCTEffectiveBackgroundColorFromTextAttributes(textAttributes);
}
// Kerning
if (!isnan(textAttributes.letterSpacing)) {
attributes[NSKernAttributeName] = @(textAttributes.letterSpacing);
}
// Paragraph Style
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
BOOL isParagraphStyleUsed = NO;
if (textAttributes.alignment.has_value()) {
TextAlignment textAlignment = textAttributes.alignment.value_or(TextAlignment::Natural);
if (textAttributes.layoutDirection.value_or(LayoutDirection::LeftToRight) == LayoutDirection::RightToLeft) {
if (textAlignment == TextAlignment::Right) {
textAlignment = TextAlignment::Left;
} else if (textAlignment == TextAlignment::Left) {
textAlignment = TextAlignment::Right;
}
}
paragraphStyle.alignment =
RCTNSTextAlignmentFromTextAlignment(textAlignment);
isParagraphStyleUsed = YES;
}
if (textAttributes.baseWritingDirection.has_value()) {
paragraphStyle.baseWritingDirection =
RCTNSWritingDirectionFromWritingDirection(textAttributes.baseWritingDirection.value());
isParagraphStyleUsed = YES;
}
if (!isnan(textAttributes.lineHeight)) {
CGFloat lineHeight =
textAttributes.lineHeight * RCTEffectiveFontSizeMultiplierFromTextAttributes(textAttributes);
paragraphStyle.minimumLineHeight = lineHeight;
paragraphStyle.maximumLineHeight = lineHeight;
isParagraphStyleUsed = YES;
}
if (isParagraphStyleUsed) {
attributes[NSParagraphStyleAttributeName] = paragraphStyle;
}
// Decoration
if (textAttributes.textDecorationLineType.value_or(TextDecorationLineType::None) != TextDecorationLineType::None) {
auto textDecorationLineType = textAttributes.textDecorationLineType.value();
NSUnderlineStyle style =
RCTNSUnderlineStyleFromStyleAndPattern(
textAttributes.textDecorationLineStyle.value_or(TextDecorationLineStyle::Single),
textAttributes.textDecorationLinePattern.value_or(TextDecorationLinePattern::Solid)
);
UIColor *textDecorationColor = RCTUIColorFromSharedColor(textAttributes.textDecorationColor);
// Underline
if (textDecorationLineType == TextDecorationLineType::Underline ||
textDecorationLineType == TextDecorationLineType::UnderlineStrikethrough) {
attributes[NSUnderlineStyleAttributeName] = @(style);
if (textDecorationColor) {
attributes[NSUnderlineColorAttributeName] = textDecorationColor;
}
}
// Strikethrough
if (textDecorationLineType == TextDecorationLineType::Strikethrough ||
textDecorationLineType == TextDecorationLineType::UnderlineStrikethrough) {
attributes[NSStrikethroughStyleAttributeName] = @(style);
if (textDecorationColor) {
attributes[NSStrikethroughColorAttributeName] = textDecorationColor;
}
}
}
// Shadow
if (textAttributes.textShadowOffset.has_value()) {
auto textShadowOffset = textAttributes.textShadowOffset.value();
NSShadow *shadow = [NSShadow new];
shadow.shadowOffset = CGSize {textShadowOffset.x, textShadowOffset.y};
shadow.shadowBlurRadius = textAttributes.textShadowRadius;
shadow.shadowColor = RCTUIColorFromSharedColor(textAttributes.textShadowColor);
attributes[NSShadowAttributeName] = shadow;
}
// Special
if (textAttributes.isHighlighted) {
attributes[RCTAttributedStringIsHighlightedAttributeName] = @YES;
}
return [attributes copy];
}
NSAttributedString *RCTNSAttributedStringFromAttributedString(const AttributedString &attributedString) {
NSMutableAttributedString *nsAttributedString = [[NSMutableAttributedString alloc] init];
[nsAttributedString beginEditing];
for (auto fragment : attributedString.getFragments()) {
NSAttributedString *nsAttributedStringFragment;
SharedLayoutableShadowNode layoutableShadowNode =
std::dynamic_pointer_cast<const LayoutableShadowNode>(fragment.shadowNode);
if (layoutableShadowNode) {
auto layoutMetrics = layoutableShadowNode->getLayoutMetrics();
CGRect bounds = {
.origin = {
.x = layoutMetrics.frame.origin.x,
.y = layoutMetrics.frame.origin.y
},
.size = {
.width = layoutMetrics.frame.size.width,
.height = layoutMetrics.frame.size.height
}
};
NSTextAttachment *attachment = [NSTextAttachment new];
attachment.bounds = bounds;
nsAttributedStringFragment = [NSAttributedString attributedStringWithAttachment:attachment];
} else {
NSString *string =
[NSString stringWithCString:fragment.string.c_str()
encoding:NSASCIIStringEncoding];
nsAttributedStringFragment =
[[NSAttributedString alloc] initWithString:string
attributes:RCTNSTextAttributesFromTextAttributes(fragment.textAttributes)];
}
NSMutableAttributedString *nsMutableAttributedStringFragment =
[[NSMutableAttributedString alloc] initWithAttributedString:nsAttributedStringFragment];
if (fragment.shadowNode) {
NSDictionary<NSAttributedStringKey, id> *additionalTextAttributes = @{
RCTAttributedStringReactTagAttributeName: @(fragment.shadowNode->getTag())
};
[nsMutableAttributedStringFragment setAttributes:additionalTextAttributes
range:NSMakeRange(0, nsMutableAttributedStringFragment.length)];
}
[nsAttributedString appendAttributedString:nsMutableAttributedStringFragment];
}
[nsAttributedString endEditing];
return nsAttributedString;
}