154 lines
6.8 KiB
JavaScript
154 lines
6.8 KiB
JavaScript
![]() |
import { CodedError, UnavailabilityError } from '@unimodules/core';
|
||
|
import ExpoFontLoader from './ExpoFontLoader';
|
||
|
import { FontDisplay } from './Font.types';
|
||
|
import { getAssetForSource, loadSingleFontAsync, fontFamilyNeedsScoping, getNativeFontName, } from './FontLoader';
|
||
|
const loaded = {};
|
||
|
const loadPromises = {};
|
||
|
/**
|
||
|
* Used to transform font family names to the scoped name. This does not need to
|
||
|
* be called in standalone or bare apps but it will return unscoped font family
|
||
|
* names if it is called in those contexts.
|
||
|
* note(brentvatne): at some point we may want to warn if this is called
|
||
|
* outside of a managed app.
|
||
|
*
|
||
|
* @param fontFamily name to process
|
||
|
* @returns a name processed for use with the [current workflow](https://docs.expo.io/versions/latest/introduction/managed-vs-bare/)
|
||
|
*/
|
||
|
export function processFontFamily(fontFamily) {
|
||
|
if (!fontFamily || !fontFamilyNeedsScoping(fontFamily)) {
|
||
|
return fontFamily;
|
||
|
}
|
||
|
if (!isLoaded(fontFamily)) {
|
||
|
if (__DEV__) {
|
||
|
if (isLoading(fontFamily)) {
|
||
|
console.error(`You started loading the font "${fontFamily}", but used it before it finished loading.\n
|
||
|
- You need to wait for Font.loadAsync to complete before using the font.\n
|
||
|
- We recommend loading all fonts before rendering the app, and rendering only Expo.AppLoading while waiting for loading to complete.`);
|
||
|
}
|
||
|
else {
|
||
|
console.error(`fontFamily "${fontFamily}" is not a system font and has not been loaded through Font.loadAsync.\n
|
||
|
- 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
|
||
|
- If this is a custom font, be sure to load it with Font.loadAsync.`);
|
||
|
}
|
||
|
}
|
||
|
return 'System';
|
||
|
}
|
||
|
return `ExpoFont-${getNativeFontName(fontFamily)}`;
|
||
|
}
|
||
|
/**
|
||
|
* Synchronously detect if the font for `fontFamily` has finished loading
|
||
|
*
|
||
|
* @param fontFamily the name used to load the `FontResource`.
|
||
|
* @returns `true` if the the font has fully loaded.
|
||
|
*/
|
||
|
export function isLoaded(fontFamily) {
|
||
|
return fontFamily in loaded;
|
||
|
}
|
||
|
/**
|
||
|
* Synchronously detect if the font for `fontFamily` is still being loaded
|
||
|
*
|
||
|
* @param fontFamily the name used to load the `FontResource`.
|
||
|
* @returns `true` if the the font is still loading.
|
||
|
*/
|
||
|
export function isLoading(fontFamily) {
|
||
|
return fontFamily in loadPromises;
|
||
|
}
|
||
|
/**
|
||
|
* Natively load a font for use with Text elements.
|
||
|
* @param fontFamilyOrFontMap string or map of values that can be used as the [`fontFamily`](https://reactnative.dev/docs/text#style) style prop with React Native Text elements.
|
||
|
* @param source the font asset that should be loaded into the `fontFamily` namespace.
|
||
|
*/
|
||
|
export async function loadAsync(fontFamilyOrFontMap, source) {
|
||
|
if (typeof fontFamilyOrFontMap === 'object') {
|
||
|
if (source) {
|
||
|
throw new CodedError(`ERR_FONT_API`, `No fontFamily can be used for the provided source: ${source}. The second argument of \`loadAsync()\` can only be used with a \`string\` value as the first argument.`);
|
||
|
}
|
||
|
const fontMap = fontFamilyOrFontMap;
|
||
|
const names = Object.keys(fontMap);
|
||
|
await Promise.all(names.map(name => loadFontInNamespaceAsync(name, fontMap[name])));
|
||
|
return;
|
||
|
}
|
||
|
return await loadFontInNamespaceAsync(fontFamilyOrFontMap, source);
|
||
|
}
|
||
|
async function loadFontInNamespaceAsync(fontFamily, source) {
|
||
|
if (!source) {
|
||
|
throw new CodedError(`ERR_FONT_SOURCE`, `Cannot load null or undefined font source: { "${fontFamily}": ${source} }. Expected asset of type \`FontSource\` for fontFamily of name: "${fontFamily}"`);
|
||
|
}
|
||
|
if (loaded[fontFamily]) {
|
||
|
return;
|
||
|
}
|
||
|
if (loadPromises[fontFamily]) {
|
||
|
return loadPromises[fontFamily];
|
||
|
}
|
||
|
// Important: we want all callers that concurrently try to load the same font to await the same
|
||
|
// promise. If we're here, we haven't created the promise yet. To ensure we create only one
|
||
|
// promise in the program, we need to create the promise synchronously without yielding the event
|
||
|
// loop from this point.
|
||
|
const asset = getAssetForSource(source);
|
||
|
loadPromises[fontFamily] = (async () => {
|
||
|
try {
|
||
|
await loadSingleFontAsync(fontFamily, asset);
|
||
|
loaded[fontFamily] = true;
|
||
|
}
|
||
|
finally {
|
||
|
delete loadPromises[fontFamily];
|
||
|
}
|
||
|
})();
|
||
|
await loadPromises[fontFamily];
|
||
|
}
|
||
|
/**
|
||
|
* Unloads all of the custom fonts. This is used for testing.
|
||
|
*/
|
||
|
export async function unloadAllAsync() {
|
||
|
if (!ExpoFontLoader.unloadAllAsync) {
|
||
|
throw new UnavailabilityError('expo-font', 'unloadAllAsync');
|
||
|
}
|
||
|
if (Object.keys(loadPromises).length) {
|
||
|
throw new CodedError(`ERR_UNLOAD`, `Cannot unload fonts while they're still loading: ${Object.keys(loadPromises).join(', ')}`);
|
||
|
}
|
||
|
for (const fontFamily of Object.keys(loaded)) {
|
||
|
delete loaded[fontFamily];
|
||
|
}
|
||
|
await ExpoFontLoader.unloadAllAsync();
|
||
|
}
|
||
|
/**
|
||
|
* Unload custom fonts matching the `fontFamily`s and display values provided.
|
||
|
* Because fonts are automatically unloaded on every platform this is mostly used for testing.
|
||
|
*
|
||
|
* @param fontFamilyOrFontMap the names of the custom fonts that will be unloaded.
|
||
|
* @param source when `fontFamilyOrFontMap` is a string, this should be the font source used to load the custom font originally.
|
||
|
*/
|
||
|
export async function unloadAsync(fontFamilyOrFontMap, options) {
|
||
|
if (!ExpoFontLoader.unloadAsync) {
|
||
|
throw new UnavailabilityError('expo-font', 'unloadAsync');
|
||
|
}
|
||
|
if (typeof fontFamilyOrFontMap === 'object') {
|
||
|
if (options) {
|
||
|
throw new CodedError(`ERR_FONT_API`, `No fontFamily can be used for the provided options: ${options}. The second argument of \`unloadAsync()\` can only be used with a \`string\` value as the first argument.`);
|
||
|
}
|
||
|
const fontMap = fontFamilyOrFontMap;
|
||
|
const names = Object.keys(fontMap);
|
||
|
await Promise.all(names.map(name => unloadFontInNamespaceAsync(name, fontMap[name])));
|
||
|
return;
|
||
|
}
|
||
|
return await unloadFontInNamespaceAsync(fontFamilyOrFontMap, options);
|
||
|
}
|
||
|
async function unloadFontInNamespaceAsync(fontFamily, options) {
|
||
|
if (!loaded[fontFamily]) {
|
||
|
return;
|
||
|
}
|
||
|
else {
|
||
|
delete loaded[fontFamily];
|
||
|
}
|
||
|
// Important: we want all callers that concurrently try to load the same font to await the same
|
||
|
// promise. If we're here, we haven't created the promise yet. To ensure we create only one
|
||
|
// promise in the program, we need to create the promise synchronously without yielding the event
|
||
|
// loop from this point.
|
||
|
const nativeFontName = getNativeFontName(fontFamily);
|
||
|
if (!nativeFontName) {
|
||
|
throw new CodedError(`ERR_FONT_FAMILY`, `Cannot unload an empty name`);
|
||
|
}
|
||
|
await ExpoFontLoader.unloadAsync(nativeFontName, options);
|
||
|
}
|
||
|
export { FontDisplay };
|
||
|
//# sourceMappingURL=Font.js.map
|