1// Copyright 2018-present 650 Industries. All rights reserved. 2 3#import <objc/runtime.h> 4#import <React/RCTFont.h> 5 6#import <ExpoModulesCore/EXDefines.h> 7#import <ExpoModulesCore/EXReactFontManager.h> 8#import <ExpoModulesCore/EXFontProcessorInterface.h> 9#import <ExpoModulesCore/EXFontManagerInterface.h> 10#import <ExpoModulesCore/EXAppLifecycleService.h> 11 12static dispatch_once_t initializeCurrentFontProcessorsOnce; 13 14static NSPointerArray *currentFontProcessors; 15 16@implementation UIFont (EXFontManager) 17 18+ (nullable UIFont *)EXfontWithName:(NSString *)name size:(CGFloat)fontSize 19{ 20 for (id<EXFontProcessorInterface> fontProcessor in currentFontProcessors) { 21 NSNumber *size = [NSNumber numberWithFloat:fontSize]; 22 UIFont *font = [fontProcessor updateFont:nil withFamily:name size:size weight:nil style:nil variant:nil scaleMultiplier:1]; 23 if (font) { 24 return font; 25 } 26 } 27 return [self EXfontWithName:name size:fontSize]; 28} 29 30@end 31 32@implementation RCTFont (EXReactFontManager) 33 34+ (UIFont *)EXUpdateFont:(UIFont *)uiFont 35 withFamily:(NSString *)family 36 size:(NSNumber *)size 37 weight:(NSString *)weight 38 style:(NSString *)style 39 variant:(NSArray<NSDictionary *> *)variant 40 scaleMultiplier:(CGFloat)scaleMultiplier 41{ 42 UIFont *font; 43 for (id<EXFontProcessorInterface> fontProcessor in currentFontProcessors) { 44 font = [fontProcessor updateFont:uiFont withFamily:family size:size weight:weight style:style variant:variant scaleMultiplier:scaleMultiplier]; 45 if (font) { 46 return font; 47 } 48 } 49 return [self EXUpdateFont:uiFont withFamily:family size:size weight:weight style:style variant:variant scaleMultiplier:scaleMultiplier]; 50} 51 52@end 53 54/** 55 * This class is responsible for allowing other modules to register as font processors in React Native. 56 * 57 * A font processor is an object conforming to EXFontProcessorInterface and is capable of 58 * providing an instance of UIFont for given (family, size, weight, style, variant, scaleMultiplier). 59 * 60 * To be able to hook into React Native's way of processing fonts we: 61 * - add a new class method to RCTFont, `EXUpdateFont:withFamily:size:weight:style:variant:scaleMultiplier` 62 * with EXReactFontManager category. 63 * - add a new static variable `currentFontProcessors` holding an array of... font processors. This variable 64 * is shared between the RCTFont's category and EXReactFontManager class. 65 * - when EXReactFontManager is initialized, we exchange implementations of RCTFont.updateFont... 66 * and RCTFont.EXUpdateFont... After the class initialized, which happens only once, calling `RCTFont updateFont` 67 * calls in fact implementation we've defined up here and calling `RCTFont EXUpdateFont` falls back 68 * to the default implementation. (This is why we call `[self EXUpdateFont]` at the end of that function, 69 * though it seems like an endless loop, in fact we dispatch to another implementation.) 70 * - When some module adds a font processor using EXFontManagerInterface, EXReactFontManager adds a weak pointer to it 71 * to currentFontProcessors array. 72 * - Implementation logic of `RCTFont.EXUpdateFont` uses current value of currentFontProcessors when processing arguments. 73 */ 74 75@interface EXReactFontManager () 76 77@property (nonatomic, strong) NSMutableSet *fontProcessors; 78 79@end 80 81@implementation EXReactFontManager 82 83EX_REGISTER_MODULE(); 84 85- (instancetype)init 86{ 87 if (self = [super init]) { 88 _fontProcessors = [NSMutableSet set]; 89 } 90 return self; 91} 92 93+ (const NSArray<Protocol *> *)exportedInterfaces 94{ 95 return @[@protocol(EXFontManagerInterface)]; 96} 97 98+ (void)initialize 99{ 100 dispatch_once(&initializeCurrentFontProcessorsOnce, ^{ 101 currentFontProcessors = [NSPointerArray weakObjectsPointerArray]; 102 }); 103 104 #ifdef RN_FABRIC_ENABLED 105 Class uiFont = [UIFont class]; 106 SEL uiUpdate = @selector(fontWithName:size:); 107 SEL exUpdate = @selector(EXfontWithName:size:); 108 109 method_exchangeImplementations(class_getClassMethod(uiFont, uiUpdate), 110 class_getClassMethod(uiFont, exUpdate)); 111 #else 112 Class rtcClass = [RCTFont class]; 113 SEL rtcUpdate = @selector(updateFont:withFamily:size:weight:style:variant:scaleMultiplier:); 114 SEL exUpdate = @selector(EXUpdateFont:withFamily:size:weight:style:variant:scaleMultiplier:); 115 116 method_exchangeImplementations(class_getClassMethod(rtcClass, rtcUpdate), 117 class_getClassMethod(rtcClass, exUpdate)); 118 #endif 119} 120 121# pragma mark - EXFontManager 122 123- (void)addFontProcessor:(id<EXFontProcessorInterface>)processor 124{ 125 [_fontProcessors addObject:processor]; 126 [currentFontProcessors compact]; 127 [currentFontProcessors addPointer:(__bridge void * _Nullable)(processor)]; 128} 129 130@end 131