remotion-dev/remotion

Easy to import Google Fonts using `@remotion/google-fonts` #1394

JonnyBurger posted onGitHub

šŸŽƒ This issue is part of our Hacktoberfest campaign! šŸ“š Read more about Hacktoberfest here šŸ•› This issue is taken by @ayatkyo! šŸ’° Thanks to CodeChem for sponsoring this issue!

Currently it is a bit of a hassle to add Google fonts to Remotion. Here is an idea of how we can make an easy to use API:

  1. There is JSON information about each Google Font, for example about Roboto:
{
    category: 'sans-serif',
    lastModified: '2021-09-22',
    subsets: [
        'cyrillic',
        'cyrillic-ext',
        'greek',
        'greek-ext',
        'latin',
        'latin-ext',
        'vietnamese',
    ],
    unicodeRange: {
        cyrillic: 'U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116',
        'cyrillic-ext':
            'U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F',
        greek: 'U+0370-03FF',
        'greek-ext': 'U+1F00-1FFF',
        latin:
            'U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD',
        'latin-ext':
            'U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF',
        vietnamese:
            'U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB',
    },
    variants: {
        italic: {
            '100': {
                local: [],
                url: {
                    eot: 'https://fonts.gstatic.com/s/roboto/v29/KFOiCnqEu92Fr1Mu51QrEz4dKw.eot',
                    svg: 'https://fonts.gstatic.com/l/font?kit=KFOiCnqEu92Fr1Mu51QrEz4dKA&skey=8f53aa2e7deadc4a&v=v29#Roboto',
                    ttf: 'https://fonts.gstatic.com/s/roboto/v29/KFOiCnqEu92Fr1Mu51QrEz4dKg.ttf',
                    woff: 'https://fonts.gstatic.com/s/roboto/v29/KFOiCnqEu92Fr1Mu51QrEz4dKQ.woff',
                    woff2:
                        'https://fonts.gstatic.com/s/roboto/v29/KFOiCnqEu92Fr1Mu51QrEzAdLw.woff2',
                },
            },
            '300': {
                local: [],
                url: {
                    eot: 'https://fonts.gstatic.com/s/roboto/v29/KFOjCnqEu92Fr1Mu51TjASc0CsA.eot',
                    svg: 'https://fonts.gstatic.com/l/font?kit=KFOjCnqEu92Fr1Mu51TjASc0CsM&skey=8f644060176e1f7e&v=v29#Roboto',
                    ttf: 'https://fonts.gstatic.com/s/roboto/v29/KFOjCnqEu92Fr1Mu51TjASc0CsE.ttf',
                    woff: 'https://fonts.gstatic.com/s/roboto/v29/KFOjCnqEu92Fr1Mu51TjASc0CsI.woff',
                    woff2:
                        'https://fonts.gstatic.com/s/roboto/v29/KFOjCnqEu92Fr1Mu51TjASc6CsQ.woff2',
                },
            },
            '400': {
                local: [],
                url: {
                    eot: 'https://fonts.gstatic.com/s/roboto/v29/KFOkCnqEu92Fr1Mu51xGIzY.eot',
                    svg: 'https://fonts.gstatic.com/l/font?kit=KFOkCnqEu92Fr1Mu51xGIzU&skey=c608c610063635f9&v=v29#Roboto',
                    ttf: 'https://fonts.gstatic.com/s/roboto/v29/KFOkCnqEu92Fr1Mu51xGIzc.ttf',
                    woff: 'https://fonts.gstatic.com/s/roboto/v29/KFOkCnqEu92Fr1Mu51xGIzQ.woff',
                    woff2:
                        'https://fonts.gstatic.com/s/roboto/v29/KFOkCnqEu92Fr1Mu51xIIzI.woff2',
                },
            },
            '500': {
                local: [],
                url: {
                    eot: 'https://fonts.gstatic.com/s/roboto/v29/KFOjCnqEu92Fr1Mu51S7ACc0CsA.eot',
                    svg: 'https://fonts.gstatic.com/l/font?kit=KFOjCnqEu92Fr1Mu51S7ACc0CsM&skey=c985e17098069ce0&v=v29#Roboto',
                    ttf: 'https://fonts.gstatic.com/s/roboto/v29/KFOjCnqEu92Fr1Mu51S7ACc0CsE.ttf',
                    woff: 'https://fonts.gstatic.com/s/roboto/v29/KFOjCnqEu92Fr1Mu51S7ACc0CsI.woff',
                    woff2:
                        'https://fonts.gstatic.com/s/roboto/v29/KFOjCnqEu92Fr1Mu51S7ACc6CsQ.woff2',
                },
            },
            '700': {
                local: [],
                url: {
                    eot: 'https://fonts.gstatic.com/s/roboto/v29/KFOjCnqEu92Fr1Mu51TzBic0CsA.eot',
                    svg: 'https://fonts.gstatic.com/l/font?kit=KFOjCnqEu92Fr1Mu51TzBic0CsM&skey=dd030d266f3beccc&v=v29#Roboto',
                    ttf: 'https://fonts.gstatic.com/s/roboto/v29/KFOjCnqEu92Fr1Mu51TzBic0CsE.ttf',
                    woff: 'https://fonts.gstatic.com/s/roboto/v29/KFOjCnqEu92Fr1Mu51TzBic0CsI.woff',
                    woff2:
                        'https://fonts.gstatic.com/s/roboto/v29/KFOjCnqEu92Fr1Mu51TzBic6CsQ.woff2',
                },
            },
            '900': {
                local: [],
                url: {
                    eot: 'https://fonts.gstatic.com/s/roboto/v29/KFOjCnqEu92Fr1Mu51TLBCc0CsA.eot',
                    svg: 'https://fonts.gstatic.com/l/font?kit=KFOjCnqEu92Fr1Mu51TLBCc0CsM&skey=b80be3241fe40325&v=v29#Roboto',
                    ttf: 'https://fonts.gstatic.com/s/roboto/v29/KFOjCnqEu92Fr1Mu51TLBCc0CsE.ttf',
                    woff: 'https://fonts.gstatic.com/s/roboto/v29/KFOjCnqEu92Fr1Mu51TLBCc0CsI.woff',
                    woff2:
                        'https://fonts.gstatic.com/s/roboto/v29/KFOjCnqEu92Fr1Mu51TLBCc6CsQ.woff2',
                },
            },
        },
        normal: {
            '100': {
                local: [],
                url: {
                    eot: 'https://fonts.gstatic.com/s/roboto/v29/KFOkCnqEu92Fr1MmgVxGIzY.eot',
                    svg: 'https://fonts.gstatic.com/l/font?kit=KFOkCnqEu92Fr1MmgVxGIzU&skey=5473b731ec7fc9c1&v=v29#Roboto',
                    ttf: 'https://fonts.gstatic.com/s/roboto/v29/KFOkCnqEu92Fr1MmgVxGIzc.ttf',
                    woff: 'https://fonts.gstatic.com/s/roboto/v29/KFOkCnqEu92Fr1MmgVxGIzQ.woff',
                    woff2:
                        'https://fonts.gstatic.com/s/roboto/v29/KFOkCnqEu92Fr1MmgVxIIzI.woff2',
                },
            },
            '300': {
                local: [],
                url: {
                    eot: 'https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmSU5fChc8.eot',
                    svg: 'https://fonts.gstatic.com/l/font?kit=KFOlCnqEu92Fr1MmSU5fChc_&skey=11ce8ad5f54705ca&v=v29#Roboto',
                    ttf: 'https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmSU5fChc9.ttf',
                    woff: 'https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmSU5fChc-.woff',
                    woff2:
                        'https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmSU5fBBc4.woff2',
                },
            },
            '400': {
                local: [],
                url: {
                    eot: 'https://fonts.gstatic.com/s/roboto/v29/KFOmCnqEu92Fr1Mu7GxO.eot',
                    svg: 'https://fonts.gstatic.com/l/font?kit=KFOmCnqEu92Fr1Mu7GxN&skey=a0a0114a1dcab3ac&v=v29#Roboto',
                    ttf: 'https://fonts.gstatic.com/s/roboto/v29/KFOmCnqEu92Fr1Mu7GxP.ttf',
                    woff: 'https://fonts.gstatic.com/s/roboto/v29/KFOmCnqEu92Fr1Mu7GxM.woff',
                    woff2:
                        'https://fonts.gstatic.com/s/roboto/v29/KFOmCnqEu92Fr1Mu4mxK.woff2',
                },
            },
            '500': {
                local: [],
                url: {
                    eot: 'https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmEU9fChc8.eot',
                    svg: 'https://fonts.gstatic.com/l/font?kit=KFOlCnqEu92Fr1MmEU9fChc_&skey=ee881451c540fdec&v=v29#Roboto',
                    ttf: 'https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmEU9fChc9.ttf',
                    woff: 'https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmEU9fChc-.woff',
                    woff2:
                        'https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmEU9fBBc4.woff2',
                },
            },
            '700': {
                local: [],
                url: {
                    eot: 'https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmWUlfChc8.eot',
                    svg: 'https://fonts.gstatic.com/l/font?kit=KFOlCnqEu92Fr1MmWUlfChc_&skey=c06e7213f788649e&v=v29#Roboto',
                    ttf: 'https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmWUlfChc9.ttf',
                    woff: 'https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmWUlfChc-.woff',
                    woff2:
                        'https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmWUlfBBc4.woff2',
                },
            },
            '900': {
                local: [],
                url: {
                    eot: 'https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmYUtfChc8.eot',
                    svg: 'https://fonts.gstatic.com/l/font?kit=KFOlCnqEu92Fr1MmYUtfChc_&skey=934406f772f9777d&v=v29#Roboto',
                    ttf: 'https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmYUtfChc9.ttf',
                    woff: 'https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmYUtfChc-.woff',
                    woff2:
                        'https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmYUtfBBc4.woff2',
                },
            },
        },
    },
    version: 'v29',
}
  1. The whole list of Google Fonts is 8MB. This is too big, so we need a way to only load specific fonts.
  2. Idea: Make a separate import statement for each font, e.g. import {loadFont} from "@remotion/google-fonts/Roboto" and import {loadFont} from "@remotion/google-fonts/Montserrat". For that we need to code-generate a TypeScript file for each font.
  3. If we can code-generate some TypeScript types based on the JSON...
