---
title: "font"
description: "The font namespace — the engine's Luau API reference for font."
section: "API Reference"
slug: "api-font"
canonical: "https://origozero.ai/docs/api-font"
updated: "2026-09-05T23:13:46.470832996+00:00"
tags: ["api", "reference"]
---

# font

The `font` namespace — 22 functions.

## globals/font/glyph {#globals-font-glyph}

```lua
font.glyph(name: string, codepoint: number) -> any
```

Read one glyph's vectorized outline from a registered font, in font
units (resolution-independent — scale by `fontSize / unitsPerEm`).

**Parameters**

- `name` `string` — Registered family name.
- `codepoint` `number` — Unicode codepoint (e.g. `string.byte("A")`).

**Returns** `any` — `{ advance, unitsPerEm, bbox = {xMin,yMin,xMax,yMax}, contours }` where each contour is `{ start = {x,y}, segments = { {kind="line|quad|cubic", ...} } }`, or nil if the font isn't registered.

```lua
local g = font.glyph("Inter", string.byte("A"))
```

## globals/font/list {#globals-font-list}

```lua
font.list() -> { string }
```

List every registered font family name.

**Returns** `{ string }` — Array of family-name strings.

```lua
for _, fam in font.list() do print(fam) end
```

## globals/font/observe {#globals-font-observe}

```lua
font.observe() -> { any }
```

What the text system is holding for fonts: one row per family the
shaper can resolve, with its face count, the numeric weights those faces
carry, whether any of them is slanted, and whether the family arrived
through a registration rather than from the platform. `weights` is what a
style's `weight` can name for that family. The same rows are `fonts` in
`text.observe()`.

**Returns** `{ any }` — Array of `{ family, faces, weights, italic, loaded }`.

```lua
for _, f in ipairs(font.observe()) do print(f.family, #f.weights) end
```

## globals/font/parse {#globals-font-parse}

```lua
font.parse(bytes: buffer | string) -> string?
```

Parse a font file (TTF / OTF raw bytes) ONCE into the baked, vectorized
glyph format (`ZFNT`): per-glyph vector outlines + metrics + character map,
plus the original bytes. Heavy — run at import time (the `.font` assetType's
onCreate / the font importer), then store the result as the asset payload.
`font.register` loads it cheaply.

**Parameters**

- `bytes` `buffer | string` — Raw font-file bytes (binary-safe) — TTF / OTF.

**Returns** `string?` — Baked `ZFNT` payload (binary-safe string), or nil if the bytes don't parse as a font.

```lua
local zfnt = font.parse(vfs.read("/zero/source/Inter.ttf"))
```

## globals/font/reconcile {#globals-font-reconcile}

```lua
font.reconcile() -> { any }
```

Every family the text shaper can resolve, held against what the shaper
does with it. `family` is the name, `faces` how many faces of it the font
database holds, `weights` the numeric weights those faces carry, `loaded`
whether it arrived through a registration rather than from the platform,
`registered` whether content registered the name, `selectable` whether some
style naming the family reaches it, `matched` whether `fontFamily = family`
on its own reaches it — the family name at the default weight over Latin
text — `weight` the weight it needs when the default is not it, `shapedWith`
the face that answered, and `reason` why when it is not the one asked for. A
family is probed at its own weights and over content from several scripts,
so a family reachable only at one weight or covering only one script is
reported selectable, with `matched` false and `weight` naming what the style
must carry. Every probe object is destroyed again, so the live text-object
count is where it was.

**Returns** `{ any }` — Array of `{ family, faces, weights, loaded, registered, selectable, matched, weight, shapedWith, reason }`.

```lua
for _, f in ipairs(font.reconcile()) do if f.selectable and not f.matched then print(f.family, f.weight) end end
```

## globals/font/register {#globals-font-register}

```lua
font.register(name: string, zfnt: string, opts: table?) -> any
```

Register a baked font (`ZFNT` from `font.parse`) under `name`, making it
usable on every text surface via `fontFamily = "<name>"`. Loads the
vectorized glyph data into the runtime store (for `font.glyph` /
`font.textMesh`) and feeds the embedded face to the 2D text and egui UI
systems. Passing raw font bytes still works but logs a slow-path warning —
bake with `font.parse` at import. Re-registering the same name replaces it.
`opts` groups several weight/style faces under one CSS family and maps
web-font names onto it: `opts.family` is the shared group key, `opts.role`
is `"regular" | "bold" | "italic" | "bolditalic"`, and `opts.aliases` is a
list of extra selectable names (web fonts + CSS generics like `"Arial"`,
`"sans-serif"`) that resolve to this group, matched case-insensitively.
With a group set, `font-weight` / `font-style` on a `font-family` pick the
real metric-compatible face instead of a synthesized one.

