unifont

repository·main·Indexed 43 days ago

https://github.com/unjs/unifont

A framework-agnostic toolset for accessing font metadata from various CDNs and providers, such as Google Fonts, Adobe, and Fontshare, using a unified API. It normalizes provider response formats into a consistent shape containing weights, styles, subsets, and @font-face data. Version 1.0.0-beta.0 is ESM-only and designed for use in Node.js, Bun, Deno, Workers, and the browser via a CORS proxy.

Tokens
21.8K
Snippets
75
Records
98
Agent score
52%

What's inside unifont

  1. What is unifont?

    main

    unifont is an open-source JavaScript library designed to provide a unified interface for reading font metadata from various font CDNs. Instead of dealing with the different response formats of Google Fonts (stylesheets), Fontshare (JSON), Fontsource (npm), or Adobe (Typekit IDs), you can use unifont to request a font family and receive a standardized response containing weights, styles, subsets, unicode ranges, and @font-face data.

    Key Characteristics:

    • Small footprint: It only reads metadata; it does not download font files, perform subsetting, manage build pipelines, or handle caching.
    • Standardized Output: Regardless of the provider, the metadata shape remains consistent.
    • Usage: It is ideal for scripts, editor plugins, and CI checks. For automated font downloading and self-hosting in Nuxt or Vite apps, consider using @nuxt/fonts or fontless instead.
  2. Using the provider context (ctx)

    main

    The ctx object is passed to your provider and provides two essential primitives:

    1. ctx.fetch: A fetch wrapper that retries failures. Note that unifont rewrites built-in provider URLs to use the configured apiBase. Custom provider endpoints are not rewritten; ensure your API handles CORS if running in a browser.
    2. ctx.storage.getItem(key, init): A self-filling cache. Pass a key and an initialization function. The function runs once, and subsequent calls read from the configured storage.
    export const foundry = defineFontProvider('foundry', async (options: FoundryOptions, ctx) => {
      const families = await ctx.storage.getItem('foundry:index.json', () =>
        ctx.fetch('https://api.foundry.example/families').then(res => res.json()),
      )
      // …
    })
  3. How unifont handles CORS in browsers and web containers

    main

    Most font provider APIs (like Google Fonts) do not send CORS headers, which causes requests to fail when executed directly from a browser or web containers like StackBlitz.

    unifont automatically detects if it is running in a browser environment. If so, it transparently rewrites provider metadata requests to go through a hosted proxy. This allows the same code to work in both Node.js environments (direct calls) and browser environments (proxied calls) without manual configuration.

    Note: This proxying only applies to provider metadata endpoints. Font files, npm CDNs, and custom provider APIs are still fetched directly.

    import { createUnifont, providers } from 'unifont'
    
    // In a browser or a StackBlitz web container, this automatically goes via the proxy.
    const unifont = await createUnifont([providers.google()])
  4. Understand what unifont caches

    main

    Providers in unifont cache two distinct types of data through the provided storage interface:

    1. Family Index: Large datasets generated at startup (e.g., listFonts data). This data changes very infrequently.
    2. Per-family Results: Smaller datasets keyed by the specific options passed to the request. While individual entries are small, there can be many of them.

    Performance Tip: Always reuse the unifont instance created by createUnifont(). Initializing a new instance per request forces providers to re-fetch the entire family index, negating the benefits of the cache.

  5. How to use unifont in a browser (CORS workaround)

    main

    In a browser or web container, CORS restrictions prevent direct access to most provider APIs (except for npm). To resolve this, unifont can send requests through a proxy.

    Warning: The default proxy https://proxy.unifont.dev is experimental and not for production use. It is intended for playgrounds and reproductions. It may be rate-limited or change without notice.

    Production Recommendation: For production environments, you should deploy the proxy/ package yourself and point the apiBase option to your deployed instance.

  6. Understand the privacy implications of using the hosted proxy

    main

    When using unifont in a browser or web container, requests to provider APIs are routed through a hosted proxy because those providers do not send CORS headers.

    Important Privacy Note: When using the hosted proxy, your requests pass through the unifont.dev server. While the site does not track users or use cookies, the proxy forwards your requests to the font providers.

    If you require complete privacy or want to avoid passing requests through the unifont.dev infrastructure, you should run your own instance of the proxy instead of using the hosted one.

  7. Understand the unifont mental model

    main

    Core Purpose

    unifont acts as a single abstraction layer for various font providers (Google, Fontshare, etc.). It normalizes the different ways these providers return data so you receive a consistent response regardless of the source.

    What it does NOT do

    • Download fonts: It only reads metadata.
    • Subset fonts: It does not perform font subsetting.
    • Generate CSS: You must use the returned @font-face data to build your own CSS.
    • Caching: It does not manage a cache for you.

    Runtime Support

    unifont is designed to run in multiple environments:

    • Node.js
    • Bun
    • Deno
    • Workers
    • The Browser (Note: In browsers, you must use a proxy to bypass CORS restrictions).
  8. Proxy behavior and limitations

    main

    The proxy is designed to be a narrow, read-only intermediary. Its behavior is governed by the following rules:

    • Methods: Only GET and HEAD requests are supported. All other methods return a 404.
    • Query Parameters: Only the specific query parameters defined for each route are passed to the upstream provider; all other parameters are dropped.
    • Headers: Only the upstream content-type is returned. No other upstream headers are replayed.
    • Security: No credentials, cookies, or authentication are supported in either direction.
    • Data Flow: Font files themselves never pass through the proxy. The proxy only handles metadata and CSS requests; font files are served directly from the provider's CDN, which already supports cross-origin requests.
  9. How providers work in unifont

    main

    A provider is a small adapter for a specific font source (e.g., Google, Adobe, npm). When you initialize unifont using createUnifont(), you pass an array of providers. Unifont will iterate through them in the order provided, asking each one until a provider successfully resolves the requested font.

    Not all providers support all features. For example, listFonts() is optional and unavailable for npm and adobe providers.

    import { createUnifont, providers } from 'unifont'
    
    const unifont = await createUnifont([
      providers.google(),
      providers.bunny(),
      providers.fontshare(),
      providers.fontsource(),
      providers.googleicons(),
      providers.npm(),
    ])
  10. Understand variableAxis resolution results

    main

    When requesting variable axes, the variableAxis property in the result object tells you how the requested axes were handled. This is useful for tools that process font files.

    Each axis entry contains:

    • values: The normalized values requested (a string for single values, or a [min, max] pair for ranges).
    • appliedAs:
      • 'font-file': The file itself was instanced/limited.
      • 'variation-settings': unifont added font-variation-settings to the descriptor.
      • 'none': The axis was not applied (e.g., axis not found or range too complex for a single face).
    const { variableAxis } = await unifont.resolveFont('Recursive', {
      variableAxis: { CASL: [1], slnt: [{ min: -15, max: 0 }] },
    })
    
    // Example output:
    // {
    //   CASL: { values: ['1'], appliedAs: 'variation-settings' },
    //   slnt: { values: [['-15', '0']], appliedAs: 'none' },
    // }
  11. Handling Variable Axes in custom providers

    main

    If your provider can deliver a font file instanced to specific axis values, use options.variableAxis to request them.

    options.variableAxis format:

    • Single value: string (e.g., '1').
    • Range: [min, max] pair of strings (e.g., ['75', '100']).

    Reporting axes:

    • Instanced files: If you return a file already applied to specific axes, return the actual values the file carries in appliedVariableAxis. This may be narrower than the request if you clamped to published values. Omit any axis you could not apply.
    • Whole variable files: If you serve the entire variable font file, omit appliedVariableAxis. unifont will automatically handle expressing single-value axes as font-variation-settings for you.
    async resolveFont(family, options) {
      options.variableAxis // { CASL: ['1'], wdth: [['75', '100']] }
      const applied = pickPublishedAxes(family, options.variableAxis)
    
      return {
        fonts: await fetchInstancedFaces(family, applied),
        appliedVariableAxis: applied, // { CASL: ['1'] }
      }
    }
  12. When to self-host the unifont proxy

    main

    The unifont proxy is a Nitro application used to bypass CORS restrictions when accessing provider APIs from a browser or web container.

    You need to deploy the proxy if:

    • You resolve fonts in a browser or a web container at runtime.

    You do NOT need the proxy if:

    • You are running on a server, in a worker, in CI, or at build time (e.g., using @nuxt/fonts or fontless). In these environments, provider APIs are called directly without CORS issues.