type Variants = {
    normal: {
        weights: '400' | '500';
        subsets:  'greek';
    };
    bold: {
        weights: '600' | '700';
        subsets: 'cyrillic' | 'cyrillic-ext';
    };
};
  1. ...We can make an awesome autocomplete experience with it:

https://user-images.githubusercontent.com/1629785/195180349-8c84d55a-ceb5-4b7f-9171-3ca6aa3d9cf9.mov

  1. Using the following function signature:
const loadFont = <T extends keyof Variants>(
    variant: T,
    options: {
        weights: Variants[T]['weights'][];
        subsets: Variants[T]['subsets'][];
    }
) => void
  1. I have implemented an example function of how a Google font could be loaded from JSON:
const loadFont = <T extends keyof Variants>(
    variant: T,
    options: {
        weights: Variants[T]['weights'][];
        subsets: Variants[T]['subsets'][];
    }
) => {
    options.weights.forEach((weight) => {
        options.subsets.forEach((subset) => {
            const handle = delayRender(
                'Fetching Roboto font ' +
                    JSON.stringify({
                        variant,
                        weight: options.weights,
                        subset: options.subsets,
                    })
            );

            const fontFace = new FontFace(
                'Roboto',
                `url(${
                    data.variants[variant as unknown as keyof typeof data['variants']][
                        weight
                    ].url.woff2
                }) format('woff2')`,
                {
                    weight,
                    style: variant,
                    unicodeRange: data.unicodeRange[subset],
                }
            );
            fontFace
                .load()
                .then(() => {
                    document.fonts.add(fontFace);
                    continueRender(handle);
                })
                .catch((err) => {
                    console.log(err);
                });
        });
    });
};

