Fontaine

Automatic font fallback based on font metrics

README

fontaine

[![npm version][npm-version-src]][npm-version-href] [![npm downloads][npm-downloads-src]][npm-downloads-href] [![Github Actions][github-actions-src]][github-actions-href] [![Codecov][codecov-src]][codecov-href]

Automatic font fallback based on font metrics



Features


- 💪 Reduces CLS by using local font fallbacks with crafted font metrics.
- ✨ Generates font metrics and overrides automatically.
- ⚡️ Pure CSS, zero runtime overhead.

On the playground project, enabling/disabling fontaine makes the following difference rendering /, with no customisation required:

|After
-----------------
CLS`0.24`
Performance`92`

Installation


With pnpm

  1. ``` sh
  2. pnpm add -D fontaine
  3. ```

Or, with npm

  1. ``` sh
  2. npm install -D fontaine
  3. ```

Or, with yarn

  1. ``` sh
  2. yarn add -D fontaine
  3. ```

Usage


  1. ``` js
  2. import { FontaineTransform } from 'fontaine'

  3. const options = {
  4.   fallbacks: ['BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Arial', 'Noto Sans'],
  5.   // You may need to resolve assets like `/fonts/Roboto.woff2` to a particular directory
  6.   resolvePath: (id) => 'file:///path/to/public/dir' + id,
  7.   // overrideName: (originalName) => `${name} override`
  8.   // sourcemap: false
  9. }

  10. // Vite
  11. export default {
  12.   plugins: [FontaineTransform.vite(options)]
  13. }

  14. // Next.js
  15. export default {
  16.   webpack(config) {
  17.     config.plugins = config.plugins || []
  18.     config.plugins.push(FontaineTransform.webpack(options))
  19.     return config
  20.   },
  21. }

  22. // Docusaurus plugin - to be provided to the plugins option of docusaurus.config.js
  23. // n.b. you'll likely need to require fontaine rather than importing it
  24. const fontaine = require('fontaine')

  25. function fontainePlugin(_context, _options) {
  26.   return {
  27.     name: 'fontaine-plugin',
  28.     configureWebpack(_config, _isServer) {
  29.       return {
  30.         plugins: [
  31.           fontaine.FontaineTransform.webpack(options),
  32.         ],
  33.       };
  34.     },
  35.   };
  36. }

  37. // Gatsby config - gatsby-node.js
  38. const { FontaineTransform } = require('fontaine')

  39. exports.onCreateWebpackConfig = ({ stage, actions, getConfig }) => {
  40.   const config = getConfig()
  41.   config.plugins.push(FontaineTransform.webpack(options))
  42.   actions.replaceWebpackConfig(config)
  43. }
  44. ```

Note

If you are using Nuxt, check out nuxt-font-metrics which usesfontaine under the hood.


If your custom font is used through the mechanism of CSS variables, you'll need to make a tweak to your CSS variables to give fontaine a helping hand. Docusaurus is an example of this, it uses the --ifm-font-family-base variable to reference a custom font. In order that fontaine can connect the variable with the font, we need to add a {Name of Font} override suffix to that variable. What does this look like? Well imagine we were using the custom font Poppins which is referenced from the --ifm-font-family-base variable, we'd make the following adjustment:

  1. ```diff
  2. :root {
  3.   /* ... */
  4. -  --ifm-font-family-base: 'Poppins';
  5. +  --ifm-font-family-base: 'Poppins', 'Poppins override';
  6. ```

Behind the scenes, there is a 'Poppins override' @font-face rule that has been created by fontaine. By manually adding this override font family to our CSS variable, we make our site use the fallback @font-face rule with the correct font metrics that fontaine generates.

How it works


fontaine will scan your @font-face rules and generate fallback rules with the correct metrics. For example:

  1. ```css
  2. @font-face {
  3.   font-family: 'Roboto';
  4.   font-display: swap;
  5.   src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff')
  6.       format('woff');
  7.   font-weight: 700;
  8. }
  9. /* This additional font-face declaration will be added to your CSS. */
  10. @font-face {
  11.   font-family: 'Roboto override';
  12.   src: local('BlinkMacSystemFont'), local('Segoe UI'), local('Helvetica Neue'),
  13.       local('Arial'), local('Noto Sans');
  14.   ascent-override: 92.7734375%;
  15.   descent-override: 24.4140625%;
  16.   line-gap-override: 0%;
  17. }
  18. ```

Then, whenever you use font-family: 'Roboto', fontaine will add the override to the font-family:

  1. ```css
  2. :
  3.   font-family: 'Roboto';
  4.   /* This becomes */
  5.   font-family: 'Roboto', 'Roboto override';
  6. }
  7. ```

💻 Development


- Clone this repository
- Enable Corepack usingcorepack enable (use npm i -g corepack for Node.js < 16.10)
- Install dependencies using pnpm install
- Run interactive tests using pnpm dev; launch a vite server using source code with pnpm demo:dev

Credits


This would not have been possible without:

- amazing tooling and generated metrics from capsizecss
- suggestion and algorithm from Katie Hempenius & Kara Erickson on the Google Aurora team - see notes on calculating font metric overrides
- package name suggestion from [@clemcode](https://github.com/clemcode)

License


Made with ❤️

Published under MIT License.



[npm-version-src]: https://img.shields.io/npm/v/fontaine?style=flat-square
[npm-version-href]: https://npmjs.com/package/fontaine
[npm-downloads-src]: https://img.shields.io/npm/dm/fontaine?style=flat-square
[npm-downloads-href]: https://npmjs.com/package/fontaine
[github-actions-src]: https://img.shields.io/github/workflow/status/unjs/fontaine/ci/main?style=flat-square
[github-actions-href]: https://github.com/unjs/fontaine/actions?query=workflow%3Aci
[codecov-src]: https://img.shields.io/codecov/c/gh/unjs/fontaine/main?style=flat-square
[codecov-href]: https://codecov.io/gh/unjs/fontaine