You can bundle fonts with an app so text renders identically across every platform, even on devices with no system fonts. When a page requests a font by CSS family, weight, and style, `FontLoader` turns that request into font data.

## The Default Font Loader

`App::Create()` installs the default OS font loader to resolve fonts installed on the OS.

When you create the renderer directly with `Renderer::Create()`, no font loader is installed. A font loader is required— call `GetPlatformFontLoader()` to use the OS loader without `App::Create()`.

Setting a custom loader on `Platform` before calling `App::Create()` or `Renderer::Create()` replaces the default loader (see [Setting Up the Platform](/docs/2.0/setting-up-the-platform)).

## Loading Fonts in CSS

To load a bundled font without writing a custom loader, define a `@font-face` rule in CSS:

```css
@font-face {
  font-family: "Rocket Sans";
  src: url("file:///fonts/rocket-sans.ttf");
}

body { font-family: "Rocket Sans", sans-serif; }
```

The font loads through the file system like any other asset, while the default OS font loader continues to resolve all other fonts. For details on serving files from memory or disk, see [Custom File System](/docs/2.0/custom-filesystem).

A custom font loader is only needed when you must supply every family, including fallbacks, from a bundled asset source.

## The FontLoader Interface

To provide a custom font loader, subclass `FontLoader` and pass an instance to `Platform::instance().set_font_loader()`.

The interface requires three virtual methods: `Load()`, `fallback_font()`, and `fallback_font_for_characters()`.

> 🚧 Thread Safety
>
> The library calls font loader methods from the Renderer's thread and from threads running Web Workers. Because these calls can overlap, the implementation must be safe to call from multiple threads at once.

## Loading Font Data

The library calls `Load()` with a requested family, weight, and style. Return a matching `FontFile`, or return `nullptr`— the library then uses a fallback font.

Generic CSS families (`serif`, `sans-serif`, and `monospace`) reach `Load()` as the family names configured in `ViewConfig` (by default `"Times New Roman"`, `"Arial"`, and `"Courier New"`). A loader that ships only bundled fonts sends all of these generic requests to `fallback_font()` unless you change those `ViewConfig` names (see [Text Rendering and Fonts](/docs/2.0/text-rendering-and-fonts)).

### Loading from Memory or Disk

To load font data from memory, call `FontFile::Create()` with a `Buffer` containing raw font bytes.

To load a font from disk, pass a file path to `FontFile::Create()`. The OS opens the file directly— the file must already exist on disk, and loading does not pass through `FileSystem`.

### Handling Font Collections

Font collection files (such as `.ttc` files) contain several font faces and can include multiple families.

Both `FontFile::Create()` overloads accept an optional face index as the second argument. Pass the matching face index for collections— without it, the library matches by weight and style alone and cannot distinguish between families that share a file (such as regional CJK faces).

### Matching Weights and Styles

The `weight` parameter is a CSS numeric weight from 100 (thin) to 900 (black), rounded to the nearest 100 (where 400 is normal and 700 is bold).

When no exact match exists, return the closest available face.

If the returned face is not bold for a weight of 600 or higher, or is not slanted for an italic request, the library synthesizes the bold or slant automatically. A page can disable this behavior with the CSS `font-synthesis` property.

## Providing Fallback Fonts

When a requested font family cannot be found or lacks specific glyphs, the library calls fallback methods to locate an alternate family.

### The Last-Resort Family

The library calls `fallback_font()` to retrieve a family name when all other font lookups fail.

`Load()` must always succeed when passed this fallback family name. If loading this family fails, the library logs a `LogLevel::Fatal` message and halts because no further fallback exists.

### Fallback for Missing Characters

The library calls `fallback_font_for_characters()` to find a font family capable of drawing specific characters that the active font lacks.

The `characters` parameter contains one or more UTF-16 characters (almost always a single character). The library calls this method primarily for CJK text missing from the active font, so you should return the name of a bundled family that provides wide character coverage.

## Example Implementation

To bundle fonts from both memory and disk, implement `FontLoader` and install it before creating the Renderer:

```cpp
#include <Ultralight/Ultralight.h>

using namespace ultralight;

class MyFontLoader : public FontLoader {
 public:
  ///
  /// The family the library uses when nothing else loads. Load() must
  /// always succeed for it.
  ///
  String fallback_font() const override { return "Rocket Sans"; }

  ///
  /// Pick a family that can draw these characters. We ship one
  /// wide-coverage face for that job.
  ///
  String fallback_font_for_characters(const String& characters, int weight,
                                      bool italic) const override {
    return "Rocket CJK";
  }

  RefPtr<FontFile> Load(const String& family, int weight,
                        bool italic) override {
    ///
    /// Our main family is packed in the asset store, so hand the library
    /// the raw TTF bytes (700 and up is bold, and we ship only upright faces).
    ///
    if (family == "Rocket Sans") {
      const char* file =
          weight >= 700 ? "rocket-sans-bold.ttf" : "rocket-sans.ttf";
      void* data = nullptr;
      size_t size = 0;
      // Pseudo-code, fetch the font bytes from your asset store.
      GetFontBytes(file, &data, &size);
      return FontFile::Create(Buffer::CreateFromCopy(data, size));
    }

    ///
    /// The CJK face is big, so it stays on disk and we pass its OS path
    /// (this one doesn't go through our FileSystem).
    ///
    if (family == "Rocket CJK")
      return FontFile::Create("fonts/rocket-cjk.ttf");

    ///
    /// Anything else, and the library falls back on its own.
    ///
    return nullptr;
  }
};

MyFontLoader font_loader;

void InitPlatform() {
  ///
  /// Install our font loader before creating the Renderer. (Ownership
  /// stays with us, so 'font_loader' must outlive the Renderer.)
  ///
  Platform::instance().set_font_loader(&font_loader);
}
```