Task

Create a @remotion/google-fonts package that allows to load each font with TypeScript autocompletion.

Acceptance criteria

  • Should write a script that takes the JSON from https://github.com/jonathantneal/google-fonts-complete and generates a file for each font
  • Developer should be able to import a single font using the import {loadFont} from "@remotion/google-fonts/Roboto" syntax, without importing the whole 8MB database
  • The font should be properly loaded, with only the styles and weights the user wants to be loaded
  • Should not do additional work if the user calls the same function multiple times
  • Write a new documentation page detailing how to exactly use this API, similar to API pages of other packages
  • Update the Fonts page https://www.remotion.dev/docs/fonts to mention this new option.

If you have a different implementation idea, let me know!


@remotion-dev has funded $279.00 to this issue.


posted by issuehunt-app[bot] over 2 years ago

Hi @JonnyBurger , can I take this issue instead of #1391? Because that issue needs #1377 to be done. Thanks.

posted by ayatkyo over 2 years ago

Yes! Assigned you šŸ˜

posted by JonnyBurger over 2 years ago

Thanks šŸ˜

I noticed that in https://github.com/jonathantneal/google-fonts-complete the font link provided only supports one font URL even though it has multiple subsets, while in actual Google Fonts CSS it uses different URLs for each subset.

For example Roboto (normal 500), when we generate using that script, we got:

