mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
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
299 lines
11 KiB
Plaintext
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{};
|
|
}
|