1+ #import " RNTextSize.h"
2+
13#if __has_include(<React/RCTConvert.h>)
24#import < React/RCTConvert.h>
35#import < React/RCTFont.h>
1113#endif
1214
1315#import < CoreText/CoreText.h>
14- #import " RNTextSize.h"
15-
16- #define _DEBUG 1
1716
1817static NSString *const E_MISSING_TEXT = @" E_MISSING_TEXT" ;
1918static NSString *const E_INVALID_FONT_SPEC = @" E_INVALID_FONT_SPEC" ;
@@ -59,19 +58,6 @@ - (dispatch_queue_t)methodQueue {
5958 return dispatch_get_main_queue ();
6059}
6160
62- /*
63- Is RN exposing its default font size? Try to get a UIFont here warnings
64- "Required dispatch_sync to load constants ...This may lead to deadlocks"
65- as in https://github.com/facebook/react-native/issues/16376
66- */
67- - (NSDictionary *)constantsToExport {
68- // iOS standard sizes
69- NSDictionary *fontSize = @{
70- @" default" : @(14 ),
71- };
72- return @{@" FontSize" : fontSize};
73- }
74-
7561/* *
7662 * Gets the width, height, line count and last line width for the provided text
7763 * font specifications.
@@ -93,7 +79,7 @@ - (NSDictionary *)constantsToExport {
9379 if (!text.length ) {
9480 resolve (@{
9581 @" width" : @0 ,
96- @" height" : @0 ,
82+ @" height" : @14 ,
9783 @" lastLineWidth" : @0 ,
9884 @" lineCount" : @0 ,
9985 });
@@ -102,8 +88,7 @@ - (NSDictionary *)constantsToExport {
10288
10389 // We cann't use RCTConvert since it does not handle font scaling and RN
10490 // does not scale the font if a custom delegate has been defined to create.
105- // UIFont * _Nullable font = [RCTConvert UIFont:options];
106- UIFont *const _Nullable font = [RNTextSize UIFontFromUserSpecs: options withBridge: _bridge];
91+ UIFont *const _Nullable font = [self scaledUIFontFromUserSpecs: options];
10792 if (!font) {
10893 reject (E_INVALID_FONT_SPEC, @" Invalid font specification." , nil );
10994 return ;
@@ -137,31 +122,28 @@ - (NSDictionary *)constantsToExport {
137122 size.width -= letterSpacing;
138123 }
139124
140- const CGFloat epsilon = 1 / RCTScreenScale (); // Yoga seems do this
141- const CGFloat width = MIN (RCTCeilPixelValue (size.width + 0.001 ), maxSize.width );
125+ const CGFloat epsilon = 0.001 ;
126+ const CGFloat width = MIN (RCTCeilPixelValue (size.width + epsilon ), maxSize.width );
142127 const CGFloat height = MIN (RCTCeilPixelValue (size.height + epsilon), maxSize.height );
143128 const CGFloat lineCount = CGRound (size.height / (font.lineHeight + font.leading ));
144129
145- CGFloat lastLineWidth = 0.0 ;
146130 if ([options[@" usePreciseWidth" ] boolValue ]) {
147131 const CGFloat lastIndex = layoutManager.numberOfGlyphs - 1 ;
148132 const CGSize lastSize = [layoutManager lineFragmentUsedRectForGlyphAtIndex: lastIndex
149133 effectiveRange: nil ].size ;
150- lastLineWidth = lastSize.width ;
134+ resolve (@{
135+ @" width" : @(width),
136+ @" height" : @(height),
137+ @" lastLineWidth" : @(lastSize.width ),
138+ @" lineCount" : @(lineCount),
139+ });
140+ } else {
141+ resolve (@{
142+ @" width" : @(width),
143+ @" height" : @(height),
144+ @" lineCount" : @(lineCount),
145+ });
151146 }
152-
153- const NSDictionary *result = @{
154- @" width" : @(width),
155- @" height" : @(height),
156- @" lineCount" : @(lineCount),
157- #if _DEBUG
158- @" _fontLineHeight" : @(font.lineHeight ),
159- @" _rawWidth" : @(size.width ),
160- @" _rawHeight" : @(size.height ),
161- @" _leading" : @(font.leading ),
162- #endif
163- };
164- resolve (result);
165147}
166148
167149/* *
@@ -173,13 +155,14 @@ - (NSDictionary *)constantsToExport {
173155 resolver:(RCTPromiseResolveBlock)resolve
174156 rejecter:(RCTPromiseRejectBlock)reject)
175157{
176- NSArray <NSString *> *const _Nullable texts = [RCTConvert NSStringArray: options[@" text" ]];
158+ // Don't use NSStringArray, we are handling nulls
159+ NSArray *const _Nullable texts = [RCTConvert NSArray: options[@" text" ]];
177160 if (isNull (texts)) {
178161 reject (E_MISSING_TEXT, @" Missing required text, must be an array." , nil );
179162 return ;
180163 }
181164
182- UIFont *const _Nullable font = [RNTextSize UIFontFromUserSpecs : options withBridge: _bridge ];
165+ UIFont *const _Nullable font = [self scaledUIFontFromUserSpecs : options];
183166 if (!font) {
184167 reject (E_INVALID_FONT_SPEC, @" Invalid font specification." , nil );
185168 return ;
@@ -203,7 +186,7 @@ - (NSDictionary *)constantsToExport {
203186 [layoutManager addTextContainer: textContainer];
204187
205188 NSMutableArray <NSNumber *> *result = [[NSMutableArray alloc ] initWithCapacity: texts.count];
206- const CGFloat epsilon = 1 / RCTScreenScale (); // Yoga seems do this
189+ const CGFloat epsilon = 0.001 ;
207190
208191 for (int ix = 0 ; ix < texts.count ; ix++) {
209192 NSString *text = texts[ix];
@@ -212,7 +195,7 @@ - (NSDictionary *)constantsToExport {
212195 continue ;
213196 }
214197 if (text == (id ) kCFNull ) {
215- result[ix] = @0 ;
198+ result[ix] = @14 ;
216199 continue ;
217200 }
218201
@@ -235,48 +218,41 @@ - (NSDictionary *)constantsToExport {
235218 * the user. Rejects if the parameters are falsy or the font could not be created.
236219 */
237220RCT_EXPORT_METHOD (fontFromSpecs:(NSDictionary *)specs
238- resolver:(RCTPromiseResolveBlock)resolve
239- rejecter:(RCTPromiseRejectBlock)reject)
221+ resolver:(RCTPromiseResolveBlock)resolve
222+ rejecter:(RCTPromiseRejectBlock)reject)
240223{
241224 if (isNull (specs)) {
242225 reject (E_INVALID_FONT_SPEC, @" Missing font specification." , nil );
243226 } else {
244- UIFont * _Nullable font = [RNTextSize UIFontFromUserSpecs: specs withBridge: _bridge ];
227+ UIFont * _Nullable font = [self UIFontFromUserSpecs: specs withScale: 1.0 ];
245228 if (font) {
246- resolve ([RNTextSize fontInfoFromUIFont: font]);
229+ resolve ([self fontInfoFromUIFont: font]);
247230 } else {
248231 reject (E_INVALID_FONT_SPEC, @" Invalid font specification." , nil );
249232 }
250233 }
251234}
252235
253236/* *
254- * Resolves with an array of font info from the predefined iOS Text Styles.
237+ * Resolves with an array of font info for the predefined iOS Text Styles.
238+ * The returned size is "Large", the default following the iOS HIG.
255239 *
256- * NOTE: The info includes unscaled font size and letterSpacing because this is
257- * managed by the RN `allowFontScaling` property.
258- * The returned size is "Large" (body of 17pt) following the iOS HIG.
240+ * NOTE: The info includes unscaled fontSize and letterSpacing. fontSize is managed by
241+ * by RN `allowFontScaling`, but letterSpacing is not.
259242 *
260243 * Altough the technique used to get create the result is complicated to maintain,
261244 * it simplifies things a lot.
262- *
263- * @see https://devsign.co/notes/tracking-and-character-spacing
264- * @see https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/typography/
265- * @see https://useyourloaf.com/blog/auto-adjusting-fonts-for-dynamic-type/
266245 */
267246RCT_EXPORT_METHOD (specsForTextStyles:(RCTPromiseResolveBlock)resolve
268- rejecter:(RCTPromiseRejectBlock)reject)
247+ rejecter:(RCTPromiseRejectBlock)reject)
269248{
249+ // From https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/typography/
270250 // These are the predefined kerning (1/1000em) to convert into letterSpacing (points)
271251 static const int T_OFFSET = 10 ; // tracking start with fontSize 10
272252 static const char trackings[] = {
273253 12 , 6 , 0 , -6 , -11 , -16 , -20 , -24 , -25 , -26 ,
274254 19 , 17 , 16 , 16 , 15 , 14 , 14 , 13 , 13 , 13 ,
275- 12 , 12 , 12 , 11 , 11 , 11 , 11 , 11 , 11 , 11 ,
276- 10 , 10 , 10 , 10 , 9 , 9 , 9 , 9 , 8 , 8 ,
277- 7 , 7 , 7 , 6 , 6 , 6 , 5 , 5 , 5 , 5 ,
278- 4 , 4 , 4 , 4 , 4 , 3 , 3 , 3 , 3 , 2 ,
279- 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 ,
255+ 12 , 12 , 12 , 11 , 11 ,
280256 };
281257 // These are the names of the properties to return
282258 static char *keys[] = {
@@ -311,7 +287,7 @@ - (NSDictionary *)constantsToExport {
311287 UIFontTextStyleBody, UIFontTextStyleCallout, UIFontTextStyleSubheadline,
312288 UIFontTextStyleFootnote, UIFontTextStyleCaption1, UIFontTextStyleCaption2,
313289 textStyleLargeTitle,
314- ];
290+ ];
315291
316292 // ...and with all in place, we are ready to create our result
317293 NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity: [textStyles count ]];
@@ -324,9 +300,9 @@ - (NSDictionary *)constantsToExport {
324300 const NSDictionary *traits = [descriptor objectForKey: UIFontDescriptorTraitsAttribute];
325301
326302 const NSString *fontFamily = font.familyName ?: font.fontName ?: (id ) [NSNull null ];
327- const NSArray *fontVariant = [RNTextSize fontVariantFromDescriptor: descriptor];
328- const NSString *fontStyle = [RNTextSize fontStyleFromTraits: traits];
329- const NSString *fontWeight = [RNTextSize fontWeightFromTraits: traits];
303+ const NSArray *fontVariant = [self fontVariantFromDescriptor: descriptor];
304+ const NSString *fontStyle = [self fontStyleFromTraits: traits];
305+ const NSString *fontWeight = [self fontWeightFromTraits: traits];
330306
331307 // The standard font size for this style is also used to calculate letterSpacing
332308 const int fontSize = sizes[ix];
@@ -359,7 +335,7 @@ - (NSDictionary *)constantsToExport {
359335 * Resolve with an array of font family names available on the system.
360336 */
361337RCT_EXPORT_METHOD (fontFamilyNames:(RCTPromiseResolveBlock)resolve
362- rejecter:(RCTPromiseRejectBlock)reject)
338+ rejecter:(RCTPromiseRejectBlock)reject)
363339{
364340 NSArray <NSString *> *fonts = [UIFont.familyNames
365341 sortedArrayUsingSelector: @selector (localizedCaseInsensitiveCompare: )];
@@ -371,8 +347,8 @@ - (NSDictionary *)constantsToExport {
371347 * Reject if the name is falsy or the names could not be obtain.
372348 */
373349RCT_EXPORT_METHOD (fontNamesForFamilyName:(NSString * _Nullable)fontFamily
374- resolver:(RCTPromiseResolveBlock)resolve
375- rejecter:(RCTPromiseRejectBlock)reject)
350+ resolver:(RCTPromiseResolveBlock)resolve
351+ rejecter:(RCTPromiseRejectBlock)reject)
376352{
377353 if (isNull (fontFamily)) {
378354 reject (E_INVALID_FONTFAMILY, @" Missing fontFamily name." , nil );
@@ -393,20 +369,28 @@ - (NSDictionary *)constantsToExport {
393369//
394370
395371/* *
396- * Create a font based on the given specs.
372+ * Create a scaled font based on the given specs.
397373 *
398- * TODO: implement the following behavior:
374+ * TODO:
399375 * This method is used instead of [RCTConvert UIFont] to support the omission
400376 * of scaling when a custom delegate has been defined for font's creation.
401377 */
402- + (UIFont * _Nullable)UIFontFromUserSpecs : (const NSDictionary *)specs
403- withBridge : (const RCTBridge *)bridge
378+ - (UIFont * _Nullable)scaledUIFontFromUserSpecs : (const NSDictionary *)specs
404379{
405380 const id allowFontScalingSrc = specs[@" allowFontScaling" ];
406- const BOOL allowFontScaling = allowFontScalingSrc == nil ? YES : [allowFontScalingSrc boolValue ];
381+ const BOOL allowFontScaling = allowFontScalingSrc ? [allowFontScalingSrc boolValue ] : YES ;
407382 const CGFloat scaleMultiplier =
408- allowFontScaling && bridge ? bridge.accessibilityManager .multiplier : 1.0 ;
383+ allowFontScaling && _bridge ? _bridge.accessibilityManager .multiplier : 1.0 ;
384+
385+ return [self UIFontFromUserSpecs: specs withScale: scaleMultiplier];
386+ }
409387
388+ /* *
389+ * Create a font based on the given specs.
390+ */
391+ - (UIFont * _Nullable)UIFontFromUserSpecs : (const NSDictionary *)specs
392+ withScale : (CGFloat)scaleMultiplier
393+ {
410394 return [RCTFont updateFont: nil
411395 withFamily: [RCTConvert NSString: specs[@" fontFamily" ]]
412396 size: [RCTConvert NSNumber: specs[@" fontSize" ]]
@@ -421,20 +405,19 @@ + (UIFont * _Nullable)UIFontFromUserSpecs:(const NSDictionary *)specs
421405 * The keys in the returned dictionary are a superset of the RN Text styles
422406 * so the format is not fully compatible.
423407 */
424- + (NSDictionary *)fontInfoFromUIFont : (const UIFont *)font
408+ - (NSDictionary *)fontInfoFromUIFont : (const UIFont *)font
425409{
426410 const UIFontDescriptor *descriptor = font.fontDescriptor ;
427411 const NSDictionary *traits = [descriptor objectForKey: UIFontDescriptorTraitsAttribute];
428412 const NSArray *fontVariant = [self fontVariantFromDescriptor: descriptor];
429- const id null = [NSNull null ];
430413
431414 return @{
432- @" fontFamily" : font.familyName ?: null ,
433- @" fontName" : font.fontName ?: null ,
415+ @" fontFamily" : RCTNullIfNil ( font.familyName ) ,
416+ @" fontName" : RCTNullIfNil ( font.fontName ) ,
434417 @" fontSize" : @(font.pointSize ),
435418 @" fontStyle" : [self fontStyleFromTraits: traits],
436419 @" fontWeight" : [self fontWeightFromTraits: traits],
437- @" fontVariant" : fontVariant ?: null ,
420+ @" fontVariant" : RCTNullIfNil ( fontVariant) ,
438421 @" ascender" : @(font.ascender ),
439422 @" descender" : @(font.descender ),
440423 @" capHeight" : @(font.capHeight ), // height of capital characters
@@ -453,7 +436,7 @@ + (NSDictionary *)fontInfoFromUIFont:(const UIFont *)font
453436 * @param trais NSDictionary with the traits of the font.
454437 * @return NSString with the weight of the font.
455438 */
456- + (NSString *)fontWeightFromTraits : (const NSDictionary *)traits
439+ - (NSString *)fontWeightFromTraits : (const NSDictionary *)traits
457440{
458441 // Use a small tolerance to avoid rounding problems
459442 const CGFloat weight = CGFloatValueFrom (traits[UIFontWeightTrait]) + 0.01 ;
@@ -474,7 +457,7 @@ + (NSString *)fontWeightFromTraits:(const NSDictionary *)traits
474457 * @param trais NSDictionary with the traits of the font.
475458 * @return NSString with the style.
476459 */
477- + (NSString *)fontStyleFromTraits : (const NSDictionary *)traits
460+ - (NSString *)fontStyleFromTraits : (const NSDictionary *)traits
478461{
479462 const UIFontDescriptorSymbolicTraits symbolicTrais = [traits[UIFontSymbolicTrait] unsignedIntValue ];
480463 const BOOL isItalic = (symbolicTrais & UIFontDescriptorTraitItalic) != 0 ;
@@ -491,7 +474,7 @@ + (NSString *)fontStyleFromTraits:(const NSDictionary *)traits
491474 * FIXME:
492475 * kNumberCase variants are not being recognized... RN bug?
493476 */
494- + (NSArray <NSString *> * _Nullable)fontVariantFromDescriptor : (const UIFontDescriptor *)descriptor
477+ - (NSArray <NSString *> * _Nullable)fontVariantFromDescriptor : (const UIFontDescriptor *)descriptor
495478{
496479 const NSArray *features = descriptor.fontAttributes [UIFontDescriptorFeatureSettingsAttribute];
497480 if (isNull (features)) {
0 commit comments