{
  "Roboto": {
    // ...
    "variants": {
      // ...
      "normal": {
        // ...
        "500": {
          "local": [],
          "url": {
            "woff2": "https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmEU9fBBc4.woff2"
          }
        },
        // ...
      }
    },
    "version": "v30"
  }
}

And from Google Fonts CSS, we got:

/* cyrillic-ext */
@font-face {
  font-family: 'Roboto';
  font-style: normal;
  font-weight: 500;
  src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmEU9fCRc4EsA.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
  font-family: 'Roboto';
  font-style: normal;
  font-weight: 500;
  src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmEU9fABc4EsA.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
  font-family: 'Roboto';
  font-style: normal;
  font-weight: 500;
  src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmEU9fCBc4EsA.woff2) format('woff2');
  unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
  font-family: 'Roboto';
  font-style: normal;
  font-weight: 500;
  src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmEU9fBxc4EsA.woff2) format('woff2');
  unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
  font-family: 'Roboto';
  font-style: normal;
  font-weight: 500;
  src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmEU9fCxc4EsA.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Roboto';
  font-style: normal;
  font-weight: 500;
  src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmEU9fChc4EsA.woff2) format('woff2');
  unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Roboto';
  font-style: normal;
  font-weight: 500;
  src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmEU9fBBc4.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

Here is cleaned list to make it easier to compare:

# From Script:
'normal 500':   https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmEU9fBBc4.woff2

# Google Fonts CSS (normal 500):
'cyrillic-ext': https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmEU9fCRc4EsA.woff2
'cyrillic':     https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmEU9fABc4EsA.woff2
'greek-ext':    https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmEU9fCBc4EsA.woff2
'greek':        https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmEU9fBxc4EsA.woff2
'vietnamese':   https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmEU9fCxc4EsA.woff2
'latin-ext':    https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmEU9fChc4EsA.woff2
'latin':        https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmEU9fBBc4.woff2

So I think we need to create a custom parser to support loading font with different subsets.

posted by ayatkyo over 2 years ago

Interesting observation! Maybe on https://developers.google.com/fonts/docs/developer_api you can get the complete list as JSON?

On this site you can make a request through the console without having to give an API key.

posted by JonnyBurger over 2 years ago

Hey y'all,

