Add iOS support for CSS property font-variant, accepting tabular-nums…

Summary:
Ground work for allowing `font-variant`s. Currently allows switching between `tabular-nums` and `proportional-nums`. I will need guidance on how to test this, and a few pointers on code style (new to Objective C, and had to make one or two hacks).
Closes https://github.com/facebook/react-native/pull/9045

Reviewed By: majak

Differential Revision: D3664338

Pulled By: javache

fbshipit-source-id: 032f326c37ee6150348da2b33b6a3fc1988e8920
This commit is contained in:
Jacob Parker 2016-08-09 08:42:58 -07:00 committed by Facebook Github Bot 1
parent ced272d906
commit f951da912d
10 changed files with 139 additions and 16 deletions

View File

@ -16,6 +16,8 @@
#import "RCTFont.h"
#import <CoreText/CoreText.h>
@interface RCTFontTests : XCTestCase
@end
@ -178,6 +180,27 @@
}
}
- (void)testVariant
{
{
UIFont *expected = [UIFont monospacedDigitSystemFontOfSize:14 weight:UIFontWeightRegular];
UIFont *result = [RCTConvert UIFont:@{@"fontVariant": @[@"tabular-nums"]}];
RCTAssertEqualFonts(expected, result);
}
{
UIFont *monospaceFont = [UIFont monospacedDigitSystemFontOfSize:14 weight:UIFontWeightRegular];
UIFontDescriptor *fontDescriptor = [monospaceFont.fontDescriptor fontDescriptorByAddingAttributes:@{
UIFontDescriptorFeatureSettingsAttribute: @[@{
UIFontFeatureTypeIdentifierKey: @(kLowerCaseType),
UIFontFeatureSelectorIdentifierKey: @(kLowerCaseSmallCapsSelector),
}]
}];
UIFont *expected = [UIFont fontWithDescriptor:fontDescriptor size:14];
UIFont *result = [RCTConvert UIFont:@{@"fontVariant": @[@"tabular-nums", @"small-caps"]}];
RCTAssertEqualFonts(expected, result);
}
}
- (void)testInvalidFont
{
{

View File

@ -465,6 +465,33 @@ exports.examples = [
</View>
);
},
}, {
title: 'Font variants',
render: function() {
return (
<View>
<Text style={{fontVariant: ['small-caps']}}>
Small Caps{'\n'}
</Text>
<Text style={{fontFamily: 'Hoefler Text', fontVariant: ['oldstyle-nums']}}>
Old Style nums 0123456789{'\n'}
</Text>
<Text style={{fontFamily: 'Hoefler Text', fontVariant: ['lining-nums']}}>
Lining nums 0123456789{'\n'}
</Text>
<Text style={{fontVariant: ['tabular-nums']}}>
Tabular nums{'\n'}
1111{'\n'}
2222{'\n'}
</Text>
<Text style={{fontVariant: ['proportional-nums']}}>
Proportional nums{'\n'}
1111{'\n'}
2222{'\n'}
</Text>
</View>
);
},
}];
var styles = StyleSheet.create({

View File

@ -20,6 +20,7 @@ extern NSString *const RCTReactTagAttributeName;
@property (nonatomic, assign) CGFloat fontSize;
@property (nonatomic, copy) NSString *fontWeight;
@property (nonatomic, copy) NSString *fontStyle;
@property (nonatomic, copy) NSArray *fontVariant;
@property (nonatomic, assign) BOOL isHighlighted;
@property (nonatomic, assign) CGFloat letterSpacing;
@property (nonatomic, assign) CGFloat lineHeight;

View File

@ -10,16 +10,15 @@
#import "RCTShadowText.h"
#import "RCTAccessibilityManager.h"
#import "RCTUIManager.h"
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTFont.h"
#import "RCTLog.h"
#import "RCTShadowRawText.h"
#import "RCTText.h"
#import "RCTUtils.h"
#import "RCTConvert.h"
#import "RCTTextView.h"
#import "RCTFont.h"
#import "RCTUIManager.h"
#import "RCTUtils.h"
NSString *const RCTShadowViewAttributeName = @"RCTShadowViewAttributeName";
NSString *const RCTIsHighlightedAttributeName = @"IsHighlightedAttributeName";
@ -267,8 +266,12 @@ static CSSSize RCTMeasure(void *context, float width, CSSMeasureMode widthMode,
_effectiveLetterSpacing = letterSpacing.doubleValue;
UIFont *font = [RCTFont updateFont:nil withFamily:fontFamily
size:fontSize weight:fontWeight style:fontStyle
UIFont *font = [RCTFont updateFont:nil
withFamily:fontFamily
size:fontSize
weight:fontWeight
style:fontStyle
variant:_fontVariant
scaleMultiplier:_allowFontScaling ? _fontSizeMultiplier : 1.0];
CGFloat heightOfTallestSubview = 0.0;
@ -467,6 +470,7 @@ RCT_TEXT_PROPERTY(FontFamily, _fontFamily, NSString *)
RCT_TEXT_PROPERTY(FontSize, _fontSize, CGFloat)
RCT_TEXT_PROPERTY(FontWeight, _fontWeight, NSString *)
RCT_TEXT_PROPERTY(FontStyle, _fontStyle, NSString *)
RCT_TEXT_PROPERTY(FontVariant, _fontVariant, NSArray *)
RCT_TEXT_PROPERTY(IsHighlighted, _isHighlighted, BOOL)
RCT_TEXT_PROPERTY(LetterSpacing, _letterSpacing, CGFloat)
RCT_TEXT_PROPERTY(LineHeight, _lineHeight, CGFloat)

View File

@ -60,6 +60,7 @@ RCT_EXPORT_SHADOW_PROPERTY(fontFamily, NSString)
RCT_EXPORT_SHADOW_PROPERTY(fontSize, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(fontWeight, NSString)
RCT_EXPORT_SHADOW_PROPERTY(fontStyle, NSString)
RCT_EXPORT_SHADOW_PROPERTY(fontVariant, NSArray)
RCT_EXPORT_SHADOW_PROPERTY(isHighlighted, BOOL)
RCT_EXPORT_SHADOW_PROPERTY(letterSpacing, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(lineHeight, CGFloat)

View File

@ -30,6 +30,15 @@ var TextStylePropTypes = Object.assign(Object.create(ViewStylePropTypes), {
['normal' /*default*/, 'bold',
'100', '200', '300', '400', '500', '600', '700', '800', '900']
),
fontVariant: ReactPropTypes.arrayOf(
ReactPropTypes.oneOf([
'small-caps',
'oldstyle-nums',
'lining-nums',
'tabular-nums',
'proportional-nums',
])
),
textShadowOffset: ReactPropTypes.shape(
{width: ReactPropTypes.number, height: ReactPropTypes.number}
),

View File

@ -232,7 +232,7 @@ RCT_CUSTOM_CONVERTER(type, type, [RCT_DEBUG ? [self NSNumber:json] : json getter
* This macro is used for creating converter functions for typed arrays.
*/
#define RCT_ARRAY_CONVERTER(type) \
+ (NSArray<id> *)type##Array:(id)json \
+ (NSArray<type *> *)type##Array:(id)json \
{ \
return RCTConvertArrayValue(@selector(type:), json); \
}

View File

@ -7,6 +7,8 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <CoreText/CoreText.h>
#import "RCTConvert.h"
#import <objc/message.h>

View File

@ -23,6 +23,7 @@
size:(NSNumber *)size
weight:(NSString *)weight
style:(NSString *)style
variant:(NSArray<NSString *> *)variant
scaleMultiplier:(CGFloat)scaleMultiplier;
+ (UIFont *)updateFont:(UIFont *)font withFamily:(NSString *)family;

View File

@ -8,6 +8,9 @@
*/
#import "RCTFont.h"
#import "RCTLog.h"
#import <CoreText/CoreText.h>
#import <mutex>
@ -127,6 +130,7 @@ static UIFont *cachedSystemFont(CGFloat size, RCTFontWeight weight)
size:[RCTConvert NSNumber:json[@"fontSize"]]
weight:[RCTConvert NSString:json[@"fontWeight"]]
style:[RCTConvert NSString:json[@"fontStyle"]]
variant:[RCTConvert NSStringArray:json[@"fontVariant"]]
scaleMultiplier:1];
}
@ -151,6 +155,45 @@ RCT_ENUM_CONVERTER(RCTFontStyle, (@{
@"oblique": @YES,
}), NO, boolValue)
typedef NSDictionary RCTFontVariantDescriptor;
+ (RCTFontVariantDescriptor *)RCTFontVariantDescriptor:(id)json
{
static NSDictionary *mapping;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
mapping = @{
@"small-caps": @{
UIFontFeatureTypeIdentifierKey: @(kLowerCaseType),
UIFontFeatureSelectorIdentifierKey: @(kLowerCaseSmallCapsSelector),
},
@"oldstyle-nums": @{
UIFontFeatureTypeIdentifierKey: @(kNumberCaseType),
UIFontFeatureSelectorIdentifierKey: @(kLowerCaseNumbersSelector),
},
@"lining-nums": @{
UIFontFeatureTypeIdentifierKey: @(kNumberCaseType),
UIFontFeatureSelectorIdentifierKey: @(kUpperCaseNumbersSelector),
},
@"tabular-nums": @{
UIFontFeatureTypeIdentifierKey: @(kNumberSpacingType),
UIFontFeatureSelectorIdentifierKey: @(kMonospacedNumbersSelector),
},
@"proportional-nums": @{
UIFontFeatureTypeIdentifierKey: @(kNumberSpacingType),
UIFontFeatureSelectorIdentifierKey: @(kProportionalNumbersSelector),
},
};
});
RCTFontVariantDescriptor *value = mapping[json];
if (RCT_DEBUG && !value && [json description].length > 0) {
RCTLogError(@"Invalid RCTFontVariantDescriptor '%@'. should be one of: %@", json,
[[mapping allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]);
}
return value;
}
RCT_ARRAY_CONVERTER(RCTFontVariantDescriptor)
@end
@implementation RCTFont
@ -160,6 +203,7 @@ RCT_ENUM_CONVERTER(RCTFontStyle, (@{
size:(NSNumber *)size
weight:(NSString *)weight
style:(NSString *)style
variant:(NSArray<RCTFontVariantDescriptor *> *)variant
scaleMultiplier:(CGFloat)scaleMultiplier
{
// Defaults
@ -195,11 +239,15 @@ RCT_ENUM_CONVERTER(RCTFontStyle, (@{
isItalic = style ? [RCTConvert RCTFontStyle:style] : isItalic;
fontWeight = weight ? [RCTConvert RCTFontWeight:weight] : fontWeight;
BOOL didFindFont = NO;
// Handle system font as special case. This ensures that we preserve
// the specific metrics of the standard system font as closely as possible.
if ([familyName isEqual:defaultFontFamily] || [familyName isEqualToString:@"System"]) {
font = cachedSystemFont(fontSize, fontWeight);
if (font) {
didFindFont = YES;
if (isItalic || isCondensed) {
UIFontDescriptor *fontDescriptor = [font fontDescriptor];
UIFontDescriptorSymbolicTraits symbolicTraits = fontDescriptor.symbolicTraits;
@ -212,13 +260,12 @@ RCT_ENUM_CONVERTER(RCTFontStyle, (@{
fontDescriptor = [fontDescriptor fontDescriptorWithSymbolicTraits:symbolicTraits];
font = [UIFont fontWithDescriptor:fontDescriptor size:fontSize];
}
return font;
}
}
// Gracefully handle being given a font name rather than font family, for
// example: "Helvetica Light Oblique" rather than just "Helvetica".
if ([UIFont fontNamesForFamilyName:familyName].count == 0) {
if (!didFindFont && [UIFont fontNamesForFamilyName:familyName].count == 0) {
font = [UIFont fontWithName:familyName size:fontSize];
if (font) {
// It's actually a font name, not a font family name,
@ -241,7 +288,6 @@ RCT_ENUM_CONVERTER(RCTFontStyle, (@{
}
// Get the closest font that matches the given weight for the fontFamily
UIFont *bestMatch = font;
CGFloat closestWeight = INFINITY;
for (NSString *name in [UIFont fontNamesForFamilyName:familyName]) {
UIFont *match = [UIFont fontWithName:name size:fontSize];
@ -249,33 +295,42 @@ RCT_ENUM_CONVERTER(RCTFontStyle, (@{
isCondensed == isCondensedFont(match)) {
CGFloat testWeight = weightOfFont(match);
if (ABS(testWeight - fontWeight) < ABS(closestWeight - fontWeight)) {
bestMatch = match;
font = match;
closestWeight = testWeight;
}
}
}
return bestMatch;
// Apply font variants to font object
if (variant) {
NSArray *fontFeatures = [RCTConvert RCTFontVariantDescriptorArray:variant];
UIFontDescriptor *fontDescriptor = [font.fontDescriptor fontDescriptorByAddingAttributes:@{
UIFontDescriptorFeatureSettingsAttribute: fontFeatures
}];
font = [UIFont fontWithDescriptor:fontDescriptor size:fontSize];
}
return font;
}
+ (UIFont *)updateFont:(UIFont *)font withFamily:(NSString *)family
{
return [self updateFont:font withFamily:family size:nil weight:nil style:nil scaleMultiplier:1];
return [self updateFont:font withFamily:family size:nil weight:nil style:nil variant:nil scaleMultiplier:1];
}
+ (UIFont *)updateFont:(UIFont *)font withSize:(NSNumber *)size
{
return [self updateFont:font withFamily:nil size:size weight:nil style:nil scaleMultiplier:1];
return [self updateFont:font withFamily:nil size:size weight:nil style:nil variant:nil scaleMultiplier:1];
}
+ (UIFont *)updateFont:(UIFont *)font withWeight:(NSString *)weight
{
return [self updateFont:font withFamily:nil size:nil weight:weight style:nil scaleMultiplier:1];
return [self updateFont:font withFamily:nil size:nil weight:weight style:nil variant:nil scaleMultiplier:1];
}
+ (UIFont *)updateFont:(UIFont *)font withStyle:(NSString *)style
{
return [self updateFont:font withFamily:nil size:nil weight:nil style:style scaleMultiplier:1];
return [self updateFont:font withFamily:nil size:nil weight:nil style:style variant:nil scaleMultiplier:1];
}
@end