Skip to content

Commit

Permalink
Implemented font-variation-settings for iOS (fabric and paper)
Browse files Browse the repository at this point in the history
  • Loading branch information
davebcn87 committed May 27, 2024
1 parent a908387 commit b491a59
Show file tree
Hide file tree
Showing 19 changed files with 208 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = {
fontSize: true,
fontStyle: true,
fontVariant: {process: processFontVariant},
fontVariationSettings: true,
fontWeight: true,
includeFontPadding: true,
letterSpacing: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,7 @@ export type ____TextStyle_InternalCore = $ReadOnly<{
fontStyle?: 'normal' | 'italic',
fontWeight?: ____FontWeight_Internal,
fontVariant?: ____FontVariantArray_Internal | string,
fontVariationSettings?: string,
textShadowOffset?: $ReadOnly<{
width: number,
height: number,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ - (RCTShadowView *)shadowView
RCT_REMAP_SHADOW_PROPERTY(fontWeight, textAttributes.fontWeight, NSString)
RCT_REMAP_SHADOW_PROPERTY(fontStyle, textAttributes.fontStyle, NSString)
RCT_REMAP_SHADOW_PROPERTY(fontVariant, textAttributes.fontVariant, NSArray)
RCT_REMAP_SHADOW_PROPERTY(fontVariationSettings, textAttributes.fontVariationSettings, NSString)
RCT_REMAP_SHADOW_PROPERTY(allowFontScaling, textAttributes.allowFontScaling, BOOL)
RCT_REMAP_SHADOW_PROPERTY(dynamicTypeRamp, textAttributes.dynamicTypeRamp, RCTDynamicTypeRamp)
RCT_REMAP_SHADOW_PROPERTY(maxFontSizeMultiplier, textAttributes.maxFontSizeMultiplier, CGFloat)
Expand Down
1 change: 1 addition & 0 deletions packages/react-native/Libraries/Text/RCTTextAttributes.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ extern NSString *const RCTTextAttributesTagAttributeName;
@property (nonatomic, copy, nullable) NSString *fontWeight;
@property (nonatomic, copy, nullable) NSString *fontStyle;
@property (nonatomic, copy, nullable) NSArray<NSString *> *fontVariant;
@property (nonatomic, copy, nullable) NSString *fontVariationSettings;
@property (nonatomic, assign) BOOL allowFontScaling;
@property (nonatomic, assign) RCTDynamicTypeRamp dynamicTypeRamp;
@property (nonatomic, assign) CGFloat letterSpacing;
Expand Down
2 changes: 2 additions & 0 deletions packages/react-native/Libraries/Text/RCTTextAttributes.mm
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ - (void)applyTextAttributes:(RCTTextAttributes *)textAttributes
_fontWeight = textAttributes->_fontWeight ?: _fontWeight;
_fontStyle = textAttributes->_fontStyle ?: _fontStyle;
_fontVariant = textAttributes->_fontVariant ?: _fontVariant;
_fontVariationSettings = textAttributes->_fontVariationSettings ?: _fontVariationSettings;
_allowFontScaling = textAttributes->_allowFontScaling || _allowFontScaling; // *
_dynamicTypeRamp = textAttributes->_dynamicTypeRamp != RCTDynamicTypeRampUndefined ? textAttributes->_dynamicTypeRamp
: _dynamicTypeRamp;
Expand Down Expand Up @@ -223,6 +224,7 @@ - (UIFont *)effectiveFont
weight:_fontWeight
style:_fontStyle
variant:_fontVariant
fontVariationSettings:_fontVariationSettings
scaleMultiplier:self.effectiveFontSizeMultiplier];
}

Expand Down
1 change: 1 addition & 0 deletions packages/react-native/React/Views/RCTFont.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ RCT_EXTERN BOOL RCTHasFontHandlerSet(void);
weight:(NSString *)weight
style:(NSString *)style
variant:(NSArray<NSString *> *)variant
fontVariationSettings:(NSString *)fontVariationSettings
scaleMultiplier:(CGFloat)scaleMultiplier;

+ (UIFont *)updateFont:(UIFont *)font withFamily:(NSString *)family;
Expand Down
79 changes: 75 additions & 4 deletions packages/react-native/React/Views/RCTFont.mm
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ + (UIFont *)UIFont:(id)json
weight:[RCTConvert NSString:json[@"fontWeight"]]
style:[RCTConvert NSString:json[@"fontStyle"]]
variant:[RCTConvert NSStringArray:json[@"fontVariant"]]
fontVariationSettings:[RCTConvert NSString:json[@"fontVariationSettings"]]
scaleMultiplier:1];
}

