Log inGet started

font

Updated 5 September 2026

The font namespace — 22 functions.

globals/font/glyph

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.

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

globals/font/list

font.list() -> { string }

List every registered font family name.

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

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

globals/font/observe

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 }.

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

globals/font/parse

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.

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

globals/font/reconcile

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 }.

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

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.

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

globals/font/textMesh

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.

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

modules/font/README

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

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")).
local g = font.glyph("Inter", string.byte("A"))

modules/font/list

list(): { string }

List every registered font family name.

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

modules/font/observe

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().

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

modules/font/parse

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.
local zfnt = font.parse(vfs.read("/zero/source/Inter.ttf"))

modules/font/reconcile

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.

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

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.
local info = font.register("Inter", font.parse(vfs.read("/zero/source/Inter.ttf")))

modules/font/textMesh

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 }.
local geom = font.textMesh("Inter", "Hello", { size = 1, depth = 0.1 })

typed/builtin//modules/api/engine/font/font/glyph

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.

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

typed/builtin//modules/api/engine/font/font/list

font.list() -> { string }

List every registered font family name.

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

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

typed/builtin//modules/api/engine/font/font/observe

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 }.

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

typed/builtin//modules/api/engine/font/font/parse

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.

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

typed/builtin//modules/api/engine/font/font/reconcile

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 }.

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

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.

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

typed/builtin//modules/api/engine/font/font/textMesh

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.

local geom = font.textMesh("Inter", "Hello", { size = 1, depth = 0.1 })
  • api
  • reference