tecs.gfx.text

Shaped text as entities in the rendered world.

A Text names a font and a string. Transform2D places its top-left corner and Tint colors every glyph. The layout system turns one text into one glyph entity per drawn character, each an ordinary textured quad sampling the font's atlas, so text moves, layers, and draws through exactly the path a sprite does.

const font = assert(tecs.gfx.fonts.newTTF({source = "assets/fonts/JetBrainsMono-ExtraBold.ttf"}))
tecs.gfx.text.install(world)

world:spawn(
    tecs.ecs.Transform2D(24, 24),
    tecs.gfx.Tint(0.92, 0.96, 1.0, 1.0),
    tecs.gfx.Text("tecs\n1200 entities", font, 28, "center")
)

Write text fields through world:getMut. A write through world:get leaves the column clean and the drawn glyphs stale.

Layout#

Text supports explicit newlines, left, center, and right alignment, and wrapping to a width. It does not anchor outside the top-left corner or style individual glyphs. Alignment moves each line within the widest line of the block, and wrapping breaks between words, letting a single word wider than the wrap width overflow rather than splitting it.

Transform2D on a text entity scales the whole block: the glyph quads take their size from Text.size, and scaleX and scaleY multiply what comes out. That is the one place a Transform2D means a factor rather than a size in world units.

Glyph entities#

Every drawn character is an entity carrying Transform2D, Tint, Sprite, Renderable2D, and the TextGlyph tag. They belong to the layout system: spawning, despawning, or writing one by hand is undone the next time its text changes. A snapshot stores them like any other entity, and the layout system despawns the restored ones on its next run because no live text owns them, so a loaded world rebuilds its text rather than doubling it.

A layout reserves missing glyphs in one batch and initializes their native component columns at the following barrier. Existing glyphs retain their IDs shortening a label releases only its surplus, and unchanged labels do no layout or glyph-creation work.

Module contents

Constructors

ConstructorDescription
newTextBuilds a text value that belongs to no entity.

Types

TypeKindDescription
AligntypeSelects where a line sits within the widest line of its block.
TextrecordLays a string out into glyph entities.

Functions

FunctionKindDescription
glyphAtfunctionReturns one drawn glyph's world centre and size.
glyphsOffunctionReturns the glyph entities one text owns, in drawn order.
installfunctionInstalls text layout into a world.
isReadyfunctionReports whether a text's atlas has reached the backend.
layoutsfunctionReturns how many texts a world has laid out.
measureIntrinsicfunctionReturns the preferred and minimum-content metrics for a text item.
measureTextfunctionReturns a text item's width and height in world units.
refreshfunctionRefreshes changed text immediately after retained layout changes its boxes.
snapfunctionRounds a world coordinate to the nearest whole unit.

Values

ValueKindDescription
TextComponentvariableThe process-wide Text component definition.
TextGlyphvariableMarks an entity as one glyph the layout system owns.

Constructors#

newTextconstructor#

function newText(value: string?, font: fonts.Font?, size: number?, align: Align?, wrapWidth: number?): Text

Builds a text value that belongs to no entity.

measureText and measureIntrinsic take one of these, so a caller sizes a layout before spawning anything. Spawning goes through Text itself, which is the component and builds the same value.

Arguments

NameTypeDescription
valuestring?

the string to lay out, defaulting to empty

fontfonts.Font?

the font supplying the glyphs, or nil to draw nothing

sizenumber?

the em size in world units, defaulting to sixteen

alignAlign?

the alignment, defaulting to "left"

wrapWidthnumber?

the maximum line width in world units, defaulting to zero, which disables wrapping

Returns

TypeDescription
Text

the text value, which the caller owns

Raises

  • when the alignment is not one of its declared values, or when the wrap width is negative

Types#

Aligntype#

type Align = "left" | "center" | "right"

Selects where a line sits within the widest line of its block.

These identifiers are stored in a snapshot and are a compatibility surface.

Textrecord#

record Text
    text: string
    font: fonts.Font?
    size: number
    align: Align
    wrapWidth: number
    width: number
    height: number
end

Lays a string out into glyph entities.

The entity's Transform2D places the top-left corner of the text block and orients and scales the whole of it, and its Tint colors every glyph. Both are ordinary components on an ordinary entity, so a text moves, tweens, and layers like anything else.

Fields

text#
text: string

Caller-writable. Sets the laid-out string. A newline starts a line and wrapWidth may introduce further breaks.

font#
font: fonts.Font?

Caller-writable. Selects the font that supplies the glyphs. Without one, the text draws nothing.

size#
size: number

Caller-writable. Sets the em size in world units.

align#
align: Align

Caller-writable. Selects the alignment within the block's widest line.

