From f951da912dd8b4dc2b0d674f8f37f9d982a03c48 Mon Sep 17 00:00:00 2001 From: Jacob Parker Date: Tue, 9 Aug 2016 08:42:58 -0700 Subject: [PATCH] =?UTF-8?q?Add=20iOS=20support=20for=20CSS=20property=20fo?= =?UTF-8?q?nt-variant,=20accepting=20tabular-nums=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../UIExplorerUnitTests/RCTFontTests.m | 23 ++++++ Examples/UIExplorer/js/TextExample.ios.js | 27 +++++++ Libraries/Text/RCTShadowText.h | 1 + Libraries/Text/RCTShadowText.m | 16 ++-- Libraries/Text/RCTTextManager.m | 1 + Libraries/Text/TextStylePropTypes.js | 9 +++ React/Base/RCTConvert.h | 2 +- React/Base/RCTConvert.m | 2 + React/Views/RCTFont.h | 1 + React/Views/RCTFont.mm | 73 ++++++++++++++++--- 10 files changed, 139 insertions(+), 16 deletions(-) diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTFontTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTFontTests.m index 6341eced1c..749c181231 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTFontTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTFontTests.m @@ -16,6 +16,8 @@ #import "RCTFont.h" +#import + @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 { { diff --git a/Examples/UIExplorer/js/TextExample.ios.js b/Examples/UIExplorer/js/TextExample.ios.js index eb106958aa..755594274a 100644 --- a/Examples/UIExplorer/js/TextExample.ios.js +++ b/Examples/UIExplorer/js/TextExample.ios.js @@ -465,6 +465,33 @@ exports.examples = [ ); }, +}, { + title: 'Font variants', + render: function() { + return ( + + + Small Caps{'\n'} + + + Old Style nums 0123456789{'\n'} + + + Lining nums 0123456789{'\n'} + + + Tabular nums{'\n'} + 1111{'\n'} + 2222{'\n'} + + + Proportional nums{'\n'} + 1111{'\n'} + 2222{'\n'} + + + ); + }, }]; var styles = StyleSheet.create({ diff --git a/Libraries/Text/RCTShadowText.h b/Libraries/Text/RCTShadowText.h index 851970aa94..7468a2ad4a 100644 --- a/Libraries/Text/RCTShadowText.h +++ b/Libraries/Text/RCTShadowText.h @@ -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; diff --git a/Libraries/Text/RCTShadowText.m b/Libraries/Text/RCTShadowText.m index 1d2b25669c..f177ad78d1 100644 --- a/Libraries/Text/RCTShadowText.m +++ b/Libraries/Text/RCTShadowText.m @@ -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) diff --git a/Libraries/Text/RCTTextManager.m b/Libraries/Text/RCTTextManager.m index 6fc8d0ae79..adc8dea8ab 100644 --- a/Libraries/Text/RCTTextManager.m +++ b/Libraries/Text/RCTTextManager.m @@ -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) diff --git a/Libraries/Text/TextStylePropTypes.js b/Libraries/Text/TextStylePropTypes.js index daa3094a63..8705506b38 100644 --- a/Libraries/Text/TextStylePropTypes.js +++ b/Libraries/Text/TextStylePropTypes.js @@ -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} ), diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h index 80624d2fee..68a3e25dcd 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -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 *)type##Array:(id)json \ ++ (NSArray *)type##Array:(id)json \ { \ return RCTConvertArrayValue(@selector(type:), json); \ } diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index c89caf8368..00cdd21340 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -7,6 +7,8 @@ * of patent rights can be found in the PATENTS file in the same directory. */ +#import + #import "RCTConvert.h" #import diff --git a/React/Views/RCTFont.h b/React/Views/RCTFont.h index 6994ced442..e2f5405bc0 100644 --- a/React/Views/RCTFont.h +++ b/React/Views/RCTFont.h @@ -23,6 +23,7 @@ size:(NSNumber *)size weight:(NSString *)weight style:(NSString *)style + variant:(NSArray *)variant scaleMultiplier:(CGFloat)scaleMultiplier; + (UIFont *)updateFont:(UIFont *)font withFamily:(NSString *)family; diff --git a/React/Views/RCTFont.mm b/React/Views/RCTFont.mm index 06a4d88bcf..ad305f31d3 100644 --- a/React/Views/RCTFont.mm +++ b/React/Views/RCTFont.mm @@ -8,6 +8,9 @@ */ #import "RCTFont.h" +#import "RCTLog.h" + +#import #import @@ -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 *)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