diff --git a/BUCK b/BUCK index b315295c974f53..8c53560e33308f 100644 --- a/BUCK +++ b/BUCK @@ -376,6 +376,7 @@ rn_xplat_cxx_library2( "$SDKROOT/System/Library/Frameworks/CFNetwork.framework", "$SDKROOT/System/Library/Frameworks/CoreGraphics.framework", "$SDKROOT/System/Library/Frameworks/CoreLocation.framework", + "$SDKROOT/System/Library/Frameworks/CoreText.framework", "$SDKROOT/System/Library/Frameworks/Foundation.framework", "$SDKROOT/System/Library/Frameworks/MapKit.framework", "$SDKROOT/System/Library/Frameworks/QuartzCore.framework", diff --git a/React/Views/RCTFont.mm b/React/Views/RCTFont.mm index 93e1a803c7ca0f..7a8cbbb19abe01 100644 --- a/React/Views/RCTFont.mm +++ b/React/Views/RCTFont.mm @@ -11,18 +11,16 @@ #import -#import - typedef CGFloat RCTFontWeight; static RCTFontWeight weightOfFont(UIFont *font) { - static NSArray *fontNames; - static NSArray *fontWeights; + static NSArray *weightSuffixes; + static NSArray *fontWeights; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // We use two arrays instead of one map because // the order is important for suffix matching. - fontNames = @[ + weightSuffixes = @[ @"normal", @"ultralight", @"thin", @@ -54,28 +52,29 @@ static RCTFontWeight weightOfFont(UIFont *font) ]; }); - for (NSInteger i = 0; i < 0 || i < (unsigned)fontNames.count; i++) { - if ([font.fontName.lowercaseString hasSuffix:fontNames[i]]) { - return (RCTFontWeight)[fontWeights[i] doubleValue]; + NSString *fontName = font.fontName; + NSInteger i = 0; + for (NSString *suffix in weightSuffixes) { + // CFStringFind is much faster than any variant of rangeOfString: because it does not use a locale. + auto options = kCFCompareCaseInsensitive | kCFCompareAnchored | kCFCompareBackwards; + if (CFStringFind((CFStringRef)fontName, (CFStringRef)suffix, options).location != kCFNotFound) { + return (RCTFontWeight)fontWeights[i].doubleValue; } + i++; } - NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute]; + auto traits = (__bridge_transfer NSDictionary *)CTFontCopyTraits((CTFontRef)font); return (RCTFontWeight)[traits[UIFontWeightTrait] doubleValue]; } static BOOL isItalicFont(UIFont *font) { - NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute]; - UIFontDescriptorSymbolicTraits symbolicTraits = [traits[UIFontSymbolicTrait] unsignedIntValue]; - return (symbolicTraits & UIFontDescriptorTraitItalic) != 0; + return (CTFontGetSymbolicTraits((CTFontRef)font) & kCTFontTraitItalic) != 0; } static BOOL isCondensedFont(UIFont *font) { - NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute]; - UIFontDescriptorSymbolicTraits symbolicTraits = [traits[UIFontSymbolicTrait] unsignedIntValue]; - return (symbolicTraits & UIFontDescriptorTraitCondensed) != 0; + return (CTFontGetSymbolicTraits((CTFontRef)font) & kCTFontTraitCondensed) != 0; } static RCTFontHandler defaultFontHandler; @@ -130,18 +129,16 @@ static inline BOOL CompareFontWeights(UIFontWeight firstWeight, UIFontWeight sec static UIFont *cachedSystemFont(CGFloat size, RCTFontWeight weight) { - static NSCache *fontCache; - static std::mutex *fontCacheMutex = new std::mutex; - - NSString *cacheKey = [NSString stringWithFormat:@"%.1f/%.2f", size, weight]; - UIFont *font; - { - std::lock_guard lock(*fontCacheMutex); - if (!fontCache) { - fontCache = [NSCache new]; - } - font = [fontCache objectForKey:cacheKey]; - } + static NSCache *fontCache = [NSCache new]; + + struct __attribute__((__packed__)) CacheKey { + CGFloat size; + RCTFontWeight weight; + }; + + CacheKey key{size, weight}; + NSValue *cacheKey = [[NSValue alloc] initWithBytes:&key objCType:@encode(CacheKey)]; + UIFont *font = [fontCache objectForKey:cacheKey]; if (!font) { if (defaultFontHandler) { @@ -151,15 +148,36 @@ static inline BOOL CompareFontWeights(UIFontWeight firstWeight, UIFontWeight sec font = [UIFont systemFontOfSize:size weight:weight]; } - { - std::lock_guard lock(*fontCacheMutex); - [fontCache setObject:font forKey:cacheKey]; - } + [fontCache setObject:font forKey:cacheKey]; } return font; } +// Caching wrapper around expensive +[UIFont fontNamesForFamilyName:] +static NSArray *fontNamesForFamilyName(NSString *familyName) +{ + static NSCache *> *cache; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + cache = [NSCache new]; + [NSNotificationCenter.defaultCenter + addObserverForName:(NSNotificationName)kCTFontManagerRegisteredFontsChangedNotification + object:nil + queue:nil + usingBlock:^(NSNotification *) { + [cache removeAllObjects]; + }]; + }); + + auto names = [cache objectForKey:familyName]; + if (!names) { + names = [UIFont fontNamesForFamilyName:familyName]; + [cache setObject:names forKey:familyName]; + } + return names; +} + @implementation RCTConvert (RCTFont) + (UIFont *)UIFont:(id)json @@ -315,7 +333,7 @@ + (UIFont *)updateFont:(UIFont *)font // Gracefully handle being given a font name rather than font family, for // example: "Helvetica Light Oblique" rather than just "Helvetica". - if (!didFindFont && [UIFont fontNamesForFamilyName:familyName].count == 0) { + if (!didFindFont && fontNamesForFamilyName(familyName).count == 0) { font = [UIFont fontWithName:familyName size:fontSize]; if (font) { // It's actually a font name, not a font family name, @@ -339,7 +357,8 @@ + (UIFont *)updateFont:(UIFont *)font // Get the closest font that matches the given weight for the fontFamily CGFloat closestWeight = INFINITY; - for (NSString *name in [UIFont fontNamesForFamilyName:familyName]) { + NSArray *names = fontNamesForFamilyName(familyName); + for (NSString *name in names) { UIFont *match = [UIFont fontWithName:name size:fontSize]; if (isItalic == isItalicFont(match) && isCondensed == isCondensedFont(match)) { CGFloat testWeight = weightOfFont(match); @@ -352,11 +371,8 @@ + (UIFont *)updateFont:(UIFont *)font // If we still don't have a match at least return the first font in the fontFamily // This is to support built-in font Zapfino and other custom single font families like Impact - if (!font) { - NSArray *names = [UIFont fontNamesForFamilyName:familyName]; - if (names.count > 0) { - font = [UIFont fontWithName:names[0] size:fontSize]; - } + if (!font && names.count > 0) { + font = [UIFont fontWithName:names[0] size:fontSize]; } // Apply font variants to font object diff --git a/packages/rn-tester/RNTesterUnitTests/RCTFontTests.m b/packages/rn-tester/RNTesterUnitTests/RCTFontTests.m index bb8f19b0383535..650b14f5151484 100644 --- a/packages/rn-tester/RNTesterUnitTests/RCTFontTests.m +++ b/packages/rn-tester/RNTesterUnitTests/RCTFontTests.m @@ -20,7 +20,7 @@ @implementation RCTFontTests // will be different objects, but the same font, so this macro now explicitly // checks that fontName (which includes the style) and pointSize are equal. #define RCTAssertEqualFonts(font1, font2) { \ - XCTAssertTrue([font1.fontName isEqualToString:font2.fontName]); \ + XCTAssertEqualObjects(font1.fontName, font2.fontName); \ XCTAssertEqual(font1.pointSize,font2.pointSize); \ }