UNPKG

5.08 kBPlain TextView Raw
1import { Asset } from 'expo-asset';
2import Constants from 'expo-constants';
3import { Platform } from '@unimodules/core';
4
5import ExpoFontLoader from './ExpoFontLoader';
6
7/**
8 * A font source can be a URI, a module ID, or an Expo Asset.
9 */
10type FontSource = string | number | Asset;
11
12const isWeb = Platform.OS === 'web';
13const isInClient = !isWeb && Constants.appOwnership === 'expo';
14const isInIOSStandalone = Constants.appOwnership === 'standalone' && Platform.OS === 'ios';
15
16const loaded: { [name: string]: boolean } = {};
17const loadPromises: { [name: string]: Promise<void> } = {};
18
19function fontFamilyNeedsScoping(name: string): boolean {
20 return (
21 (isInClient || isInIOSStandalone) &&
22 !Constants.systemFonts.includes(name) &&
23 name !== 'System' &&
24 !name.includes(Constants.sessionId)
25 );
26}
27
28/**
29 * Used to transform font family names to the scoped name. This does not need to
30 * be called in standalone or bare apps but it will return unscoped font family
31 * names if it is called in those contexts.
32 * note(brentvatne): at some point we may want to warn if this is called
33 * outside of a managed app.
34 */
35export function processFontFamily(name: string | null): string | null {
36 if (!name || !fontFamilyNeedsScoping(name)) {
37 return name;
38 }
39
40 if (!isLoaded(name)) {
41 if (__DEV__) {
42 if (isLoading(name)) {
43 console.error(
44 `You started loading the font "${name}", but used it before it finished loading.\n
45- You need to wait for Font.loadAsync to complete before using the font.\n
46- We recommend loading all fonts before rendering the app, and rendering only Expo.AppLoading while waiting for loading to complete.`
47 );
48 } else {
49 console.error(
50 `fontFamily "${name}" is not a system font and has not been loaded through Font.loadAsync.\n
51- If you intended to use a system font, make sure you typed the name correctly and that it is supported by your device operating system.\n
52- If this is a custom font, be sure to load it with Font.loadAsync.`
53 );
54 }
55 }
56
57 return 'System';
58 }
59
60 return `ExpoFont-${_getNativeFontName(name)}`;
61}
62
63export function isLoaded(name: string): boolean {
64 return loaded.hasOwnProperty(name);
65}
66
67export function isLoading(name: string): boolean {
68 return loadPromises.hasOwnProperty(name);
69}
70
71export async function loadAsync(
72 nameOrMap: string | { [name: string]: FontSource },
73 source?: FontSource
74): Promise<void> {
75 if (typeof nameOrMap === 'object') {
76 const fontMap = nameOrMap;
77 const names = Object.keys(fontMap);
78 await Promise.all(names.map(name => loadAsync(name, fontMap[name])));
79 return;
80 }
81
82 const name = nameOrMap;
83
84 if (loaded[name]) {
85 return;
86 }
87
88 if (loadPromises[name]) {
89 return loadPromises[name];
90 }
91
92 // Important: we want all callers that concurrently try to load the same font to await the same
93 // promise. If we're here, we haven't created the promise yet. To ensure we create only one
94 // promise in the program, we need to create the promise synchronously without yielding the event
95 // loop from this point.
96
97 if (!source) {
98 throw new Error(`No source from which to load font "${name}"`);
99 }
100 const asset = _getAssetForSource(source);
101 loadPromises[name] = (async () => {
102 try {
103 await _loadSingleFontAsync(name, asset);
104 loaded[name] = true;
105 } finally {
106 delete loadPromises[name];
107 }
108 })();
109
110 await loadPromises[name];
111}
112
113function _getAssetForSource(source: FontSource): Asset {
114 if (source instanceof Asset) {
115 return source;
116 }
117
118 if (!isWeb && typeof source === 'string') {
119 return Asset.fromURI(source);
120 }
121
122 if (isWeb || typeof source === 'number') {
123 return Asset.fromModule(source);
124 }
125
126 // @ts-ignore Error: Type 'string' is not assignable to type 'Asset'
127 // We can't have a string here, we would have thrown an error if !isWeb
128 // or returned Asset.fromModule if isWeb.
129 return source;
130}
131
132async function _loadSingleFontAsync(name: string, asset: Asset): Promise<void> {
133 await asset.downloadAsync();
134 if (!asset.downloaded) {
135 throw new Error(`Failed to download asset for font "${name}"`);
136 }
137 await ExpoFontLoader.loadAsync(_getNativeFontName(name), asset.localUri);
138}
139
140function _getNativeFontName(name: string): string {
141 if (fontFamilyNeedsScoping(name)) {
142 return `${Constants.sessionId}-${name}`;
143 } else {
144 return name;
145 }
146}
147
148declare var module: any;
149
150if (module && module.exports) {
151 let wasImportWarningShown = false;
152 // @ts-ignore: Temporarily define an export named "Font" for legacy compatibility
153 Object.defineProperty(exports, 'Font', {
154 get() {
155 if (!wasImportWarningShown) {
156 console.warn(
157 `The syntax "import { Font } from 'expo-font'" is deprecated. Use "import * as Font from 'expo-font'" or import named exports instead. Support for the old syntax will be removed in SDK 33.`
158 );
159 wasImportWarningShown = true;
160 }
161 return {
162 processFontFamily,
163 isLoaded,
164 isLoading,
165 loadAsync,
166 };
167 },
168 });
169}
170
\No newline at end of file