Files
react-native/ReactCommon/fabric/textlayoutmanager/platform/ios/RCTAttributedTextUtils.mm
T
Jiayan ZhuangandFacebook GitHub Bot 550c66ea64 Change accessibilityRole to an enum
Summary:
Changelog:
[Internal] - Change the accessibilityRole from a string to an enum.

The change aims to save storage especially when scaling. Also, the enum makes it more difficult to input wrong because it gets checked when compiling.

Reviewed By: sammy-SC

Differential Revision: D22396351

fbshipit-source-id: b46ce0bd156c7705020ef5b061d8ac29e2cf3948
2020-07-14 09:55:32 -07:00

299 lines
11 KiB
Plaintext

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* 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 <react/core/LayoutableShadowNode.h>
#include <react/textlayoutmanager/RCTFontProperties.h>
#include <react/textlayoutmanager/RCTFontUtils.h>
#include <react/textlayoutmanager/RCTTextPrimitivesConversions.h>
#include <react/utils/ManagedObjectWrapper.h>
using namespace facebook::react;
@implementation RCTWeakEventEmitterWrapper {
std::weak_ptr<const EventEmitter> _weakEventEmitter;
}
- (void)setEventEmitter:(SharedEventEmitter)eventEmitter
{
_weakEventEmitter = eventEmitter;
}
- (SharedEventEmitter)eventEmitter
{
return _weakEventEmitter.lock();
}
- (void)dealloc
{
_weakEventEmitter.reset();
}
@end
inline static UIFontWeight RCTUIFontWeightFromInteger(NSInteger fontWeight)
{
assert(fontWeight > 50);
assert(fontWeight < 950);
static UIFontWeight weights[] = {/* ~100 */ UIFontWeightUltraLight,
/* ~200 */ UIFontWeightThin,
/* ~300 */ UIFontWeightLight,
/* ~400 */ UIFontWeightRegular,
/* ~500 */ UIFontWeightMedium,
/* ~600 */ UIFontWeightSemibold,
/* ~700 */ UIFontWeightBold,
/* ~800 */ UIFontWeightHeavy,
/* ~900 */ UIFontWeightBlack};
// The expression is designed to convert something like 760 or 830 to 7.
return weights[(fontWeight + 50) / 100 - 1];
}
inline static UIFont *RCTEffectiveFontFromTextAttributes(const TextAttributes &textAttributes)
{
NSString *fontFamily = [NSString stringWithCString:textAttributes.fontFamily.c_str() encoding:NSUTF8StringEncoding];
RCTFontProperties fontProperties;
fontProperties.family = fontFamily;
fontProperties.size = textAttributes.fontSize;
fontProperties.style = textAttributes.fontStyle.hasValue()
? RCTFontStyleFromFontStyle(textAttributes.fontStyle.value())
: RCTFontStyleUndefined;
fontProperties.variant = textAttributes.fontVariant.hasValue()
? RCTFontVariantFromFontVariant(textAttributes.fontVariant.value())
: RCTFontVariantUndefined;
fontProperties.weight = textAttributes.fontWeight.hasValue()
? RCTUIFontWeightFromInteger((NSInteger)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];
}
NSDictionary<NSAttributedStringKey, id> *RCTNSTextAttributesFromTextAttributes(TextAttributes const &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.hasValue()) {
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.hasValue()) {
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.hasValue()) {
auto textShadowOffset = textAttributes.textShadowOffset.value();
NSShadow *shadow = [NSShadow new];
shadow.shadowOffset = CGSize{textShadowOffset.width, textShadowOffset.height};
shadow.shadowBlurRadius = textAttributes.textShadowRadius;
shadow.shadowColor = RCTUIColorFromSharedColor(textAttributes.textShadowColor);
attributes[NSShadowAttributeName] = shadow;
}
// Special
if (textAttributes.isHighlighted) {
attributes[RCTAttributedStringIsHighlightedAttributeName] = @YES;
}
if (textAttributes.accessibilityRole.hasValue()) {
auto accessibilityRole = textAttributes.accessibilityRole.value();
switch (accessibilityRole) {
case AccessibilityRole::Link:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("link");
break;
case AccessibilityRole::Button:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("button");
break;
case AccessibilityRole::Image:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("image");
break;
};
}
return [attributes copy];
}
NSAttributedString *RCTNSAttributedStringFromAttributedString(const AttributedString &attributedString)
{
static UIImage *placeholderImage;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
placeholderImage = [[UIImage alloc] init];
});
NSMutableAttributedString *nsAttributedString = [[NSMutableAttributedString alloc] init];
[nsAttributedString beginEditing];
for (auto fragment : attributedString.getFragments()) {
NSMutableAttributedString *nsAttributedStringFragment;
if (fragment.isAttachment()) {
auto layoutMetrics = fragment.parentShadowView.layoutMetrics;
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.image = placeholderImage;
attachment.bounds = bounds;
nsAttributedStringFragment = [[NSMutableAttributedString attributedStringWithAttachment:attachment] mutableCopy];
} else {
NSString *string = [NSString stringWithCString:fragment.string.c_str() encoding:NSUTF8StringEncoding];
nsAttributedStringFragment = [[NSMutableAttributedString alloc]
initWithString:string
attributes:RCTNSTextAttributesFromTextAttributes(fragment.textAttributes)];
}
if (fragment.parentShadowView.componentHandle) {
RCTWeakEventEmitterWrapper *eventEmitterWrapper = [RCTWeakEventEmitterWrapper new];
eventEmitterWrapper.eventEmitter = fragment.parentShadowView.eventEmitter;
NSDictionary<NSAttributedStringKey, id> *additionalTextAttributes =
@{RCTAttributedStringEventEmitterKey : eventEmitterWrapper};
[nsAttributedStringFragment addAttributes:additionalTextAttributes
range:NSMakeRange(0, nsAttributedStringFragment.length)];
}
[nsAttributedString appendAttributedString:nsAttributedStringFragment];
}
[nsAttributedString endEditing];
return nsAttributedString;
}
NSAttributedString *RCTNSAttributedStringFromAttributedStringBox(AttributedStringBox const &attributedStringBox)
{
switch (attributedStringBox.getMode()) {
case AttributedStringBox::Mode::Value:
return RCTNSAttributedStringFromAttributedString(attributedStringBox.getValue());
case AttributedStringBox::Mode::OpaquePointer:
return (NSAttributedString *)unwrapManagedObject(attributedStringBox.getOpaquePointer());
}
}
AttributedStringBox RCTAttributedStringBoxFromNSAttributedString(NSAttributedString *nsAttributedString)
{
return nsAttributedString.length ? AttributedStringBox{wrapManagedObject(nsAttributedString)} : AttributedStringBox{};
}