wrapWidth#
wrapWidth: number

Caller-writable. Sets the maximum line width in world units. Zero disables wrapping.

width#
width: number

Engine-owned. Reports the width of the last layout in world units, before the Transform2D scale. Assigning it has no effect.

height#
height: number

Engine-owned. Reports the height of the last layout in world units, on the same terms. Assigning it has no effect.

Functions#

glyphAtfunction#

function glyphAt(borrows world: ecs.World, entity: integer, index: integer): number?, number?, number?, number?

Returns one drawn glyph's world centre and size.

Reads the glyph entity rather than laying the text out again, so it reports the placement that is being drawn.

Arguments

NameTypeDescription
borrows worldecs.World

the world that contains the text entity

entityinteger

an entity carrying Text

indexinteger

a one-based drawn-glyph index; whitespace takes no index

Returns

TypeDescription
number?

the world x of the glyph centre, or nil when no such glyph exists

number?

the world y of the glyph centre, or nil with the first return

number?

the glyph width in world units, or nil with the first return

number?

the glyph height in world units, or nil with the first return

glyphsOffunction#

function glyphsOf(borrows world: ecs.World, entity: integer): {integer}

Returns the glyph entities one text owns, in drawn order.

Arguments

NameTypeDescription
borrows worldecs.World

the world that contains the text entity

entityinteger

an entity carrying Text

Returns

TypeDescription
{integer}

a fresh array the caller owns, empty for a text with no glyphs

installfunction#

function install(exclusive world: ecs.World): nil

Installs text layout into a world.

Installing twice does nothing, so a world may install it directly rather than relying on the application having done so.

Arguments

NameTypeDescription
exclusive worldecs.World

the world receiving the layout system and its ownership tables

Returns

TypeDescription
nil

isReadyfunction#

function isReady(item: Text): boolean

Reports whether a text's atlas has reached the backend.

Extraction draws a glyph whose atlas is not resident against the backend's white fallback, which is a solid block rather than a letter, so a caller that must not show that waits on this. Asking queues whatever the atlas gained, exactly as tecs.gfx.fonts.imageOf does.

Arguments

NameTypeDescription
itemText

the text to inspect

Returns

TypeDescription
boolean

whether the font's atlas is resident, which is false without a font

layoutsfunction#

function layouts(borrows world: ecs.World): integer

Returns how many texts a world has laid out.

Counts rows laid out rather than frames, so a scene whose text is settled leaves it still. A test asserts on that to prove the dirty gate holds.

Arguments

NameTypeDescription
borrows worldecs.World

the world to inspect

Returns

TypeDescription
integer

a count that only increases, or zero before installation

measureIntrinsicfunction#

function measureIntrinsic(item: Text): number, number, number

Returns the preferred and minimum-content metrics for a text item.

The minimum-content width is the widest single word, which is the narrowest a wrapping layout can be made without splitting one.

Arguments

NameTypeDescription
itemText

a text value with a font

Returns

TypeDescription
number

the preferred width, with wrapping disabled

number

the preferred height, with wrapping disabled

number

the minimum-content width

measureTextfunction#

function measureText(item: Text, wrapWidth: number?): number, number

Returns a text item's width and height in world units.

Runs the same shaping the layout system runs, without touching an entity or making a glyph resident.

Arguments

NameTypeDescription
itemText

a text value to read, which need not belong to an entity

wrapWidthnumber?

overrides item.wrapWidth for this measurement alone, without changing the authored value

Returns

TypeDescription
number

the width at item.size, before the Transform2D scale, which is zero without a font

number

the height on the same terms, counting complete line boxes

refreshfunction#

function refresh(exclusive world: ecs.World): nil

Refreshes changed text immediately after retained layout changes its boxes.

Arguments

NameTypeDescription
exclusive worldecs.World

the world whose text and glyph entities are synchronized

Returns

TypeDescription
nil

snapfunction#

function snap(value: number): number

Rounds a world coordinate to the nearest whole unit.

An alpha-raster font drawn at its loaded size lines its texels up with the target's only when the block's origin is whole, so a screen-space label snaps its position through this before writing it.

Arguments

NameTypeDescription
valuenumber

the coordinate to snap

Returns

TypeDescription
number

the nearest whole value, with a half rounding away from zero

Values#

TextComponentvariable#

The process-wide Text component definition.

The component name and the text, font, size, align, and wrapWidth snapshot keys are a compatibility surface. A snapshot stores the font by name, because a font is a shared table rather than a value.

TextGlyphvariable#

const TextGlyph: components.Component

Marks an entity as one glyph the layout system owns.

Game code neither adds nor removes it. It is public so a query can exclude the glyphs a text produced, and its name is a compatibility surface.