Log inGet started

text

Updated 5 September 2026

The text namespace — 49 functions.

globals/text/alive

text.alive(handle: any?) -> boolean

Whether the text system still holds this handle — true between text.create and the text.destroy that released it.

Parameters

  • handle any (optional) — Text handle from text.create.

Returns boolean — True while the handle is live.

if not text.alive(h) then h = text.create({ content = "again" }) end

globals/text/count

text.count() -> number

How many text objects the text system is holding — the number that moves when text.create and text.destroy are called.

Returns number — The live text-object count.

local before = text.count()

globals/text/create

text.create(options: table) -> any

Create a text handle from an initial content + style table. The handle owns a runtime GPU texture (see text.textureGuid); pass it to every other call.

Parameters

  • options table — Table of content plus style fields (fontSize, color, alignment, richText, maxWidth, ...).

Returns any — An opaque text handle, or nil if the text system is unavailable.

local h = text.create({ content = "Hello", fontSize = 48 })

globals/text/destroy

text.destroy(handle: any?) -> boolean

Destroy a text handle and release its raster + glyph layout.

Parameters

  • handle any (optional) — Text handle from text.create.

Returns boolean — True when the text system held the handle and released it; false for a handle it did not have.

text.destroy(h)

globals/text/face

text.face(handle: any?) -> any

Which font face one handle actually shaped with, and whether that is the family its style asked for. requested is what was asked, resolved is the face that answered, matched says whether they agree and reason says why when they do not — one of text.faceReasons(). A style that named no family reports noFamilyRequested: it got the default because it asked for nothing, so reason rather than matched is what an alert switches on. faces lists every face the shaper used, most glyphs first, so a fallback that covered part of the string is visible alongside the face that covered the rest.

Parameters

  • handle any (optional) — Text handle from text.create.

Returns any{ requested, resolved, postScriptName, matched, reason, faces, glyphCount }, or nil for a handle the text system does not hold.

local r = text.face(h).reason; if r == "familyUnknown" or r == "familyNotSelectable" then print(r) end

globals/text/faceReasons

text.faceReasons() -> { string }

Every reason the face readings give for a label or a family not being in the family a style named, nearest cause first. text.face gives them for one label; font.reconcile() also gives familyCoveredNoGlyph, which it can only reach by laying the family out under its own weights and over several scripts.

Returns { string } — Array of reason strings.

for _, r in ipairs(text.faceReasons()) do print(r) end

globals/text/listFonts

text.listFonts() -> { string }

List the font families currently available to the text system.

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

local fonts = text.listFonts()

globals/text/loadFont

text.loadFont(ref: any?) -> any

Load a font from an asset reference so it becomes available to setStyle's fontFamily.

Parameters

  • ref any (optional) — Font asset reference or path.

Returns any — The loaded font-family name, or nil on failure.

text.loadFont(asset.ref("fonts.inter", "font"))

globals/text/measure

text.measure(handle: any?) -> any

Measure the rasterised text in pixels without producing a texture.

Parameters

  • handle any (optional) — Text handle from text.create.

Returns any — Table with width and height in pixels.

local size = text.measure(h)

globals/text/observe

text.observe() -> any

Everything the text system is holding right now. count is the live text objects; objects is one row each, carrying its content, the style it was laid out with, its measured extent, whether it is dirty, the face the shaper actually used, the owner entity whose component created it with whether that entity is still there, and the raster texture its last rasterisation landed in with the bytes it costs. orphans is the subset whose owning entity is gone, fonts the families the shaper can resolve, and raster the glyph-raster bytes with the pool they belong to named. Built when you ask, so it costs nothing per frame and reads the same in edit mode as in play.

Returns any{ count, objects, orphans, fonts, dirty, raster }.

local live = text.observe().count

globals/text/orphans

text.orphans() -> { any }

The text objects whose owning entity no longer exists — a quad the engine is still holding for something that has been despawned. Each row is the same shape text.observe().objects carries.

Returns { any } — Array of text-object rows with a dead owner.

