Text

Fonts, text measurement, and prepared text drawing.

A string is described once and drawn many times. Text.from starts a Builder, the size, spacing and font receivers adjust it, and prepare! hands the host the UTF-8 bytes, the style, and the measured bounds. What comes back is a Prepared, which every later frame draws with no encoding, no measuring, and no allocation:

title = Text.from("Hello", font).size(38).prepare!()?
model.title.draw!(frame, { pos: { x: 400, y: 60 }, color: Color.white, align: Text.align_top_center })

align says which point of the text pos names, so centring a title needs no measurement at the call site. When one is wanted anyway -- to size a panel around the text, or to hit-test it -- prepared.bounds() answers the size the host measured, and Draw.Font.measure answers the same question for a string that was never prepared. Both are pure, so layout can be decided in update! and kept in the model.

Most calls have a receiver form and a free-function form: prepared.draw!(frame, placement) and Text.draw_prepared!(frame, cfg) are the same drawing call, and builder.prepare!() and Text.prepare_builder!(builder) are the same preparation. Prefer the receiver. Note that the two draw forms take the frame in different positions: the receiver's own value comes first, so the frame is its second argument, while the free function takes the frame first as every other free drawing function does.

The two halves live in different phases. Preparing text allocates a host resource: it is legal in init!, update!, and tasks, and refused in render!. Drawing prepared text is legal in render! only, inside the frame scope the host opens around it. Prepare the strings an app draws repeatedly once, in init!, and keep the Prepared values in the model.

default_spacing : F32

The letter spacing a Builder starts with, and what raylib's own text drawing uses.

from : Str, Font -> Builder

Start describing a string drawn in a font. Adjust the result with size, spacing and font, then call prepare!.

prepare_builder! : Builder => Try(Prepared, [ResourceLimit, ..])

Prepare a builder's text, as Builder.prepare! does.

Legal in init!, update!, and tasks; refused in render!.

align_top_left : Align

Anchor pos at the top-left corner of the text.

These nine constants are the ones to use with Placement. Draw has a set under the same names for Draw.text_at!, which draws an unprepared string.

align_center : Align

Anchor pos at the centre of the text, in both axes.

align_offset : Size, Align -> Vec2

How far the anchor named by an Align sits from the text's top-left corner, for a text of this size. origin_for is what a draw uses; this is the piece it is built from.

origin_for : Vec2, Size, Align -> Vec2

The top-left corner a text of this size must be drawn at for its align anchor to land on pos. Pure, so an app can compute the same box a draw will occupy without drawing anything.

draw_prepared! : Frame, { text : Prepared, pos : Vec2, color : Rgba, align : Align } => {  }

Draw prepared text, as Prepared.draw! does.

Legal in render! only.

Prefer the receiver. This form takes the frame first, like every other free drawing function, and takes the text as a field of its config record rather than as its own argument.

HAlign : [Left, Center, Right]

Which horizontal edge or centre of the text pos names.

VAlign : [Top, Middle, Bottom]

Which vertical edge or centre of the text pos names.

Align : {
    horizontal : HAlign,
    vertical : VAlign,
}

Where pos sits within the text, in both axes at once. The nine align_* values below are every combination, named.

Size : Size

A measured width and height, in the same logical units as every drawing call. This is Draw.TextSize and the types package's Font.Size under a third name; they are one type.

Placement : {
    pos : Vec2,
    color : Rgba,
    align : Align,
}

Everything a draw needs beyond the text itself: where to put it, what colour to paint it, and which point of it pos names.

Builder

Text.Builder :: # (opaque)

A string and the style to prepare it with. Start one with Text.from, adjust it with the receivers below, and finish it with prepare!.

A builder is a plain description, so building one costs nothing and it can be assembled anywhere. Only prepare! reaches the host.

size : Builder, F32 -> Builder

Draw this text at a different pixel size. The default is 20.

Sizes far above the font's own base_size scale its glyph atlas up rather than re-rasterizing, so load the font at the size wanted when the difference shows.

spacing : Builder, F32 -> Builder

Change the space added between glyphs. The default is Text.default_spacing.

font : Builder, Font -> Builder

Draw this text in a different font.

prepare! : Builder => Try(Prepared, [ResourceLimit, ..])

Cache immutable UTF-8 text, font and style, and measurement in the host.

Legal in init!, update!, and tasks; refused in render!.

Prepared

Text.Prepared :: # (opaque)

Host-owned immutable text. Its ARC handle retains any loaded font and its cached native NUL-terminated bytes are reused by every draw.

bounds : Prepared -> Size

The size the host measured while preparing this text.

Pure, and free: the measurement was taken once, at preparation. Use it to size a panel around the text or to hit-test it, in update! as readily as in render!.

draw! : Prepared, Frame, Placement => {  }

Draw this prepared text at a placement.

Legal in render! only.

The frame is the second argument here because the prepared text is the receiver; Text.draw_prepared! is the same call with the frame first.

stub : Prepared

Resource-free prepared text for pure tests.

Prepared text carries more than a handle: the host measured it once while preparing it, and the value keeps that size. A stub has no measurement to keep, so its measured bounds are zeroed -- bounds() answers { width: 0, height: 0 } and every alignment therefore resolves to the placement point itself. Copy this value with the bounds a test needs, the way the roc-ray-types package's Texture.stub is copied with dimensions.

The handle never resolves to a host resource, so drawing it is skipped the way a released one is. Do not use it to test drawing, measurement, or resource lifetime.