An open API service indexing awesome lists of open source software.

https://github.com/unjs/unifont

Framework agnostic tools for accessing data from font CDNs and providers
https://github.com/unjs/unifont

font performance

Last synced: 5 months ago
JSON representation

Framework agnostic tools for accessing data from font CDNs and providers

Awesome Lists containing this project

README

          

# unifont

[![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]

> Framework agnostic tools for accessing data from font CDNs and providers.

## Installation

Using npm:

```
npm i unifont
```

Using pnpm:

```
pnpm add unifont
```

Using yarn:

```
yarn add unifont
```

## Getting started

This package is ESM-only.

```js
import { createUnifont, providers } from 'unifont'

const unifont = await createUnifont([
providers.google(),
])

const availableFonts = await unifont.listFonts()
const { fonts } = await unifont.resolveFont('Poppins')
```

## Built-in providers

The following providers are built-in but you can build [custom providers](#building-your-own-provider) too.

### Adobe

A provider for [Adobe Fonts](https://fonts.adobe.com/).

```js
import { providers } from 'unifont'

providers.adobe({ /* options */ })
```

#### Options

##### `id`

- Type: `string | string[]`
- Required

```js
import { providers } from 'unifont'

providers.adobe({ id: 'your-id' })
providers.adobe({ id: ['foo', 'bar'] })
```

It is recommended to load these IDs as environment variables.

### Bunny

A provider for [Bunny Fonts](https://fonts.bunny.net/).

```js
import { providers } from 'unifont'

providers.bunny()
```

### Fontshare

A provider for [Fontshare](https://www.fontshare.com/).

```js
import { providers } from 'unifont'

providers.fontshare()
```

### Fontsource

A provider for [Fontsource](https://fontsource.org/).

```js
import { providers } from 'unifont'

providers.fontsource()
```

It uses the API, not installed NPM packages (see [PR #189](https://github.com/unjs/unifont/pull/189)).

### Google

A provider for [Google Fonts](https://fonts.google.com/).

```js
import { providers } from 'unifont'

providers.google()
```

#### Options

##### `experimental.variableAxis`

- Type: `{ [fontFamily: string]: Partial> }`

Allows setting variable axis configuration on a per-font basis:

```js
import { providers } from 'unifont'

providers.google({
experimental: {
variableAxis: {
Poppins: {
slnt: [['-15', '0']],
CASL: [['0', '1']],
CRSV: ['1'],
MONO: [['0', '1']],
},
},
},
})
```

Overriden by the `experimental.variableAxis` family option.

##### `experimental.glyphs`

- Type: `{ [fontFamily: string]: string[] }`

Allows specifying a list of glyphs to be included in the font for each font family. This can reduce the size of the font file:

```js
import { providers } from 'unifont'

providers.google({
experimental: {
glyphs: {
Poppins: ['Hello', 'World']
},
},
})
```

Overriden by the `experimental.glyphs` family option.

#### Family options

##### `experimental.variableAxis`

- Type: `Partial>`

Allows setting variable axis configuration on a per-font basis:

```js
import { createUnifont, providers } from 'unifont'

const unifont = await createUnifont([
providers.google(),
])

const { fonts } = await unifont.resolveFont('Poppins', {
options: {
google: {
experimental: {
variableAxis: {
slnt: [['-15', '0']],
CASL: [['0', '1']],
CRSV: ['1'],
MONO: [['0', '1']],
},
},
},
},
})
```

##### `experimental.glyphs`

- Type: `string[]`

Allows specifying a list of glyphs to be included in the font for each font family. This can reduce the size of the font file:

```js
import { createUnifont, providers } from 'unifont'

const unifont = await createUnifont([
providers.google(),
])

const { fonts } = await unifont.resolveFont('Poppins', {
options: {
google: {
experimental: {
glyphs: ['Hello', 'World'],
},
},
},
})
```

### Google icons

A provider for [Google Icons](https://fonts.google.com/icons).

```js
import { providers } from 'unifont'

providers.googleicons()
```

#### Options

##### `experimental.glyphs`

- Type: `{ [fontFamily: string]: string[] }`

Allows specifying a list of glyphs to be included in the font for each font family. This can reduce the size of the font file:

```js
import { providers } from 'unifont'

providers.googleicons({
experimental: {
glyphs: {
'Material Symbols Outlined': ['arrow_right', 'favorite', 'arrow_drop_down']
},
},
})
```

Only available when resolving the new `Material Symbols` icons. Overriden by the `experimental.glyphs` family option.

#### Family options

##### `experimental.glyphs`

- Type: `string[]`

Allows specifying a list of glyphs to be included in the font for each font family. This can reduce the size of the font file:

```js
import { createUnifont, providers } from 'unifont'

const unifont = await createUnifont([
providers.googleicons(),
])

const { fonts } = await unifont.resolveFont('Poppins', {
options: {
googleicons: {
experimental: {
'Material Symbols Outlined': ['arrow_right', 'favorite', 'arrow_drop_down']
},
},
},
})
```

Only available when resolving the new `Material Symbols` icons.

## `Unifont`

Use `createUnifont()` to create a `Unifont` instance. It requires an array of font providers at this first parameter:

```js
import { createUnifont, providers } from 'unifont'

const unifont = await createUnifont([
providers.google(),
])
```

### Options

`createUnifont()` accepts options as its 2nd parameter.

#### `storage`

- `Type`: `Storage`

Allows caching the results of font APIs to avoid unnecessary hits to them. Uses a memory cache by default.

This storage type is compatible with [`unstorage`](https://unstorage.unjs.io.):

```ts
import { createUnifont, providers } from 'unifont'
import { createStorage } from 'unstorage'
import fsDriver from 'unstorage/drivers/fs-lite'

const storage = createStorage({
driver: fsDriver({ base: 'node_modules/.cache/unifont' }),
})

const unifont = await createUnifont([
providers.google()
], { storage })

// cached data is stored in `node_modules/.cache/unifont`
await unifont.resolveFont('Poppins')
```

#### `throwOnError`

- Type: `boolean`

Allows throwing on error if a font provider:

- Fails to initialize
- Fails while calling `resolveFont()`
- Fails while calling `listFonts()`

If set to `false` (default), an error will be logged to the console instead:

```ts
import { createUnifont, providers } from 'unifont'

const unifont = await createUnifont([
providers.google()
], { throwOnError: true })
```

### Methods

#### `resolveFont()`

- Type: `(fontFamily: string, options?: Partial, providers?: T[]) => Promise`

Retrieves font face data from available providers:

```js
import { createUnifont, providers } from 'unifont'

const unifont = await createUnifont([
providers.google(),
providers.fontsource(),
])

const { fonts } = await unifont.resolveFont('Poppins')
```

It loops through all providers and returns the result of the first provider that can return some data.

##### Options

It accepts options as the 2nd parameter. Each provider chooses to support them or not.

###### `weights`

- Type: `string[]`
- Default: `['400']`

Specifies what weights to retrieve. Variable weights must me in the format ` `:

```js
import { createUnifont, providers } from 'unifont'

const unifont = await createUnifont([
providers.google(),
])

const { fonts } = await unifont.resolveFont('Poppins', {
weights: ['300', '500 900']
})
```

###### `styles`

- Type: `('normal' | 'italic' | 'oblique')[]`
- Default: `['normal', 'italic']`

Specifies what styles to retrieve:

```js
import { createUnifont, providers } from 'unifont'

const unifont = await createUnifont([
providers.google(),
])

const { fonts } = await unifont.resolveFont('Poppins', {
styles: ['normal']
})
```

###### `subsets`

- Type: `string[]`
- Default: `['cyrillic-ext', 'cyrillic', 'greek-ext', 'greek', 'vietnamese', 'latin-ext', 'latin']`

Specifies what subsets to retrieve:

```js
import { createUnifont, providers } from 'unifont'

const unifont = await createUnifont([
providers.google(),
])

const { fonts } = await unifont.resolveFont('Poppins', {
subsets: ['latin']
})
```

###### `options`

- Type: `{ [key: string]?: Record }`

A provider can define options to provide on a font family basis. Types will be automatically inferred:

```js
import { createUnifont, providers } from 'unifont'

const unifont = await createUnifont([
providers.google(),
])

const { fonts } = await unifont.resolveFont('Poppins', {
options: {
google: {
experimental: {
glyphs: ['Hello', 'World']
}
}
},
})
```

###### `formats`

- Type: `('woff2' | 'woff' | 'otf' | 'ttf' | 'eot')[]`
- Default: `['woff2']`

Specifies what font formats to retrieve:

```js
import { createUnifont, providers } from 'unifont'

const unifont = await createUnifont([
providers.google(),
])

const { fonts } = await unifont.resolveFont('Poppins', {
formats: ['woff2', 'woff2']
})
```

##### Providers

- Type: `string[]`

By default it uses all the providers provided to `createUnifont()`. However you can restrict usage to only a subset:

```js
import { createUnifont, providers } from 'unifont'

const unifont = await createUnifont([
providers.google(),
providers.fontsource(),
])

const { fonts } = await unifont.resolveFont('Poppins', {}, ['google'])
```

#### `listFonts()`

- Type: `(providers?: T[]) => Promise`

Retrieves font names available for all providers:

```js
import { createUnifont, providers } from 'unifont'

const unifont = await createUnifont([
providers.google(),
])

const availableFonts = await unifont.listFont()
```

It may return `undefined` if no provider is able to return names.

##### Providers

- Type: `string[]`

By default it uses all the providers provided to `createUnifont()`. However you can restrict usage to only a subset:

```js
import { createUnifont, providers } from 'unifont'

const unifont = await createUnifont([
providers.google(),
providers.fontsource(),
])

const availableFonts = await unifont.listFont(['google'])
```

## Building your own provider

### Defining a provider

To build your own font provider, use the `defineFontProvider()` helper:

```ts
import { defineFontProvider } from 'unifont'

export const myProvider = defineFontProvider(/* ... */)
```

It accepts a unique name as a first argument and a callback function as 2nd argument:

```ts
import { defineFontProvider } from 'unifont'

export const myProvider = defineFontProvider('my-provider', async (options, ctx) => {
// ...
})
```

If you use options, you can simply annotate it:

```ts
import { defineFontProvider } from 'unifont'

export interface MyProviderOptions {
foo?: string
}

export const myProvider = defineFontProvider('my-provider', async (options: MyProviderOptions, ctx) => {
// ...
})
```

The context (`ctx`) gives access to the [`storage`](#storage), allowing you to cache results. We'll see how below.

### Initialization

The callback runs when a `Unifont` instance is created. It is used for initialization logic, such as fetching the list of available fonts:

```ts
import { defineFontProvider } from 'unifont'

export const myProvider = defineFontProvider('my-provider', async (options, ctx) => {
const fonts: { name: string, cssUrl: string }[] = await ctx.storage.getItem('my-provider:meta.json', () => fetch('https://api.example.com/fonts.json').then(res => res.json()))

// ...
})
```

You can now use this data in the methods.

### `listFonts()`

While optional, it's easy to implement this method now that we have the full list:

```ts
import { defineFontProvider } from 'unifont'

export const myProvider = defineFontProvider('my-provider', async (options, ctx) => {
const fonts: { name: string, cssUrl: string }[] = [/* ... */]

return {
listFonts() {
return fonts.map(font => font.name)
}
// ...
}
})
```

### `resolveFont()`

This is where most of the logic lies. It depends a lot on how the provider works, and often involves parsing CSS files. Have a look at the implementation of built-in providers for inspiration!

```ts
import { hash } from 'ohash'
import { defineFontProvider } from 'unifont'

export const myProvider = defineFontProvider('my-provider', async (options, ctx) => {
const fonts: { name: string, cssUrl: string }[] = [/* ... */]

return {
// ...
async resolveFont(fontFamily, options) {
const font = fonts.find(font => font.name === fontFamily)
if (!font) {
return
}

return {
fonts: await ctx.storage.getItem(`my-provider:${fontFamily}-${hash(options)}-data.json`, async () => {
// Fetch an API, extract CSS...
return [/* ... */]
})
}
}
}
})
```

If you use family options, you can override the type of `options` and it will be inferred:

```ts
import type { ResolveFontOptions } from 'unifont'
import { hash } from 'ohash'
import { defineFontProvider } from 'unifont'

export interface MyProviderFamilyOptions {
foo?: string
}

export const myProvider = defineFontProvider('my-provider', async (options, ctx) => {
// ...

return {
// ...
async resolveFont(fontFamily, options: ResolveFontOptions) {
// ...
}
}
})
```

## 💻 Development

- Clone this repository
- Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable`
- Install dependencies using `pnpm install`
- Run interactive tests using `pnpm dev`

## License

Made with ❤️

Published under [MIT License](./LICENCE).

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