Giter Club home page Giter Club logo

react-native-font-faces's Introduction

React Native Font Faces

Easily emulate @font-face behavior in react-native.

Motivation:

Using custom fonts in React Native becomes complicated when trying to work with different font weights and styles. Even though the React Native TextStyle type includes properties for fontFamily, fontWeight and fontStyle, these properties seem to work only for the default built-in fonts, and have limited support when using custom fonts. For this reason, selecting a specific font weight and style is traditionally achieved by specifying the exact PostScript name of the desired loaded font file.

For example:

const style: ViewStyle = {
  fontFamily: 'Roboto-MediumItalic',
};

This makes it difficult to achieve merged styles or text style composition. A preferable solution might be something like this:

const style: ViewStyle = {
  fontFamily: 'Roboto',
  fontWeight: '500',
  fontStyle: 'italic',
};

This library aims to make life easier by allowing React Native developers to use fontWeight and fontStyle with custom fonts on iOS, Android, and Web.

Getting Started

  1. Add the required dependencies to your application's package.json:

    yarn add react-native-font-faces

    If you are using Expo and need to load additional custom font files into your app, also add the following:

    yarn add expo-font
  2. Add a call to enableFontFaces() in your application's entry point, and import the desired font faces. Then just use the font family as you would normally expect:

    // App.tsx
    
    import React from 'react';
    import { useFonts } from 'expo-font';
    import { AppLoading } from 'expo';
    import { AppContent } from './AppContent';
    import { Roboto_All, enableFontFaces, getExpoFontMap } from 'react-native-font-faces';
    
    enableFontFaces(Roboto_All);
    
    export default function App() {
      const [loaded, error] = useFonts(getExpoFontMap(Roboto_All));
    
      if (!loaded) {
        return <AppLoading />;
      } else if (error) {
        return <Text>{error.message}</Text>;
      } else {
        return (
          <View style={styles.container}>
            <StatusBar style="auto" />
            <Text style={styles.text}>This should be Regular</Text>
            <Text style={[styles.text, styles.italic]}>This should be Italic</Text>
            <Text style={[styles.text, styles.bold]}>This should be Bold</Text>
            <Text style={[styles.text, styles.bold, styles.italic]}>This should be BoldItalic</Text>
            <Text style={[styles.text, styles.thin]}>This should be Thin</Text>
            <Text style={[styles.text, styles.thin, styles.italic]}>This should be ThinItalic</Text>
          </View>
        );
      }
    }
    
    const styles = StyleSheet.create({
      text: {
        fontFamily: 'Roboto',
      },
      bold: {
        fontWeight: 'bold',
      },
      thin: {
        fontWeight: '100',
      },
      italic: {
        fontStyle: 'italic',
      },
      container: {
        flex: 1,
        backgroundColor: '#fff',
        alignItems: 'center',
        justifyContent: 'center',
      },
    });

Migrating from 3.x

In version 4.x, we removed FontFacesProvider and added enableFontFaces. Follow these steps to migrate:

  1. Remove all instances of <FontFacesProvider />.
  2. Add a call to enableFontFaces() in your application's entrypoint.
  3. (Optional) Add a call to useFonts() (expo-font) or loadFonts() (react-native-dynamic-fonts) to dynamically load remote fonts.

Migrating from 2.x

In version 3.x, we simplified FontFacesProvider and removed useFontFaces. Follow these steps to migrate:

  1. Remove all instances of useFontFaces().
  2. Update your application's <FontFacesProvider/> to provide the onLoading and onError props (optional).

Migrating from 1.x

In version 2.x, we introduced FontFacesProvider and useFontFaces, and removed enableFontFaces. Follow these steps to migrate:

  1. Remove all instances of enableFontFaces().
  2. Add a <FontFacesProvider/> around your application's root component.
  3. Add const [fontsLoaded] = useFontFaces(...) inside an inner function component's body and handle the fontsLoaded value appropriately.

react-native-font-faces's People

Contributors

andreialecu avatar cmorbitzer avatar dependabot-preview[bot] avatar dependabot[bot] avatar kylerjensen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

react-native-font-faces's Issues

Provider API is too cumbersome

The existing provider API is too cumbersome when dealing with loading states. Let's find a way to slim it down and keep it to hooks only.

TS2307 error while type checking

Hey there! Thank you for the great work on this lib. We've been implementing it in an app to simplify some of the font handling code and noticed that type checking started failing:

husky > pre-commit (node v14.15.0)
node_modules/react-native-font-faces/dist/typescript/utilities/enableFontFaces.d.ts:2:39 - error TS2307: Cannot find module 'src/types/EnableFontFacesResult' or its corresponding type declarations.
2 import { EnableFontFacesResult } from 'src/types/EnableFontFacesResult';
                                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
node_modules/react-native-font-faces/dist/typescript/utilities/getDynamicFontList.d.ts:2:33 - error TS2307: Cannot find module 'src/types/DynamicFontList' or its corresponding type declarations.
2 import { DynamicFontList } from 'src/types/DynamicFontList';
                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~
