Resolving fonts
Weights, styles, subsets and formats, and the bit that catches people out.
resolveFont() takes a family, some options, and optionally a list of providers to try.
const { fonts, fallbacks, provider } = await unifont.resolveFont('Poppins', {
weights: ['400', '700'],
styles: ['normal', 'italic'],
subsets: ['latin', 'latin-ext'],
formats: ['woff2'],
})Every option is a hint, not a guarantee you'll get back exactly that. For example, if you ask for otf from a provider that only serves woff2, you'll get woff2.
Weights
Default: ['400'].
Discrete weights are strings. A variable range is one string with a space: '100 900'.
await unifont.resolveFont('Poppins', { weights: ['300', '500 900'] })If no variable font covers the range you asked for, unifont won't expand that range into every published weight. It picks a few: the weights at each end, plus whichever is nearest 400, so body text still looks right. '500 900' on a static family gives you two or three faces, not five.
If no published weight falls inside the range at all ('450 480' on a family that ships 400 and 500) you get the nearest weight rather than nothing.
List the weights you want if you need the ones in between:
await unifont.resolveFont('Poppins', { weights: ['500', '600', '700', '800', '900'] })Styles
Default: ['normal', 'italic'].
await unifont.resolveFont('Poppins', { styles: ['normal'] })Worth narrowing. The default doubles the number of files, for a page that may never use italic.
Subsets
Default: ['cyrillic-ext', 'cyrillic', 'greek-ext', 'greek', 'vietnamese', 'latin-ext', 'latin'].
await unifont.resolveFont('Poppins', { subsets: ['latin'] })Subsets make the biggest difference to how much you send, and they're the easiest to get wrong. Drop latin-ext and you lose characters that Polish, Czech and Turkish need. Every family page on this site shows a coverage check against sample text, so you can see what narrower subsets cost.
Providers that don't split by subset, like Fontshare and most of npm, ignore this option and return whole files with no unicode-range.
Formats
Default: ['woff2'].
await unifont.resolveFont('Poppins', { formats: ['woff2', 'woff'] })Every browser has supported woff2 for years. Only ask for more if you know why you need it.
Variable axes
Weight and style aside, a variable family can publish any number of axes: optical size (opsz), width (wdth), slant (slnt), and custom ones like Recursive's CASL and MONO. variableAxis asks for values along them, keyed by axis tag:
await unifont.resolveFont('Recursive', {
weights: ['300 1000'],
variableAxis: {
CASL: [1],
slnt: [{ min: -15, max: 0 }],
},
})A number is one value, and { min, max } is an inclusive range, which you can also write as a [min, max] pair. Strings are accepted everywhere a number is. getFontProperties() reports the axes a family publishes, so you can check what's available before asking:
const properties = await unifont.getFontProperties('Recursive')
properties?.axes // [{ tag: 'CASL', min: 0, max: 1, defaultValue: 0 }, …]What that buys you depends on the provider, and on the family. Google instances the font file itself, so the file you get back contains only the axes and values you asked for, and out-of-range values are clamped to what the family publishes. An axis a family doesn't have is dropped. Other providers serve the whole variable file, so unifont sets font-variation-settings on the resolved faces instead: the file is the same size, but the text renders at the axis values you asked for. A face that already carries a descriptor keeps the tags you didn't ask about.
The fallback applies an axis only when you name a single value for it. A range, or several values for one axis, needs either a provider that can instance the file or a subsetting step of your own.
The result records what happened to each axis you asked for, so a tool that processes font files can act on what's left:
const { variableAxis } = await unifont.resolveFont('Recursive', {
variableAxis: { CASL: [1], slnt: [{ min: -15, max: 0 }] },
})
variableAxis
// {
// CASL: { values: ['1'], appliedAs: 'variation-settings' },
// slnt: { values: [['-15', '0']], appliedAs: 'none' },
// }appliedAs is font-file when the file itself is instanced or limited to those values, variation-settings when they're in the font-variation-settings descriptor, and none when they're left for you, either because the family doesn't publish the axis or because a single font face can't express what you asked for. It's reported per axis, so one request can come back with all three. Values come back normalised: a single value as a string, a range as a [min, max] pair. The whole field is undefined when you didn't ask for any axes.
unifont writes font-variation-settings as "<tag>" <value>, comma separated, so a consumer that instances an appliedAs: 'variation-settings' axis itself can drop that tag from the descriptor.
Per-family provider options
Some providers accept options for a single family. They're grouped by provider name, and typed from the providers you passed to createUnifont():
const { fonts } = await unifont.resolveFont('Poppins', {
options: {
google: {
experimental: { glyphs: ['Hello', 'World'] },
},
},
})What comes back
interface FontFaceData {
src: Array<{ name: string } | { url: string, format?: string, tech?: string }>
display?: 'auto' | 'block' | 'swap' | 'fallback' | 'optional'
weight?: string | number | [number, number]
stretch?: string
style?: string
unicodeRange?: string[]
featureSettings?: string
variationSettings?: string
meta?: { priority?: number, subset?: string, init?: RequestInit }
}A src entry with name is a local font. One with url is remote. meta.subset is the provider's own name for the range.
meta.init carries a RequestInit if the provider needs headers to fetch the file. Pass it through if you're downloading the font rather than linking to it.