I was battling with this too. This is a solution that works for me in <Player />, <IframePlayer /> and in server-side rendering.

In short the technique is to add a <style>-tag with the CSS @import directive, and then wait for the document font loading events before proceeding with the rendering.

What I like about the technique is that it's dirt simple, and I find the @import directive in CSS much easier to use than JavaScript-based options. Plus it's generalized... so not only Google Fonts. šŸ„³

I'm happy to contribute a PR of some sort with the technique.

import React, { useEffect, useState } from 'react';
import {
  continueRender,
  delayRender,
  useCurrentFrame,
  useVideoConfig,
} from 'remotion';

// utility, does nothing else than allow vs code to do CSS syntax highlighting when used
// Usage:
//   css`
//     .my-classname { background-color: red; }
//   `
export const css = (strings: TemplateStringsArray, ...values: any[]) => {
  let str = '';
  strings.forEach((string, i) => {
    str += string + (values[i] ?? '');
  });
  return str;
};

function logLoadedFonts() {
  const loadedFonts: string[] = [];
  document.fonts.forEach(({ family, variant, weight }) => {
    loadedFonts.push(`${family} ${weight} ${variant}`);
  });
  console.log(`Loaded fonts:\n${loadedFonts.join('\n')}`);
}

function useWaitForFontsToLoad() {
  const [waitForFontsHandle] = useState(() => delayRender());

  useEffect(() => {
    function handleLoadingDone() {
      logLoadedFonts();
      continueRender(waitForFontsHandle);
    }

    document.fonts.addEventListener('loadingdone', handleLoadingDone);

    return () => {
      document.fonts.removeEventListener('loadingdone', handleLoadingDone);
    };
  }, []);
}

export const MyVideo: React.FC<{ text: string }> = ({ text }) => {
  const frame = useCurrentFrame();
  const { width } = useVideoConfig();
  useWaitForFontsToLoad();

  const fontsCssSource = css`
    @import url('https://fonts.googleapis.com/css2?family=Bangers');
  `;

  const baseCss = css`
    /* somewhat stolen from https://github.com/sindresorhus/modern-normalize */
    *,
    ::before,
    ::after {
      box-sizing: border-box;
    }
    html {
      /* use a base size that scales with the composition width  */
      font-size: ${0.02 * width}px;
      line-height: 1.15;
      -webkit-text-size-adjust: 100%;
      -moz-tab-size: 4;
      tab-size: 4;
    }
    body {
      margin: 0;
      font-family: system-ui,
        -apple-system /* Firefox supports this but not yet \`system-ui\` */,
        'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji',
        'Segoe UI Emoji';
    }
  `;

  return (
    <>
      <style>{fontsCssSource}</style>
      <style>{baseCss}</style>
      <div
        style={{
          backgroundColor: 'lightblue',
          flex: 1,
          textAlign: 'center',
          fontSize: '3em',
          fontFamily: 'Bangers',
        }}
      >
        <p>{text}</p>
        <p>The current frame is {frame}.</p>
      </div>
    </>
  );
};
posted by marcusstenbeck over 2 years ago

So I think we need to create a custom parser to support loading font with different subsets.

If you want to go down the parser rabbit hole I suggest having a look at peggy. It's a library for creating parsers. šŸ¤“

posted by marcusstenbeck over 2 years ago

Interesting observation! Maybe on https://developers.google.com/fonts/docs/developer_api you can get the complete list as JSON?

On this site you can make a request through the console without having to give an API key.

Yes, actually I use that to get the list of available fonts, but unfortunately the API only provides us with .ttf font and also no Unicode ranges.

I have completed the .ts font generator and will provide an example repo for testing here soon.

posted by ayatkyo over 2 years ago

@marcusstenbeck Thanks for sharing the code, it really helpful.

For the current CSS parser, I use PostCSS. But the peggy package looks interesting, I will definitely try it out in some projects.

posted by ayatkyo over 2 years ago