node_modules/react-native-font-faces/dist/typescript/utilities/getExpoFontMap.d.ts:2:29 - error TS2307: Cannot find module 'src/types/ExpoFontMap' or its corresponding type declarations.
2 import { ExpoFontMap } from 'src/types/ExpoFontMap';
                              ~~~~~~~~~~~~~~~~~~~~~~~

This usually happens due to the usage of non relative paths, see:

https://cdn.jsdelivr.net/npm/[email protected]/dist/typescript/utilities/getDynamicFontList.d.ts

Performance: memoization

Hi again.

While fixing the other issue yesterday, it got me thinking that trying to determine which font to use in each render might not be super efficient.

I decided to take a look at it from a performance perspective, here are my findings.

It appears that on an Oppo A72 Android device, it takes about 1ms to execute the logic in the overridden render function. Now, if there are 16 Text nodes on the screen, that's already 16 ms, so a dropped frame.

Screenshot 2021-06-25 at 10 15 34

If however I add some memoization, it drops to 0.1ms most of the time, which is a 10x improvement:

Screenshot 2021-06-25 at 10 19 31

I'll open a PR shortly with this for your consideration

Add ability to resolve a custom font face to a native font name

Background:

Using custom fonts in React Native becomes complicated when trying to work with different font weights and styles. Even though the React Native TextStyle type includes properties for fontFamily, fontWeight and fontStyle, these properties seem to work only for the default built-in fonts, and have limited support when using custom fonts. For this reason, selecting a specific font weight and style is traditionally achieved by specifying the exact PostScript name of the desired loaded font file.

For example:

const style: ViewStyle = {
  fontFamily: 'Roboto-MediumItalic'
};

This makes it difficult to achieve merged styles or text style composition. A preferable solution might be something like this:

const style: ViewStyle = {
  fontFamily: 'Roboto',
  fontWeight: '500',
  fontStyle: 'italic'
};

Proposed Solution:

Write a simple hook that allows the user to "load" a set of font faces for consumption within the application.

Though not a perfect solution, a first step might be to build a mechanism that allows the user to alias a given set of style attributes that would eventually be resolved to a literal font.

Example:

// App.tsx

const Roboto_Regular: FontFace = {
  fontName: 'Roboto-Regular',
  fontFamily: 'Roboto',
  fontWeight: '400',
  fontStyle: 'normal',
};

const Roboto_Italic: FontFace = {
  fontName: 'Roboto-Italic',
  fontFamily: 'Roboto',
  fontWeight: '400',
  fontStyle: 'italic',
};

const Roboto_Bold: FontFace = {
  fontName: 'Roboto-Bold',
  fontFamily: 'Roboto',
  fontWeight: '700',
  fontStyle: 'normal',
};

const Roboto_BoldItalic: FontFace = {
  fontName: 'Roboto-BoldItalic',
  fontFamily: 'Roboto',
  fontWeight: '700',
  fontStyle: 'italic',
};

const styles = StyleSheet.create({
  text: { fontFamily: 'Roboto' },
  bold: { fontWeight: 'bold' },
  italic: { fontStyle: 'italic' }
});

export default () => {
  useFontFaces([Roboto_Regular, Roboto_Italic, Roboto_Bold, Roboto_BoldItalic]);
  return (
    <Text style={[styles.text]}>This should be Roboto-Regular</Text>
    <Text style={[styles.text, styles.bold]}>This should be Roboto-Bold</Text>
    <Text style={[styles.text, styles.bold, styles.italic]}>This should be Roboto-BoldItalic</Text>
  );
};

How it works:

Taking inspiration from siliconjungles/react-native-fontweight, it is possible to override the default render() function of the built-in Text component. This allows us to modify the incoming style props before they are passed to the operating system.

Caveats:

  • For now, this only works on iOS and Android, so we will have to use font faces that match what will be available on the web.
  • For now, this library will not download or import any font files or mess with any css stylesheets; so the aliased font names will have to be already available in the app. Use a tool like expo-font to dynamically load custom font files, or bundle them with your application.

Progress:

  • Build the mechanism to override the Text.render function
  • Build a font matching mechanism that mirrors the CSS behavior for choosing with a given set of style attributes
  • Figure out how to make it so that the user can call useFontFaces multiple times in a single app without breaking functionality won't do - settling for global activator function for v1. Consider for v2.
  • Build the useFontFaces hook See previous

Does not work properly with the Hermes JS engine

I've been scratching my head for a while on this and went on to troubleshoot it.

It appears that on Android when using Hermes, this library is not able to pick the correct font via the matchFontFace function.

The reason this seems to occur is because Hermes does not have a stable sort function: facebook/hermes#212 so this piece of code does not work properly:

export function matchFontFace(fontFaces: FontFace[], textStyle: TextStyle): FontFace | undefined {
return fontFaces
.filter(ff => filterFontFamilyincludes(ff, textStyle))
.sort((a, b) => compareFontWeightDistance(a, b, textStyle))
.sort((a, b) => compareFontStyleExactMatch(a, b, textStyle))
.sort((a, b) => compareFontFamilyStartsWith(a, b, textStyle))
.find(() => true);
}

Any subsequent sort will then randomize the sort order.

I think a fix could be to add a dependency on a third party library to do the sorting instead of using the built in sort function.

What do you think?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.