RCTFont.mm 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. /*
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. #import "RCTFont.h"
  8. #import "RCTAssert.h"
  9. #import "RCTLog.h"
  10. #import <CoreText/CoreText.h>
  11. #import <mutex>
  12. typedef CGFloat RCTFontWeight;
  13. static RCTFontWeight weightOfFont(UIFont *font)
  14. {
  15. static NSArray *fontNames;
  16. static NSArray *fontWeights;
  17. static dispatch_once_t onceToken;
  18. dispatch_once(&onceToken, ^{
  19. // We use two arrays instead of one map because
  20. // the order is important for suffix matching.
  21. fontNames = @[
  22. @"normal",
  23. @"ultralight",
  24. @"thin",
  25. @"light",
  26. @"regular",
  27. @"medium",
  28. @"semibold",
  29. @"demibold",
  30. @"extrabold",
  31. @"ultrabold",
  32. @"bold",
  33. @"heavy",
  34. @"black"
  35. ];
  36. fontWeights = @[
  37. @(UIFontWeightRegular),
  38. @(UIFontWeightUltraLight),
  39. @(UIFontWeightThin),
  40. @(UIFontWeightLight),
  41. @(UIFontWeightRegular),
  42. @(UIFontWeightMedium),
  43. @(UIFontWeightSemibold),
  44. @(UIFontWeightSemibold),
  45. @(UIFontWeightHeavy),
  46. @(UIFontWeightHeavy),
  47. @(UIFontWeightBold),
  48. @(UIFontWeightHeavy),
  49. @(UIFontWeightBlack)
  50. ];
  51. });
  52. for (NSInteger i = 0; i < 0 || i < (unsigned)fontNames.count; i++) {
  53. if ([font.fontName.lowercaseString hasSuffix:fontNames[i]]) {
  54. return (RCTFontWeight)[fontWeights[i] doubleValue];
  55. }
  56. }
  57. NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute];
  58. return (RCTFontWeight)[traits[UIFontWeightTrait] doubleValue];
  59. }
  60. static BOOL isItalicFont(UIFont *font)
  61. {
  62. NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute];
  63. UIFontDescriptorSymbolicTraits symbolicTraits = [traits[UIFontSymbolicTrait] unsignedIntValue];
  64. return (symbolicTraits & UIFontDescriptorTraitItalic) != 0;
  65. }
  66. static BOOL isCondensedFont(UIFont *font)
  67. {
  68. NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute];
  69. UIFontDescriptorSymbolicTraits symbolicTraits = [traits[UIFontSymbolicTrait] unsignedIntValue];
  70. return (symbolicTraits & UIFontDescriptorTraitCondensed) != 0;
  71. }
  72. static RCTFontHandler defaultFontHandler;
  73. void RCTSetDefaultFontHandler(RCTFontHandler handler)
  74. {
  75. defaultFontHandler = handler;
  76. }
  77. BOOL RCTHasFontHandlerSet()
  78. {
  79. return defaultFontHandler != nil;
  80. }
  81. // We pass a string description of the font weight to the defaultFontHandler because UIFontWeight
  82. // is not defined pre-iOS 8.2.
  83. // Furthermore, UIFontWeight's are lossy floats, so we must use an inexact compare to figure out
  84. // which one we actually have.
  85. static inline BOOL CompareFontWeights(UIFontWeight firstWeight, UIFontWeight secondWeight)
  86. {
  87. #if CGFLOAT_IS_DOUBLE
  88. return fabs(firstWeight - secondWeight) < 0.01;
  89. #else
  90. return fabsf(firstWeight - secondWeight) < 0.01;
  91. #endif
  92. }
  93. static NSString *FontWeightDescriptionFromUIFontWeight(UIFontWeight fontWeight)
  94. {
  95. if (CompareFontWeights(fontWeight, UIFontWeightUltraLight)) {
  96. return @"ultralight";
  97. } else if (CompareFontWeights(fontWeight, UIFontWeightThin)) {
  98. return @"thin";
  99. } else if (CompareFontWeights(fontWeight, UIFontWeightLight)) {
  100. return @"light";
  101. } else if (CompareFontWeights(fontWeight, UIFontWeightRegular)) {
  102. return @"regular";
  103. } else if (CompareFontWeights(fontWeight, UIFontWeightMedium)) {
  104. return @"medium";
  105. } else if (CompareFontWeights(fontWeight, UIFontWeightSemibold)) {
  106. return @"semibold";
  107. } else if (CompareFontWeights(fontWeight, UIFontWeightBold)) {
  108. return @"bold";
  109. } else if (CompareFontWeights(fontWeight, UIFontWeightHeavy)) {
  110. return @"heavy";
  111. } else if (CompareFontWeights(fontWeight, UIFontWeightBlack)) {
  112. return @"black";
  113. }
  114. RCTAssert(NO, @"Unknown UIFontWeight passed in: %f", fontWeight);
  115. return @"regular";
  116. }
  117. static UIFont *cachedSystemFont(CGFloat size, RCTFontWeight weight)
  118. {
  119. static NSCache *fontCache;
  120. static std::mutex *fontCacheMutex = new std::mutex;
  121. NSString *cacheKey = [NSString stringWithFormat:@"%.1f/%.2f", size, weight];
  122. UIFont *font;
  123. {
  124. std::lock_guard<std::mutex> lock(*fontCacheMutex);
  125. if (!fontCache) {
  126. fontCache = [NSCache new];
  127. }
  128. font = [fontCache objectForKey:cacheKey];
  129. }
  130. if (!font) {
  131. if (defaultFontHandler) {
  132. NSString *fontWeightDescription = FontWeightDescriptionFromUIFontWeight(weight);
  133. font = defaultFontHandler(size, fontWeightDescription);
  134. } else {
  135. font = [UIFont systemFontOfSize:size weight:weight];
  136. }
  137. {
  138. std::lock_guard<std::mutex> lock(*fontCacheMutex);
  139. [fontCache setObject:font forKey:cacheKey];
  140. }
  141. }
  142. return font;
  143. }
  144. @implementation RCTConvert (RCTFont)
  145. + (UIFont *)UIFont:(id)json
  146. {
  147. json = [self NSDictionary:json];
  148. return [RCTFont updateFont:nil
  149. withFamily:[RCTConvert NSString:json[@"fontFamily"]]
  150. size:[RCTConvert NSNumber:json[@"fontSize"]]
  151. weight:[RCTConvert NSString:json[@"fontWeight"]]
  152. style:[RCTConvert NSString:json[@"fontStyle"]]
  153. variant:[RCTConvert NSStringArray:json[@"fontVariant"]]
  154. scaleMultiplier:1];
  155. }
  156. RCT_ENUM_CONVERTER(
  157. RCTFontWeight,
  158. (@{
  159. @"normal" : @(UIFontWeightRegular),
  160. @"bold" : @(UIFontWeightBold),
  161. @"100" : @(UIFontWeightUltraLight),
  162. @"200" : @(UIFontWeightThin),
  163. @"300" : @(UIFontWeightLight),
  164. @"400" : @(UIFontWeightRegular),
  165. @"500" : @(UIFontWeightMedium),
  166. @"600" : @(UIFontWeightSemibold),
  167. @"700" : @(UIFontWeightBold),
  168. @"800" : @(UIFontWeightHeavy),
  169. @"900" : @(UIFontWeightBlack),
  170. }),
  171. UIFontWeightRegular,
  172. doubleValue)
  173. typedef BOOL RCTFontStyle;
  174. RCT_ENUM_CONVERTER(
  175. RCTFontStyle,
  176. (@{
  177. @"normal" : @NO,
  178. @"italic" : @YES,
  179. @"oblique" : @YES,
  180. }),
  181. NO,
  182. boolValue)
  183. typedef NSDictionary RCTFontVariantDescriptor;
  184. + (RCTFontVariantDescriptor *)RCTFontVariantDescriptor:(id)json
  185. {
  186. static NSDictionary *mapping;
  187. static dispatch_once_t onceToken;
  188. dispatch_once(&onceToken, ^{
  189. mapping = @{
  190. @"small-caps" : @{
  191. UIFontFeatureTypeIdentifierKey : @(kLowerCaseType),
  192. UIFontFeatureSelectorIdentifierKey : @(kLowerCaseSmallCapsSelector),
  193. },
  194. @"oldstyle-nums" : @{
  195. UIFontFeatureTypeIdentifierKey : @(kNumberCaseType),
  196. UIFontFeatureSelectorIdentifierKey : @(kLowerCaseNumbersSelector),
  197. },
  198. @"lining-nums" : @{
  199. UIFontFeatureTypeIdentifierKey : @(kNumberCaseType),
  200. UIFontFeatureSelectorIdentifierKey : @(kUpperCaseNumbersSelector),
  201. },
  202. @"tabular-nums" : @{
  203. UIFontFeatureTypeIdentifierKey : @(kNumberSpacingType),
  204. UIFontFeatureSelectorIdentifierKey : @(kMonospacedNumbersSelector),
  205. },
  206. @"proportional-nums" : @{
  207. UIFontFeatureTypeIdentifierKey : @(kNumberSpacingType),
  208. UIFontFeatureSelectorIdentifierKey : @(kProportionalNumbersSelector),
  209. },
  210. };
  211. });
  212. RCTFontVariantDescriptor *value = mapping[json];
  213. if (RCT_DEBUG && !value && [json description].length > 0) {
  214. RCTLogError(
  215. @"Invalid RCTFontVariantDescriptor '%@'. should be one of: %@",
  216. json,
  217. [[mapping allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]);
  218. }
  219. return value;
  220. }
  221. RCT_ARRAY_CONVERTER(RCTFontVariantDescriptor)
  222. @end
  223. @implementation RCTFont
  224. + (UIFont *)updateFont:(UIFont *)font
  225. withFamily:(NSString *)family
  226. size:(NSNumber *)size
  227. weight:(NSString *)weight
  228. style:(NSString *)style
  229. variant:(NSArray<RCTFontVariantDescriptor *> *)variant
  230. scaleMultiplier:(CGFloat)scaleMultiplier
  231. {
  232. // Defaults
  233. static NSString *defaultFontFamily;
  234. static dispatch_once_t onceToken;
  235. dispatch_once(&onceToken, ^{
  236. defaultFontFamily = [UIFont systemFontOfSize:14].familyName;
  237. });
  238. const RCTFontWeight defaultFontWeight = UIFontWeightRegular;
  239. const CGFloat defaultFontSize = 14;
  240. // Initialize properties to defaults
  241. CGFloat fontSize = defaultFontSize;
  242. RCTFontWeight fontWeight = defaultFontWeight;
  243. NSString *familyName = defaultFontFamily;
  244. BOOL isItalic = NO;
  245. BOOL isCondensed = NO;
  246. if (font) {
  247. familyName = font.familyName ?: defaultFontFamily;
  248. fontSize = font.pointSize ?: defaultFontSize;
  249. fontWeight = weightOfFont(font);
  250. isItalic = isItalicFont(font);
  251. isCondensed = isCondensedFont(font);
  252. }
  253. // Get font attributes
  254. fontSize = [RCTConvert CGFloat:size] ?: fontSize;
  255. if (scaleMultiplier > 0.0 && scaleMultiplier != 1.0) {
  256. fontSize = round(fontSize * scaleMultiplier);
  257. }
  258. familyName = [RCTConvert NSString:family] ?: familyName;
  259. isItalic = style ? [RCTConvert RCTFontStyle:style] : isItalic;
  260. fontWeight = weight ? [RCTConvert RCTFontWeight:weight] : fontWeight;
  261. BOOL didFindFont = NO;
  262. // Handle system font as special case. This ensures that we preserve
  263. // the specific metrics of the standard system font as closely as possible.
  264. if ([familyName isEqual:defaultFontFamily] || [familyName isEqualToString:@"System"]) {
  265. font = cachedSystemFont(fontSize, fontWeight);
  266. if (font) {
  267. didFindFont = YES;
  268. if (isItalic || isCondensed) {
  269. UIFontDescriptor *fontDescriptor = [font fontDescriptor];
  270. UIFontDescriptorSymbolicTraits symbolicTraits = fontDescriptor.symbolicTraits;
  271. if (isItalic) {
  272. symbolicTraits |= UIFontDescriptorTraitItalic;
  273. }
  274. if (isCondensed) {
  275. symbolicTraits |= UIFontDescriptorTraitCondensed;
  276. }
  277. fontDescriptor = [fontDescriptor fontDescriptorWithSymbolicTraits:symbolicTraits];
  278. font = [UIFont fontWithDescriptor:fontDescriptor size:fontSize];
  279. }
  280. }
  281. }
  282. // Gracefully handle being given a font name rather than font family, for
  283. // example: "Helvetica Light Oblique" rather than just "Helvetica".
  284. if (!didFindFont && [UIFont fontNamesForFamilyName:familyName].count == 0) {
  285. font = [UIFont fontWithName:familyName size:fontSize];
  286. if (font) {
  287. // It's actually a font name, not a font family name,
  288. // but we'll do what was meant, not what was said.
  289. familyName = font.familyName;
  290. fontWeight = weight ? fontWeight : weightOfFont(font);
  291. isItalic = style ? isItalic : isItalicFont(font);
  292. isCondensed = isCondensedFont(font);
  293. } else {
  294. // Not a valid font or family
  295. RCTLogError(@"Unrecognized font family '%@'", familyName);
  296. if ([UIFont respondsToSelector:@selector(systemFontOfSize:weight:)]) {
  297. font = [UIFont systemFontOfSize:fontSize weight:fontWeight];
  298. } else if (fontWeight > UIFontWeightRegular) {
  299. font = [UIFont boldSystemFontOfSize:fontSize];
  300. } else {
  301. font = [UIFont systemFontOfSize:fontSize];
  302. }
  303. }
  304. }
  305. // Get the closest font that matches the given weight for the fontFamily
  306. CGFloat closestWeight = INFINITY;
  307. for (NSString *name in [UIFont fontNamesForFamilyName:familyName]) {
  308. UIFont *match = [UIFont fontWithName:name size:fontSize];
  309. if (isItalic == isItalicFont(match) && isCondensed == isCondensedFont(match)) {
  310. CGFloat testWeight = weightOfFont(match);
  311. if (ABS(testWeight - fontWeight) < ABS(closestWeight - fontWeight)) {
  312. font = match;
  313. closestWeight = testWeight;
  314. }
  315. }
  316. }
  317. // If we still don't have a match at least return the first font in the fontFamily
  318. // This is to support built-in font Zapfino and other custom single font families like Impact
  319. if (!font) {
  320. NSArray *names = [UIFont fontNamesForFamilyName:familyName];
  321. if (names.count > 0) {
  322. font = [UIFont fontWithName:names[0] size:fontSize];
  323. }
  324. }
  325. // Apply font variants to font object
  326. if (variant) {
  327. NSArray *fontFeatures = [RCTConvert RCTFontVariantDescriptorArray:variant];
  328. UIFontDescriptor *fontDescriptor = [font.fontDescriptor
  329. fontDescriptorByAddingAttributes:@{UIFontDescriptorFeatureSettingsAttribute : fontFeatures}];
  330. font = [UIFont fontWithDescriptor:fontDescriptor size:fontSize];
  331. }
  332. return font;
  333. }
  334. + (UIFont *)updateFont:(UIFont *)font withFamily:(NSString *)family
  335. {
  336. return [self updateFont:font withFamily:family size:nil weight:nil style:nil variant:nil scaleMultiplier:1];
  337. }
  338. + (UIFont *)updateFont:(UIFont *)font withSize:(NSNumber *)size
  339. {
  340. return [self updateFont:font withFamily:nil size:size weight:nil style:nil variant:nil scaleMultiplier:1];
  341. }
  342. + (UIFont *)updateFont:(UIFont *)font withWeight:(NSString *)weight
  343. {
  344. return [self updateFont:font withFamily:nil size:nil weight:weight style:nil variant:nil scaleMultiplier:1];
  345. }
  346. + (UIFont *)updateFont:(UIFont *)font withStyle:(NSString *)style
  347. {
  348. return [self updateFont:font withFamily:nil size:nil weight:nil style:style variant:nil scaleMultiplier:1];
  349. }
  350. @end