You are right, the Google Developer API does not seem fit, I also don't see a way to get the WOFF files. Glad to hear you are making progress!

I will investigate what Marcus said soon.

posted by JonnyBurger over 2 years ago

Hi, I have created an example repo at https://github.com/ayatkyo/remotion-google-fonts-example

Use pnpm generate-fonts to generate all google fonts as .ts files. The font metadata is included in generated .ts file so we don't need to parse a big JSON file.

I also export some other things besides loadFont for easy use. For example, instead of typing the font family name manually, we can use the exported family.

import { loadFont } from './fonts/Roboto';

loadFont('normal', {
  weights: ['400', '600', '700', '800'],
  subsets: ['latin'],
});

let textStyle: React.CSSProperties = {
  fontFamily: 'Roboto',
  // ...
};

We can use:

import { loadFont, family } from './fonts/Roboto';

loadFont('normal', {
  weights: ['400', '600', '700', '800'],
  subsets: ['latin'],
});

let textStyle: React.CSSProperties = {
  fontFamily: family,
  // ...
};

Also for using multiple fonts in the same file, we should use like this:

import * as TitanOne from './fonts/TitanOne';
import * as Pacifico from './fonts/Pacifico';

TitanOne.loadFont('normal', {
  weights: ['400'],
  subsets: ['latin'],
});
Pacifico.loadFont('normal', {
  weights: ['400'],
  subsets: ['latin'],
});
posted by ayatkyo over 2 years ago

Should not do additional work if the user calls the same function multiple times

For this feature, I have tried checking all loaded fonts from document.fonts like this when call loadFonts:

for (const weight of options.weights) {
  for (const subset of options.subsets) {
    //  ...

    //  Check is already loaded
    let hasLoaded = false;
    for (const docfont of document.fonts.values()) {
      if (docfont.family == meta.family &&
        docfont.style == style &&
        docfont.weight == weight &&
        docfont.unicodeRange == meta.unicodeRanges[subset]) {
        hasLoaded = true;
        break;
      }
    }

    if (hasLoaded) {
      console.log(`Skip '${meta.family}' style:${style} weight:${weight} subset:${subset}, font already loaded`);
      continue;
    }

    //  ...
  }
}

But I don't know if this is the right solution.

I also add more conditions like docfont.status == 'loaded', but if we call loadFont() twice, the second loadFont() call is not waiting for the first loadFont() call, so the condition remains false because the status is not loaded.

posted by ayatkyo over 2 years ago

@ayatkyo This looks pretty promising!

Are you getting a lot of ECONNRESET errors as well when running pnpm generate-fonts? It looks like it's generating all fonts at once...

Once you are comfortable with a prototype, please send a PR to this repository, make a new folder in packages, I can help you make it integrate properly with the monorepo.

I agree with the API in your first comment, looks very good!

For checking whether the font has already been loaded, I was thinking of just making a unique key for each font style and keep an object in module scope and return if it's already been called before. For example:

const fontsLoaded = {
  'Montserrat-400-latin-italic': true
}

// ...

const loadFont = () => {
    const key = makeKey({weight, fontFamily, script})
    if (fontsLoaded[key]) {
        return 
    }
}

Does that seem sound?

posted by JonnyBurger over 2 years ago

Are you getting a lot of ECONNRESET errors as well when running pnpm generate-fonts? ...

@JonnyBurger Unfortunately I don't get ECONNRESET error, I also tested on my server and don't get the error. Maybe something to do with the connection or User-Agent that is used in the script.

Can you access the links using a browser?

It looks like it's generating all fonts at once...

Yes, the script will generate the fonts in a batch with max 100 requests will run concurrently. So it won't need to wait previous request to complete.

For checking whether the font has already been loaded, I was thinking of just making a unique key for each font style and keep an object in module scope and return if it's already been called before.

That sounds good, I have implemented that and its works well.

For the package, what should be included as the package contents, is it only contain the generated font (.ts) or together with the script to generate it?