**Parameters**

- `name` `string` — Family name to register under.
- `zfnt` `string` — Baked `ZFNT` payload from `font.parse` (binary-safe string).
- `opts` `table` _(optional)_ — `{ family: string?, role: string?, aliases: {string}? }` — group key,
weight/style role, and case-insensitive selectable aliases.

**Returns** `any` — `{ family, faces, glyphCount }` on success, or nil on failure.

```lua
local info = font.register("Inter", font.parse(vfs.read("/zero/source/Inter.ttf")))
```

## globals/font/textMesh {#globals-font-textmesh}

```lua
font.textMesh(name: string, text: string, opts: table?) -> any
```

Tessellate a string into renderable mesh geometry from a registered
font's glyph outlines — true 3D text, laid out left-to-right by advance
(newlines drop a line). Hand the result to `renderer.mesh.create()` (GPU)
or `asset.create("mesh")` (persistable).

**Parameters**

- `name` `string` — Registered family name.
- `text` `string` — String to lay out.
- `opts` `table` _(optional)_ — `{ size?=1, depth?=0 (extrude, EM units), tolerance?=0.0015, letterSpacing?=0, lineHeight?=0 }`.

**Returns** `any` — `{ positions, indices, normals, uvs }` as flat float / u32 arrays, or nil if the font isn't registered or the string is all whitespace.

```lua
local geom = font.textMesh("Inter", "Hello", { size = 1, depth = 0.1 })
```

## modules/font/README {#modules-font-readme}

```lua
require("@builtin/modules/api/engine/font") -- font (also available as global 'font')
```

Font primitive — parse a font file ONCE into a baked, vectorized glyph format (`ZFNT`), then drive every text surface from it. A font is a general CPU resource addressed by name (the CPU counterpart of a renderer GPU resource), so a single registration is usable from UI text, 2D text, and true 3D glyph geometry. Public Luau surface over the `__font` Internal FFI namespace. Authored fonts are `.font` assets whose `onRegister` hook calls `font.register`.

Usage: local font = require("@builtin/modules/api/engine/font")
Also available as global: font

## modules/font/glyph {#modules-font-glyph}

```lua
glyph(name: string, codepoint: number): any
```

Read one glyph's vectorized outline from a registered font, in font
units (resolution-independent — scale by `fontSize / unitsPerEm`).

**Parameters**

- `name` `string` — Registered family name.
- `codepoint` `number` — Unicode codepoint (e.g. `string.byte("A")`).

```lua
local g = font.glyph("Inter", string.byte("A"))
```

## modules/font/list {#modules-font-list}

```lua
list(): { string }
```

List every registered font family name.

```lua
for _, fam in font.list() do print(fam) end
```

## modules/font/observe {#modules-font-observe}

```lua
observe(): { any }
```

What the text system is holding for fonts: one row per family the
shaper can resolve, with its face count, the numeric weights those faces
carry, whether any of them is slanted, and whether the family arrived
through a registration rather than from the platform. `weights` is what a
style's `weight` can name for that family. The same rows are `fonts` in
`text.observe()`.

```lua
for _, f in ipairs(font.observe()) do print(f.family, #f.weights) end
```

## modules/font/parse {#modules-font-parse}

```lua
parse(bytes: buffer | string): string?
```

Parse a font file (TTF / OTF raw bytes) ONCE into the baked, vectorized
glyph format (`ZFNT`): per-glyph vector outlines + metrics + character map,
plus the original bytes. Heavy — run at import time (the `.font` assetType's
onCreate / the font importer), then store the result as the asset payload.
`font.register` loads it cheaply.

**Parameters**

- `bytes` `buffer | string` — Raw font-file bytes (binary-safe) — TTF / OTF.

```lua
local zfnt = font.parse(vfs.read("/zero/source/Inter.ttf"))
```

## modules/font/reconcile {#modules-font-reconcile}

```lua
reconcile(): { any }
```

Every family the text shaper can resolve, held against what the shaper
does with it. `family` is the name, `faces` how many faces of it the font
database holds, `weights` the numeric weights those faces carry, `loaded`
whether it arrived through a registration rather than from the platform,
`registered` whether content registered the name, `selectable` whether some
style naming the family reaches it, `matched` whether `fontFamily = family`
on its own reaches it — the family name at the default weight over Latin
text — `weight` the weight it needs when the default is not it, `shapedWith`
the face that answered, and `reason` why when it is not the one asked for. A
family is probed at its own weights and over content from several scripts,
so a family reachable only at one weight or covering only one script is
reported selectable, with `matched` false and `weight` naming what the style
must carry. Every probe object is destroyed again, so the live text-object
count is where it was.

