123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393 |
- /*
- * 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 "RCTFont.h"
- #import "RCTAssert.h"
- #import "RCTLog.h"
- #import <CoreText/CoreText.h>
- #import <mutex>
- typedef CGFloat RCTFontWeight;
- static RCTFontWeight weightOfFont(UIFont *font)
- {
- static NSArray *fontNames;
- 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 = @[
- @"normal",
- @"ultralight",
- @"thin",
- @"light",
- @"regular",
- @"medium",
- @"semibold",
- @"demibold",
- @"extrabold",
- @"ultrabold",
- @"bold",
- @"heavy",
- @"black"
- ];
- fontWeights = @[
- @(UIFontWeightRegular),
- @(UIFontWeightUltraLight),
- @(UIFontWeightThin),
- @(UIFontWeightLight),
- @(UIFontWeightRegular),
- @(UIFontWeightMedium),
- @(UIFontWeightSemibold),
- @(UIFontWeightSemibold),
- @(UIFontWeightHeavy),
- @(UIFontWeightHeavy),
- @(UIFontWeightBold),
- @(UIFontWeightHeavy),
- @(UIFontWeightBlack)
- ];
- });
- for (NSInteger i = 0; i < 0 || i < (unsigned)fontNames.count; i++) {
- if ([font.fontName.lowercaseString hasSuffix:fontNames[i]]) {
- return (RCTFontWeight)[fontWeights[i] doubleValue];
- }
- }
- NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute];
- 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;
- }
- static BOOL isCondensedFont(UIFont *font)
- {
- NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute];
- UIFontDescriptorSymbolicTraits symbolicTraits = [traits[UIFontSymbolicTrait] unsignedIntValue];
- return (symbolicTraits & UIFontDescriptorTraitCondensed) != 0;
- }
- static RCTFontHandler defaultFontHandler;
- void RCTSetDefaultFontHandler(RCTFontHandler handler)
- {
- defaultFontHandler = handler;
- }
- BOOL RCTHasFontHandlerSet()
- {
- return defaultFontHandler != nil;
- }
- // We pass a string description of the font weight to the defaultFontHandler because UIFontWeight
- // is not defined pre-iOS 8.2.
- // Furthermore, UIFontWeight's are lossy floats, so we must use an inexact compare to figure out
- // which one we actually have.
- static inline BOOL CompareFontWeights(UIFontWeight firstWeight, UIFontWeight secondWeight)
- {
- #if CGFLOAT_IS_DOUBLE
- return fabs(firstWeight - secondWeight) < 0.01;
- #else
- return fabsf(firstWeight - secondWeight) < 0.01;
- #endif
- }
- static NSString *FontWeightDescriptionFromUIFontWeight(UIFontWeight fontWeight)
- {
- if (CompareFontWeights(fontWeight, UIFontWeightUltraLight)) {
- return @"ultralight";
- } else if (CompareFontWeights(fontWeight, UIFontWeightThin)) {
- return @"thin";
- } else if (CompareFontWeights(fontWeight, UIFontWeightLight)) {
- return @"light";
- } else if (CompareFontWeights(fontWeight, UIFontWeightRegular)) {
- return @"regular";
- } else if (CompareFontWeights(fontWeight, UIFontWeightMedium)) {
- return @"medium";
- } else if (CompareFontWeights(fontWeight, UIFontWeightSemibold)) {
- return @"semibold";
- } else if (CompareFontWeights(fontWeight, UIFontWeightBold)) {
- return @"bold";
- } else if (CompareFontWeights(fontWeight, UIFontWeightHeavy)) {
- return @"heavy";
- } else if (CompareFontWeights(fontWeight, UIFontWeightBlack)) {
- return @"black";
- }
- RCTAssert(NO, @"Unknown UIFontWeight passed in: %f", fontWeight);
- return @"regular";
- }
- 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<std::mutex> lock(*fontCacheMutex);
- if (!fontCache) {
- fontCache = [NSCache new];
- }
- font = [fontCache objectForKey:cacheKey];
- }
- if (!font) {
- if (defaultFontHandler) {
- NSString *fontWeightDescription = FontWeightDescriptionFromUIFontWeight(weight);
- font = defaultFontHandler(size, fontWeightDescription);
- } else {
- font = [UIFont systemFontOfSize:size weight:weight];
- }
- {
- std::lock_guard<std::mutex> lock(*fontCacheMutex);
- [fontCache setObject:font forKey:cacheKey];
- }
- }
- return font;
- }
- @implementation RCTConvert (RCTFont)
- + (UIFont *)UIFont:(id)json
- {
- json = [self NSDictionary:json];
- return [RCTFont updateFont:nil
- withFamily:[RCTConvert NSString:json[@"fontFamily"]]
- size:[RCTConvert NSNumber:json[@"fontSize"]]
- weight:[RCTConvert NSString:json[@"fontWeight"]]
- style:[RCTConvert NSString:json[@"fontStyle"]]
- variant:[RCTConvert NSStringArray:json[@"fontVariant"]]
- scaleMultiplier:1];
- }
- RCT_ENUM_CONVERTER(
- RCTFontWeight,
- (@{
- @"normal" : @(UIFontWeightRegular),
- @"bold" : @(UIFontWeightBold),
- @"100" : @(UIFontWeightUltraLight),
- @"200" : @(UIFontWeightThin),
- @"300" : @(UIFontWeightLight),
- @"400" : @(UIFontWeightRegular),
- @"500" : @(UIFontWeightMedium),
- @"600" : @(UIFontWeightSemibold),
- @"700" : @(UIFontWeightBold),
- @"800" : @(UIFontWeightHeavy),
- @"900" : @(UIFontWeightBlack),
- }),
- UIFontWeightRegular,
- doubleValue)
- typedef BOOL RCTFontStyle;
- RCT_ENUM_CONVERTER(
- RCTFontStyle,
- (@{
- @"normal" : @NO,
- @"italic" : @YES,
- @"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
- + (UIFont *)updateFont:(UIFont *)font
- withFamily:(NSString *)family
- size:(NSNumber *)size
- weight:(NSString *)weight
- style:(NSString *)style
- variant:(NSArray<RCTFontVariantDescriptor *> *)variant
- scaleMultiplier:(CGFloat)scaleMultiplier
- {
- // Defaults
- static NSString *defaultFontFamily;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- defaultFontFamily = [UIFont systemFontOfSize:14].familyName;
- });
- const RCTFontWeight defaultFontWeight = UIFontWeightRegular;
- const CGFloat defaultFontSize = 14;
- // Initialize properties to defaults
- CGFloat fontSize = defaultFontSize;
- RCTFontWeight fontWeight = defaultFontWeight;
- NSString *familyName = defaultFontFamily;
- BOOL isItalic = NO;
- BOOL isCondensed = NO;
- if (font) {
- familyName = font.familyName ?: defaultFontFamily;
- fontSize = font.pointSize ?: defaultFontSize;
- fontWeight = weightOfFont(font);
- isItalic = isItalicFont(font);
- isCondensed = isCondensedFont(font);
- }
- // Get font attributes
- fontSize = [RCTConvert CGFloat:size] ?: fontSize;
- if (scaleMultiplier > 0.0 && scaleMultiplier != 1.0) {
- fontSize = round(fontSize * scaleMultiplier);
- }
- familyName = [RCTConvert NSString:family] ?: familyName;
- 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;
- if (isItalic) {
- symbolicTraits |= UIFontDescriptorTraitItalic;
- }
- if (isCondensed) {
- symbolicTraits |= UIFontDescriptorTraitCondensed;
- }
- fontDescriptor = [fontDescriptor fontDescriptorWithSymbolicTraits:symbolicTraits];
- font = [UIFont fontWithDescriptor:fontDescriptor size:fontSize];
- }
- }
- }
- // 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) {
- font = [UIFont fontWithName:familyName size:fontSize];
- if (font) {
- // It's actually a font name, not a font family name,
- // but we'll do what was meant, not what was said.
- familyName = font.familyName;
- fontWeight = weight ? fontWeight : weightOfFont(font);
- isItalic = style ? isItalic : isItalicFont(font);
- isCondensed = isCondensedFont(font);
- } else {
- // Not a valid font or family
- RCTLogError(@"Unrecognized font family '%@'", familyName);
- if ([UIFont respondsToSelector:@selector(systemFontOfSize:weight:)]) {
- font = [UIFont systemFontOfSize:fontSize weight:fontWeight];
- } else if (fontWeight > UIFontWeightRegular) {
- font = [UIFont boldSystemFontOfSize:fontSize];
- } else {
- font = [UIFont systemFontOfSize:fontSize];
- }
- }
- }
- // Get the closest font that matches the given weight for the fontFamily
- CGFloat closestWeight = INFINITY;
- for (NSString *name in [UIFont fontNamesForFamilyName:familyName]) {
- UIFont *match = [UIFont fontWithName:name size:fontSize];
- if (isItalic == isItalicFont(match) && isCondensed == isCondensedFont(match)) {
- CGFloat testWeight = weightOfFont(match);
- if (ABS(testWeight - fontWeight) < ABS(closestWeight - fontWeight)) {
- font = match;
- closestWeight = testWeight;
- }
- }
- }
- // 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];
- }
- }
- // 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 variant: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];
- }
- + (UIFont *)updateFont:(UIFont *)font withWeight:(NSString *)weight
- {
- 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 variant:nil scaleMultiplier:1];
- }
- @end
|