font
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
namestring— Registered family name.codepointnumber— 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
bytesbuffer | 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
namestring— Family name to register under.zfntstring— BakedZFNTpayload fromfont.parse(binary-safe string).optstable(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
namestring— Registered family name.textstring— String to lay out.optstable(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
namestring— Registered family name.codepointnumber— 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
bytesbuffer | 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
namestring— Family name to register under.zfntstring— BakedZFNTpayload fromfont.parse(binary-safe string).optstable?(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
namestring— Registered family name.textstring— String to lay out.optstable?(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
namestring— Registered family name.codepointnumber— 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
bytesbuffer | 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
namestring— Family name to register under.zfntstring— BakedZFNTpayload fromfont.parse(binary-safe string).optstable(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
namestring— Registered family name.textstring— String to lay out.optstable(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 })