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