Expand Down Expand Up @@ -378,12 +379,27 @@ + (RCTFontVariantDescriptor *)RCTFontVariantDescriptor:(id)json

@implementation RCTFont

+ (NSNumber *)openTypeTagToNumber:(NSString *)tag {
if (tag.length != 4) {
return nil;
}

NSData *data = [tag dataUsingEncoding:NSUTF8StringEncoding];
const char *bytes = (const char *)[data bytes];

uint32_t value = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];

return @(value);
}


+ (UIFont *)updateFont:(UIFont *)font
withFamily:(NSString *)family
size:(NSNumber *)size
weight:(NSString *)weight
style:(NSString *)style
variant:(NSArray<RCTFontVariantDescriptor *> *)variant
fontVariationSettings:(NSString *)fontVariationSettings
scaleMultiplier:(CGFloat)scaleMultiplier
{
// Defaults
Expand Down Expand Up @@ -499,27 +515,82 @@ + (UIFont *)updateFont:(UIFont *)font
font = [UIFont fontWithDescriptor:fontDescriptor size:fontSize];
}

if (fontVariationSettings) {
NSArray<NSString *> *variationSettings = [fontVariationSettings componentsSeparatedByString:@","];
for (NSString *variationSetting in variationSettings) {
NSString *trimmedSetting = [variationSetting stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSArray<NSString *> *setting = [trimmedSetting componentsSeparatedByString:@" "];

if (setting.count != 2) {
NSLog(@"Error: Setting does not contain exactly 2 components. Setting: %@", setting);
continue;
}

NSString *attribute = [[[setting[0]
stringByReplacingOccurrencesOfString:@"\"" withString:@""]
stringByReplacingOccurrencesOfString:@"\'" withString:@""]
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];

NSString *value = [setting[1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];

if (attribute.length == 0 || value.length == 0) {
NSLog(@"Error: Invalid attribute or value. Attribute: %@, Value: %@", attribute, value);
continue;
}

NSNumber *tag = [RCTFont openTypeTagToNumber:attribute];
if (!tag) {
NSLog(@"Error: Invalid attribute in fontVariationSettings: %@", attribute);
continue;
}

NSNumber *valueNumber = @([value intValue]);
NSDictionary<NSNumber *, NSNumber *> *variations = @{tag: valueNumber};

UIFontDescriptor *fontDescriptor = [UIFontDescriptor fontDescriptorWithFontAttributes:@{
UIFontDescriptorNameAttribute: familyName,
(NSString *)kCTFontVariationAttribute: variations
}];

if (!fontDescriptor) {
NSLog(@"Error: Could not create font descriptor with variations. Variations: %@", variations);
continue;
}

UIFontDescriptor *existingFontDescriptors = font.fontDescriptor;
UIFontDescriptor *mergedFontDescriptor = [existingFontDescriptors fontDescriptorByAddingAttributes:fontDescriptor.fontAttributes];
font = [UIFont fontWithDescriptor:mergedFontDescriptor size:fontSize];
}
}

return font;
}

+ (UIFont *)updateFont:(UIFont *)font withFamily:(NSString *)family
{
return [self updateFont:font withFamily:family size:nil weight:nil style:nil variant:nil scaleMultiplier:1];
return [self updateFont:font withFamily:family size:nil weight:nil style:nil variant:nil fontVariationSettings:nil scaleMultiplier:1];
}

+ (UIFont *)updateFont:(UIFont *)font withSize:(NSNumber *)size
{
return [self updateFont:font withFamily:nil size:size weight:nil style:nil variant:nil scaleMultiplier:1];
return [self updateFont:font withFamily:nil size:size weight:nil style:nil variant:nil fontVariationSettings:nil scaleMultiplier:1];
}

+ (UIFont *)updateFont:(UIFont *)font withWeight:(NSString *)weight
{
return [self updateFont:font withFamily:nil size:nil weight:weight style:nil variant:nil scaleMultiplier:1];
return [self updateFont:font withFamily:nil size:nil weight:weight style:nil variant:nil fontVariationSettings:nil scaleMultiplier:1];
}

+ (UIFont *)updateFont:(UIFont *)font withStyle:(NSString *)style
{
return [self updateFont:font withFamily:nil size:nil weight:nil style:style variant:nil scaleMultiplier:1];
return [self updateFont:font withFamily:nil size:nil weight:nil style:style variant:nil fontVariationSettings:nil scaleMultiplier:1];
}

+ (UIFont *)updateFont:(UIFont *)font withFontVariationSettings:(NSString *)fontVariationSettings
{
return [self updateFont:font withFamily:nil size:nil weight:nil style:nil variant:nil fontVariationSettings:fontVariationSettings scaleMultiplier:1];
}



@end
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ void TextAttributes::apply(TextAttributes textAttributes) {
fontVariant = textAttributes.fontVariant.has_value()
? textAttributes.fontVariant
: fontVariant;
fontVariationSettings = !textAttributes.fontVariationSettings.empty()
? textAttributes.fontVariationSettings : fontVariationSettings;
allowFontScaling = textAttributes.allowFontScaling.has_value()
? textAttributes.allowFontScaling
: allowFontScaling;
Expand Down Expand Up @@ -124,6 +126,7 @@ bool TextAttributes::operator==(const TextAttributes& rhs) const {
fontWeight,
fontStyle,
fontVariant,
fontVariationSettings,
allowFontScaling,
dynamicTypeRamp,
alignment,
Expand All @@ -148,6 +151,7 @@ bool TextAttributes::operator==(const TextAttributes& rhs) const {
rhs.fontWeight,
rhs.fontStyle,
rhs.fontVariant,
rhs.fontVariationSettings,
rhs.allowFontScaling,
rhs.dynamicTypeRamp,
rhs.alignment,
Expand Down Expand Up @@ -207,6 +211,7 @@ SharedDebugStringConvertibleList TextAttributes::getDebugProps() const {
debugStringConvertibleItem("fontWeight", fontWeight),
debugStringConvertibleItem("fontStyle", fontStyle),
debugStringConvertibleItem("fontVariant", fontVariant),
debugStringConvertibleItem("fontVariationSettings", fontVariationSettings),
debugStringConvertibleItem("allowFontScaling", allowFontScaling),
debugStringConvertibleItem("dynamicTypeRamp", dynamicTypeRamp),
debugStringConvertibleItem("letterSpacing", letterSpacing),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class TextAttributes : public DebugStringConvertible {
std::optional<FontWeight> fontWeight{};
std::optional<FontStyle> fontStyle{};
std::optional<FontVariant> fontVariant{};
std::string fontVariationSettings{""};
std::optional<bool> allowFontScaling{};
std::optional<DynamicTypeRamp> dynamicTypeRamp{};
Float letterSpacing{std::numeric_limits<Float>::quiet_NaN()};
Expand Down Expand Up @@ -120,6 +121,7 @@ struct hash<facebook::react::TextAttributes> {
textAttributes.fontWeight,
textAttributes.fontStyle,
textAttributes.fontVariant,
textAttributes.fontVariationSettings,
textAttributes.allowFontScaling,
textAttributes.letterSpacing,
textAttributes.textTransform,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ static TextAttributes convertRawProp(
"fontVariant",
sourceTextAttributes.fontVariant,
defaultTextAttributes.fontVariant);
textAttributes.fontVariationSettings = convertRawProp(
context,
rawProps,
"fontVariationSettings",
sourceTextAttributes.fontVariationSettings,
defaultTextAttributes.fontVariationSettings);

textAttributes.allowFontScaling = convertRawProp(
context,
rawProps,
Expand Down Expand Up @@ -258,6 +265,8 @@ void BaseTextProps::setProp(
defaults, value, textAttributes, fontStyle, "fontStyle");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, fontVariant, "fontVariant");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, fontVariationSettings, "fontVariationSettings");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, allowFontScaling, "allowFontScaling");
REBUILD_FIELD_SWITCH_CASE(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ inline bool areTextAttributesEquivalentLayoutWise(
lhs.fontWeight,
lhs.fontStyle,
lhs.fontVariant,
lhs.fontVariationSettings,
lhs.allowFontScaling,
lhs.dynamicTypeRamp,
lhs.alignment) ==
Expand All @@ -101,6 +102,7 @@ inline bool areTextAttributesEquivalentLayoutWise(
rhs.fontWeight,
rhs.fontStyle,
rhs.fontVariant,
rhs.fontVariationSettings,
rhs.allowFontScaling,
rhs.dynamicTypeRamp,
rhs.alignment) &&
Expand All @@ -121,6 +123,7 @@ inline size_t textAttributesHashLayoutWise(
textAttributes.fontWeight,
textAttributes.fontStyle,
textAttributes.fontVariant,
textAttributes.fontVariationSettings,
textAttributes.allowFontScaling,
textAttributes.dynamicTypeRamp,
textAttributes.letterSpacing,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ inline static CGFloat RCTEffectiveFontSizeMultiplierFromTextAttributes(const Tex
inline static UIFont *RCTEffectiveFontFromTextAttributes(const TextAttributes &textAttributes)
{
NSString *fontFamily = [NSString stringWithUTF8String:textAttributes.fontFamily.c_str()];
NSString *fontVariationSettings = [NSString stringWithUTF8String:textAttributes.fontVariationSettings.c_str()];

RCTFontProperties fontProperties;
fontProperties.family = fontFamily;
Expand All @@ -146,6 +147,8 @@ inline static CGFloat RCTEffectiveFontSizeMultiplierFromTextAttributes(const Tex
fontProperties.variant = textAttributes.fontVariant.has_value()
? RCTFontVariantFromFontVariant(textAttributes.fontVariant.value())
: RCTFontVariantUndefined;
fontProperties.fontVariationSettings = fontVariationSettings;

fontProperties.weight = textAttributes.fontWeight.has_value()
? RCTUIFontWeightFromInteger((NSInteger)textAttributes.fontWeight.value())
: NAN;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ struct RCTFontProperties {
UIFontWeight weight = NAN;
RCTFontStyle style = RCTFontStyleUndefined;
RCTFontVariant variant = RCTFontVariantUndefined;
NSString *fontVariationSettings = nil;
CGFloat sizeMultiplier = NAN;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,19 @@ static RCTFontProperties RCTDefaultFontProperties()
return defaultFontProperties;
}

static NSNumber *openTypeTagToNumber(NSString *tag) {
if (tag.length != 4) {
NSLog(@"Error: Tag must be exactly 4 characters long");
return nil;
}

const unsigned char* bytes = (const unsigned char*)[tag cStringUsingEncoding:NSUTF8StringEncoding];

uint32_t value = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];

return @(value);
}

static RCTFontProperties RCTResolveFontProperties(
RCTFontProperties fontProperties,
RCTFontProperties baseFontProperties)
Expand Down Expand Up @@ -207,6 +220,53 @@ static RCTFontStyle RCTGetFontStyle(UIFont *font)
fontDescriptorByAddingAttributes:@{UIFontDescriptorFeatureSettingsAttribute : fontFeatures}];
font = [UIFont fontWithDescriptor:fontDescriptor size:effectiveFontSize];
}


if (fontProperties.fontVariationSettings) {
NSArray<NSString *> *variationSettings = [fontProperties.fontVariationSettings componentsSeparatedByString:@","];
for (NSString *variationSetting in variationSettings) {
NSString *trimmedSetting = [variationSetting stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSArray<NSString *> *setting = [trimmedSetting componentsSeparatedByString:@" "];

if (setting.count != 2) {
NSLog(@"Error: Setting does not contain exactly 2 components. Setting: %@", setting);
continue;
}

NSString *attribute = [[[setting[0]
stringByReplacingOccurrencesOfString:@"\"" withString:@""]
stringByReplacingOccurrencesOfString:@"\'" withString:@""]
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];

NSString *value = [setting[1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];

if (attribute.length == 0 || value.length == 0) {
NSLog(@"Error: Invalid attribute or value. Attribute: %@, Value: %@", attribute, value);
continue;
}

NSNumber *tag = openTypeTagToNumber(attribute);
if (!tag) {
NSLog(@"Error: Invalid attribute in fontVariationSettings: %@", attribute);
continue;
}

NSNumber *valueNumber = @([value intValue]);
NSDictionary<NSNumber *, NSNumber *> *variations = @{tag: valueNumber};

UIFontDescriptor *fontDescriptor = [UIFontDescriptor fontDescriptorWithFontAttributes:@{
UIFontDescriptorNameAttribute: fontProperties.family,
(NSString *)kCTFontVariationAttribute: variations
}];

if (!fontDescriptor) {
NSLog(@"Error: Could not create font descriptor with variations. Variations: %@", variations);
continue;
}

UIFontDescriptor *existingFontDescriptors = font.fontDescriptor;
UIFontDescriptor *mergedFontDescriptor = [existingFontDescriptors fontDescriptorByAddingAttributes:fontDescriptor.fontAttributes];
font = [UIFont fontWithDescriptor:mergedFontDescriptor size:effectiveFontSize];
}
}
return font;
}
Binary file added packages/rn-tester/MaterialSymbolsSharp.ttf
Binary file not shown.
7 changes: 7 additions & 0 deletions packages/rn-tester/RNTester/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSApplicationCategoryType</key>
<string></string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
Expand All @@ -46,6 +48,11 @@
<string>You need to add NSLocationWhenInUseUsageDescription key in Info.plist to enable geolocation, otherwise it is going to *fail silently*!</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>You need to add NSPhotoLibraryUsageDescription key in Info.plist to enable photo library usage, otherwise it is going to *fail silently*!</string>
<key>UIAppFonts</key>
<array>
<string>Inter.ttf</string>
<string>MaterialSymbolsSharp.ttf</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
Expand Down
Binary file added packages/rn-tester/RNTester/Inter.ttf
Binary file not shown.
Loading

0 comments on commit b491a59

Please sign in to comment.