```lua
for _, f in ipairs(font.reconcile()) do if f.selectable and not f.matched then print(f.family, f.weight) end end
```

## modules/font/register {#modules-font-register}

```lua
register(name: string, zfnt: string, opts: table?): any
```

Register a baked font (`ZFNT` from `font.parse`) under `name`, making it
usable on every text surface via `fontFamily = "<name>"`. Loads the
vectorized glyph data into the runtime store (for `font.glyph` /
`font.textMesh`) and feeds the embedded face to the 2D text and egui UI
systems. Passing raw font bytes still works but logs a slow-path warning —
bake with `font.parse` at import. Re-registering the same name replaces it.
`opts` groups several weight/style faces under one CSS family and maps
web-font names onto it: `opts.family` is the shared group key, `opts.role`
is `"regular" | "bold" | "italic" | "bolditalic"`, and `opts.aliases` is a
list of extra selectable names (web fonts + CSS generics like `"Arial"`,
`"sans-serif"`) that resolve to this group, matched case-insensitively.
With a group set, `font-weight` / `font-style` on a `font-family` pick the
real metric-compatible face instead of a synthesized one.

**Parameters**

- `name` `string` — Family name to register under.
- `zfnt` `string` — Baked `ZFNT` payload from `font.parse` (binary-safe string).
- `opts` `table?` _(optional)_ — `{ family: string?, role: string?, aliases: {string}? }` — group key,
weight/style role, and case-insensitive selectable aliases.

```lua
local info = font.register("Inter", font.parse(vfs.read("/zero/source/Inter.ttf")))
```

## modules/font/textMesh {#modules-font-textmesh}

```lua
textMesh(name: string, text: string, opts: table?): any
```

Tessellate a string into renderable mesh geometry from a registered
font's glyph outlines — true 3D text, laid out left-to-right by advance
(newlines drop a line). Hand the result to `renderer.mesh.create()` (GPU)
or `asset.create("mesh")` (persistable).

**Parameters**

- `name` `string` — Registered family name.
- `text` `string` — String to lay out.
- `opts` `table?` _(optional)_ — `{ size?=1, depth?=0 (extrude, EM units), tolerance?=0.0015, letterSpacing?=0, lineHeight?=0 }`.

```lua
local geom = font.textMesh("Inter", "Hello", { size = 1, depth = 0.1 })
```

## typed/builtin//modules/api/engine/font/font/glyph {#typed-builtin-modules-api-engine-font-font-glyph}

```lua
font.glyph(name: string, codepoint: number) -> any
```

Read one glyph's vectorized outline from a registered font, in font
units (resolution-independent — scale by `fontSize / unitsPerEm`).

**Parameters**

- `name` `string` — Registered family name.
- `codepoint` `number` — Unicode codepoint (e.g. `string.byte("A")`).

**Returns** `any` — `{ advance, unitsPerEm, bbox = {xMin,yMin,xMax,yMax}, contours }` where each contour is `{ start = {x,y}, segments = { {kind="line|quad|cubic", ...} } }`, or nil if the font isn't registered.

```lua
local g = font.glyph("Inter", string.byte("A"))
```

## typed/builtin//modules/api/engine/font/font/list {#typed-builtin-modules-api-engine-font-font-list}

```lua
font.list() -> { string }
```

List every registered font family name.

**Returns** `{ string }` — Array of family-name strings.

```lua
for _, fam in font.list() do print(fam) end
```

## typed/builtin//modules/api/engine/font/font/observe {#typed-builtin-modules-api-engine-font-font-observe}

```lua
font.observe() -> { any }
```

What the text system is holding for fonts: one row per family the
shaper can resolve, with its face count, the numeric weights those faces
carry, whether any of them is slanted, and whether the family arrived
through a registration rather than from the platform. `weights` is what a
style's `weight` can name for that family. The same rows are `fonts` in
`text.observe()`.

**Returns** `{ any }` — Array of `{ family, faces, weights, italic, loaded }`.

```lua
for _, f in ipairs(font.observe()) do print(f.family, #f.weights) end
```

## typed/builtin//modules/api/engine/font/font/parse {#typed-builtin-modules-api-engine-font-font-parse}