print(#text.orphans() .. " labels outlived their entity")

globals/text/rasterMemory

text.rasterMemory() -> any

The glyph-raster bytes, broken out of the runtime GPU texture pool. bytes is summed off the same map renderer.gpuMemory().textures is totalled from, so shareOfPool is a share of that number rather than a second count of the same memory.

Returns any{ pool, bytes, textures, poolBytes, shareOfPool }.

local r = text.rasterMemory(); print(r.bytes .. " of " .. r.poolBytes)

globals/text/rasterize

text.rasterize(handle: any?, texture: any?, scale: number?) -> any

Rasterise the handle's current text + style into the given runtime GPU texture. Bind that texture's guid as a material's base_color_texture to display the text; re-rasterising the same texture overwrites it in place.

Parameters

  • handle any (optional) — Text handle from text.create.
  • texture any (optional) — Destination GPU texture handle (renderer.texture.create) or its guid string — WHERE the raster lands.
  • scale number (optional) — World/pixel scale factor for the raster (default 1.0).

Returns any — Table with width and height (in pixels), or nil if nothing rasterised.

local tex = renderer.texture.create({ width = 256, height = 64 })
local r = text.rasterize(h, tex, 1.0)

globals/text/setStyle

text.setStyle(handle: any?, style: table) -> boolean

Replace the handle's style. Fields not present keep their current value.

Parameters

  • handle any (optional) — Text handle from text.create.
  • style table — Style table (fontSize, color, alignment, outline, ...).

Returns boolean — True when the text system held the handle and took the style; false when it did not.

text.setStyle(h, { fontSize = 64, color = "yellow" })

globals/text/setText

text.setText(handle: any?, content: string) -> boolean

Replace the handle's text content.

Parameters

  • handle any (optional) — Text handle from text.create.
  • content string — New text string.

Returns boolean — True when the text system held the handle and took the content; false when it did not, which is how a caller learns its handle went away.

if not text.setText(h, "HP: 100") then h = text.create({ content = "HP: 100" }) end

globals/text/textureGuid

text.textureGuid(handle: any?) -> string?

The runtime GPU texture guid this handle rasterises into — bind it as a material texture (base_color_texture) to display the text.

Parameters

  • handle any (optional) — Text handle from text.create.

Returns string? — The texture guid string, or nil for a handle the text system does not hold.

entity(id).component.get("Material"):setTexture("base_color_texture", text.textureGuid(h))

modules/text/README

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

Text rasterisation resource — create a text handle, set its content and style, then rasterise it to a texture for display, and observe what the text system is holding. Public Luau surface over the __text and __textObserve Internal FFI namespaces.

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

modules/text/alive

alive(handle: any): boolean

Whether the text system still holds this handle — true between text.create and the text.destroy that released it.

Parameters

  • handle any (optional) — Text handle from text.create.
if not text.alive(h) then h = text.create({ content = "again" }) end

modules/text/count

count(): number

How many text objects the text system is holding — the number that moves when text.create and text.destroy are called.

local before = text.count()

modules/text/create

create(options: table): any

Create a text handle from an initial content + style table. The handle owns a runtime GPU texture (see text.textureGuid); pass it to every other call.

Parameters

  • options table — Table of content plus style fields (fontSize, color, alignment, richText, maxWidth, ...).
local h = text.create({ content = "Hello", fontSize = 48 })

modules/text/destroy

destroy(handle: any): boolean

Destroy a text handle and release its raster + glyph layout.

Parameters

  • handle any (optional) — Text handle from text.create.
text.destroy(h)

modules/text/face

face(handle: any): any

Which font face one handle actually shaped with, and whether that is the family its style asked for. requested is what was asked, resolved is the face that answered, matched says whether they agree and reason says why when they do not — one of text.faceReasons(). A style that named no family reports noFamilyRequested: it got the default because it asked for nothing, so reason rather than matched is what an alert switches on. faces lists every face the shaper used, most glyphs first, so a fallback that covered part of the string is visible alongside the face that covered the rest.

Parameters

  • handle any (optional) — Text handle from text.create.
local r = text.face(h).reason; if r == "familyUnknown" or r == "familyNotSelectable" then print(r) end

modules/text/faceReasons

faceReasons(): { string }

Every reason the face readings give for a label or a family not being in the family a style named, nearest cause first. text.face gives them for one label; font.reconcile() also gives familyCoveredNoGlyph, which it can only reach by laying the family out under its own weights and over several scripts.

for _, r in ipairs(text.faceReasons()) do print(r) end

modules/text/listFonts

listFonts(): { string }

List the font families currently available to the text system.

local fonts = text.listFonts()

modules/text/loadFont

loadFont(ref: any): any

Load a font from an asset reference so it becomes available to setStyle's fontFamily.

Parameters

  • ref any (optional) — Font asset reference or path.
text.loadFont(asset.ref("fonts.inter", "font"))

modules/text/measure

measure(handle: any): any

Measure the rasterised text in pixels without producing a texture.

Parameters

  • handle any (optional) — Text handle from text.create.
local size = text.measure(h)

modules/text/observe

observe(): any

Everything the text system is holding right now. count is the live text objects; objects is one row each, carrying its content, the style it was laid out with, its measured extent, whether it is dirty, the face the shaper actually used, the owner entity whose component created it with whether that entity is still there, and the raster texture its last rasterisation landed in with the bytes it costs. orphans is the subset whose owning entity is gone, fonts the families the shaper can resolve, and raster the glyph-raster bytes with the pool they belong to named. Built when you ask, so it costs nothing per frame and reads the same in edit mode as in play.

local live = text.observe().count

modules/text/orphans

orphans(): { any }

The text objects whose owning entity no longer exists — a quad the engine is still holding for something that has been despawned. Each row is the same shape text.observe().objects carries.

print(#text.orphans() .. " labels outlived their entity")

modules/text/rasterMemory

rasterMemory(): any

The glyph-raster bytes, broken out of the runtime GPU texture pool. bytes is summed off the same map renderer.gpuMemory().textures is totalled from, so shareOfPool is a share of that number rather than a second count of the same memory.

local r = text.rasterMemory(); print(r.bytes .. " of " .. r.poolBytes)

modules/text/rasterize

rasterize(handle: any, texture: any, scale: number?): any

Rasterise the handle's current text + style into the given runtime GPU texture. Bind that texture's guid as a material's base_color_texture to display the text; re-rasterising the same texture overwrites it in place.

Parameters

  • handle any (optional) — Text handle from text.create.
  • texture any (optional) — Destination GPU texture handle (renderer.texture.create) or its guid string — WHERE the raster lands.
  • scale number? (optional) — World/pixel scale factor for the raster (default 1.0).
local tex = renderer.texture.create({ width = 256, height = 64 })
local r = text.rasterize(h, tex, 1.0)

modules/text/setStyle

setStyle(handle: any, style: table): boolean

Replace the handle's style. Fields not present keep their current value.

Parameters

  • handle any (optional) — Text handle from text.create.
  • style table — Style table (fontSize, color, alignment, outline, ...).
text.setStyle(h, { fontSize = 64, color = "yellow" })

modules/text/setText

setText(handle: any, content: string): boolean

Replace the handle's text content.

Parameters

  • handle any (optional) — Text handle from text.create.
  • content string — New text string.
if not text.setText(h, "HP: 100") then h = text.create({ content = "HP: 100" }) end

modules/text/textureGuid

textureGuid(handle: any): string?

The runtime GPU texture guid this handle rasterises into — bind it as a material texture (base_color_texture) to display the text.

Parameters

  • handle any (optional) — Text handle from text.create.
entity(id).component.get("Material"):setTexture("base_color_texture", text.textureGuid(h))

typed/builtin//modules/api/engine/text/text/alive

text.alive(handle: any?) -> boolean

Whether the text system still holds this handle — true between text.create and the text.destroy that released it.

Parameters

  • handle any (optional) — Text handle from text.create.

Returns boolean — True while the handle is live.

if not text.alive(h) then h = text.create({ content = "again" }) end

typed/builtin//modules/api/engine/text/text/count

text.count() -> number

How many text objects the text system is holding — the number that moves when text.create and text.destroy are called.

Returns number — The live text-object count.

local before = text.count()

typed/builtin//modules/api/engine/text/text/create

text.create(options: table) -> any

Create a text handle from an initial content + style table. The handle owns a runtime GPU texture (see text.textureGuid); pass it to every other call.

typed/builtin//modules/api/engine/text/text/destroy

text.destroy(handle: any?) -> boolean

Destroy a text handle and release its raster + glyph layout.

Parameters

  • handle any (optional) — Text handle from text.create.

Returns boolean — True when the text system held the handle and released it; false for a handle it did not have.

text.destroy(h)

typed/builtin//modules/api/engine/text/text/face

text.face(handle: any?) -> any

Which font face one handle actually shaped with, and whether that is the family its style asked for. requested is what was asked, resolved is the face that answered, matched says whether they agree and reason says why when they do not — one of text.faceReasons(). A style that named no family reports noFamilyRequested: it got the default because it asked for nothing, so reason rather than matched is what an alert switches on. faces lists every face the shaper used, most glyphs first, so a fallback that covered part of the string is visible alongside the face that covered the rest.

Parameters

  • handle any (optional) — Text handle from text.create.

Returns any{ requested, resolved, postScriptName, matched, reason, faces, glyphCount }, or nil for a handle the text system does not hold.

local r = text.face(h).reason; if r == "familyUnknown" or r == "familyNotSelectable" then print(r) end

typed/builtin//modules/api/engine/text/text/faceReasons

text.faceReasons() -> { string }

Every reason the face readings give for a label or a family not being in the family a style named, nearest cause first. text.face gives them for one label; font.reconcile() also gives familyCoveredNoGlyph, which it can only reach by laying the family out under its own weights and over several scripts.

Returns { string } — Array of reason strings.

for _, r in ipairs(text.faceReasons()) do print(r) end

typed/builtin//modules/api/engine/text/text/listFonts

text.listFonts() -> { string }

List the font families currently available to the text system.

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

local fonts = text.listFonts()

typed/builtin//modules/api/engine/text/text/loadFont

text.loadFont(ref: any?) -> any

Load a font from an asset reference so it becomes available to setStyle's fontFamily.

Parameters

  • ref any (optional) — Font asset reference or path.

Returns any — The loaded font-family name, or nil on failure.

text.loadFont(asset.ref("fonts.inter", "font"))

typed/builtin//modules/api/engine/text/text/measure

text.measure(handle: any?) -> any

Measure the rasterised text in pixels without producing a texture.

Parameters

  • handle any (optional) — Text handle from text.create.

Returns any — Table with width and height in pixels.

local size = text.measure(h)

typed/builtin//modules/api/engine/text/text/observe

text.observe() -> any

Everything the text system is holding right now. count is the live text objects; objects is one row each, carrying its content, the style it was laid out with, its measured extent, whether it is dirty, the face the shaper actually used, the owner entity whose component created it with whether that entity is still there, and the raster texture its last rasterisation landed in with the bytes it costs. orphans is the subset whose owning entity is gone, fonts the families the shaper can resolve, and raster the glyph-raster bytes with the pool they belong to named. Built when you ask, so it costs nothing per frame and reads the same in edit mode as in play.

Returns any{ count, objects, orphans, fonts, dirty, raster }.

local live = text.observe().count

typed/builtin//modules/api/engine/text/text/orphans

text.orphans() -> { any }

The text objects whose owning entity no longer exists — a quad the engine is still holding for something that has been despawned. Each row is the same shape text.observe().objects carries.

Returns { any } — Array of text-object rows with a dead owner.

print(#text.orphans() .. " labels outlived their entity")

typed/builtin//modules/api/engine/text/text/rasterMemory

text.rasterMemory() -> any

The glyph-raster bytes, broken out of the runtime GPU texture pool. bytes is summed off the same map renderer.gpuMemory().textures is totalled from, so shareOfPool is a share of that number rather than a second count of the same memory.

Returns any{ pool, bytes, textures, poolBytes, shareOfPool }.

local r = text.rasterMemory(); print(r.bytes .. " of " .. r.poolBytes)

typed/builtin//modules/api/engine/text/text/rasterize

text.rasterize(handle: any?, texture: any?, scale: number?) -> any

Rasterise the handle's current text + style into the given runtime GPU texture. Bind that texture's guid as a material's base_color_texture to display the text; re-rasterising the same texture overwrites it in place.

Parameters

  • handle any (optional) — Text handle from text.create.
  • texture any (optional) — Destination GPU texture handle (renderer.texture.create) or its guid string — WHERE the raster lands.
  • scale number (optional) — World/pixel scale factor for the raster (default 1.0).

Returns any — Table with width and height (in pixels), or nil if nothing rasterised.

local tex = renderer.texture.create({ width = 256, height = 64 })
local r = text.rasterize(h, tex, 1.0)

typed/builtin//modules/api/engine/text/text/setStyle

text.setStyle(handle: any?, style: table) -> boolean

Replace the handle's style. Fields not present keep their current value.

Parameters

  • handle any (optional) — Text handle from text.create.
  • style table — Style table (fontSize, color, alignment, outline, ...).

Returns boolean — True when the text system held the handle and took the style; false when it did not.

text.setStyle(h, { fontSize = 64, color = "yellow" })

typed/builtin//modules/api/engine/text/text/setText

text.setText(handle: any?, content: string) -> boolean

Replace the handle's text content.

Parameters

  • handle any (optional) — Text handle from text.create.
  • content string — New text string.

Returns boolean — True when the text system held the handle and took the content; false when it did not, which is how a caller learns its handle went away.

if not text.setText(h, "HP: 100") then h = text.create({ content = "HP: 100" }) end

typed/builtin//modules/api/engine/text/text/textureGuid

text.textureGuid(handle: any?) -> string?

The runtime GPU texture guid this handle rasterises into — bind it as a material texture (base_color_texture) to display the text.

Parameters

  • handle any (optional) — Text handle from text.create.

Returns string? — The texture guid string, or nil for a handle the text system does not hold.

entity(id).component.get("Material"):setTexture("base_color_texture", text.textureGuid(h))
  • api
  • reference