If we want to access it like "@remotion/google-fonts/FontName", I think we should put it in the root directory of the package, right?

Also maybe we can automatically generate the fonts using GitHub Action.

posted by ayatkyo over 2 years ago

@ayatkyo Sorry for the late response...

Yes, the script will generate the fonts in a batch with max 100 requests will run concurrently. So it won't need to wait previous request to complete.

I think that's the reason why I get ECONNRESET errors on macOS! Maybe it's good to lower the limit to around 10.

That sounds good, I have implemented that and its works well.

Nice!

For the package, what should be included as the package contents, is it only contain the generated font (.ts) or together with the script to generate it?

Let's also include the script, but we can use .npmignore to only ship the fonts.

If we want to access it like "@remotion/google-fonts/FontName", I think we should put it in the root directory of the package, right?

Correct, we need to ship a .js file and a .d.ts file, like this: https://github.com/remotion-dev/remotion/blob/873e59cf972b855c1ecb98e99ae459fce02b2de9/packages/core/version.js#L2 and https://github.com/remotion-dev/remotion/blob/873e59cf972b855c1ecb98e99ae459fce02b2de9/packages/core/version.d.ts#L2

This enables an import like import version from "remotion/version"

Also maybe we can automatically generate the fonts using GitHub Action.

We could, I'd say for simplicity this is not required for now, unless you feel motivated to do so šŸ˜

posted by JonnyBurger over 2 years ago

@JonnyBurger Thanks for the response, I have created a Draft PR and changed the generator limit to 10.

We could, I'd say for simplicity this is not required for now, unless you feel motivated to do so šŸ˜

Good, I will create that in a separate PR after completing this. šŸ˜

posted by ayatkyo over 2 years ago

@jonnyburger has rewarded $251.10 to @ayatkyo. See it on IssueHunt

  • :moneybag: Total deposit: $279.00
  • :tada: Repository reward(0%): $0.00
  • :wrench: Service fee(10%): $27.90
posted by issuehunt-app[bot] over 2 years ago

@JonnyBurger Are there any limitations with this when running on AWS lambda? The preview shows up correctly on the localhost:3000 preview page, but when running on lambda it is not using the correct font.

posted by Leland-Takamine over 2 years ago

@Leland-Takamine I would be surprised if it did not work on Lambda, are you aware that you need to redeploy your site to S3 when making a change?

posted by JonnyBurger over 2 years ago

@JonnyBurger Ah ok I think I mistakenly thought that the font was not loading correctly because the output looked so different in the rendered video output. Note the spacing between lines and different kerning (top=preview, bottom=rendered video):

<img width="870" alt="image" src="https://user-images.githubusercontent.com/847683/202577869-3a3b93bd-b5c6-4f02-842b-146b6678a2dc.png">

Is this just due to a difference between browsers being used in each situation?

(btw rendering locally seems to render correctly with no discrepancies with the preview)

posted by Leland-Takamine over 2 years ago

I think that is the difference between macOS and Linux rendering unfortunately šŸ˜‘

Maybe a Chrome flag can help, if you let me know the font name, I will do a quick test. But it looks like while one font is thinner than the other and the kerning is bad, it is the same font.

posted by JonnyBurger over 2 years ago

Got it ok thanks for context! The font is Roboto Mono

posted by Leland-Takamine over 2 years ago

@Leland-Takamine Sorry for the slow response in investigating it! Unfortunately, I can confirm the issue and do not have found a solution by trying out various Chrome flags and the method described here: https://blog.richardkeller.net/wkhtmltopdf-bad-kerning-on-aws-lambda/

So it seems like either a Linux or Chrome behavior out of my control :(

posted by JonnyBurger over 2 years ago

Fund this Issue

$279.00
Rewarded

Rewarded pull request

Recent activities

ayatkyo was rewarded by jonnyburger for remotion-dev/remotion# 1394
over 2 years ago
jonnyburger submitted an output to  remotion-dev/ remotion# 1394
over 2 years ago