```lua
font.parse(bytes: buffer | string) -> string?
```

Parse a font file (TTF / OTF raw bytes) ONCE into the baked, vectorized
glyph format (`ZFNT`): per-glyph vector outlines + metrics + character map,
plus the original bytes. Heavy — run at import time (the `.font` assetType's
onCreate / the font importer), then store the result as the asset payload.
`font.register` loads it cheaply.

**Parameters**

- `bytes` `buffer | string` — Raw font-file bytes (binary-safe) — TTF / OTF.

**Returns** `string?` — Baked `ZFNT` payload (binary-safe string), or nil if the bytes don't parse as a font.

```lua
local zfnt = font.parse(vfs.read("/zero/source/Inter.ttf"))
```

## typed/builtin//modules/api/engine/font/font/reconcile {#typed-builtin-modules-api-engine-font-font-reconcile}

```lua
font.reconcile() -> { any }
```

Every family the text shaper can resolve, held against what the shaper
does with it. `family` is the name, `faces` how many faces of it the font
database holds, `weights` the numeric weights those faces carry, `loaded`
whether it arrived through a registration rather than from the platform,
`registered` whether content registered the name, `selectable` whether some
style naming the family reaches it, `matched` whether `fontFamily = family`
on its own reaches it — the family name at the default weight over Latin
text — `weight` the weight it needs when the default is not it, `shapedWith`
the face that answered, and `reason` why when it is not the one asked for. A
family is probed at its own weights and over content from several scripts,
so a family reachable only at one weight or covering only one script is
reported selectable, with `matched` false and `weight` naming what the style
must carry. Every probe object is destroyed again, so the live text-object
count is where it was.

**Returns** `{ any }` — Array of `{ family, faces, weights, loaded, registered, selectable, matched, weight, shapedWith, reason }`.

```lua
for _, f in ipairs(font.reconcile()) do if f.selectable and not f.matched then print(f.family, f.weight) end end
```

## typed/builtin//modules/api/engine/font/font/register {#typed-builtin-modules-api-engine-font-font-register}

```lua
font.register(name: string, zfnt: string, opts: table?) -> any
```

Register a baked font (`ZFNT` from `font.parse`) under `name`, making it
usable on every text surface via `fontFamily = "<name>"`. Loads the
vectorized glyph data into the runtime store (for `font.glyph` /
`font.textMesh`) and feeds the embedded face to the 2D text and egui UI
systems. Passing raw font bytes still works but logs a slow-path warning —
bake with `font.parse` at import. Re-registering the same name replaces it.
`opts` groups several weight/style faces under one CSS family and maps
web-font names onto it: `opts.family` is the shared group key, `opts.role`
is `"regular" | "bold" | "italic" | "bolditalic"`, and `opts.aliases` is a
list of extra selectable names (web fonts + CSS generics like `"Arial"`,
`"sans-serif"`) that resolve to this group, matched case-insensitively.
With a group set, `font-weight` / `font-style` on a `font-family` pick the
real metric-compatible face instead of a synthesized one.

**Parameters**

- `name` `string` — Family name to register under.
- `zfnt` `string` — Baked `ZFNT` payload from `font.parse` (binary-safe string).
- `opts` `table` _(optional)_ — `{ family: string?, role: string?, aliases: {string}? }` — group key,
weight/style role, and case-insensitive selectable aliases.

**Returns** `any` — `{ family, faces, glyphCount }` on success, or nil on failure.

```lua
local info = font.register("Inter", font.parse(vfs.read("/zero/source/Inter.ttf")))
```

## typed/builtin//modules/api/engine/font/font/textMesh {#typed-builtin-modules-api-engine-font-font-textmesh}

```lua
font.textMesh(name: string, text: string, opts: table?) -> any
```

Tessellate a string into renderable mesh geometry from a registered
font's glyph outlines — true 3D text, laid out left-to-right by advance
(newlines drop a line). Hand the result to `renderer.mesh.create()` (GPU)
or `asset.create("mesh")` (persistable).

**Parameters**

- `name` `string` — Registered family name.
- `text` `string` — String to lay out.
- `opts` `table` _(optional)_ — `{ size?=1, depth?=0 (extrude, EM units), tolerance?=0.0015, letterSpacing?=0, lineHeight?=0 }`.

**Returns** `any` — `{ positions, indices, normals, uvs }` as flat float / u32 arrays, or nil if the font isn't registered or the string is all whitespace.

```lua
local geom = font.textMesh("Inter", "Hello", { size = 1, depth = 0.1 })
```
