Skip to content

Text and fonts ​

A Text node draws a string. A Font is the typeface it uses. willow draws most fonts as signed distance fields (SDF), so text stays sharp at any size and scale.

A label ​

ts
import { Text, Font } from "willow";

class Title {
    static main(): void {
        const title: Text = new Text("Welcome", Font.system());
        title.fontSize = 48;
        title.color = Color.hex("#FFE9A8");
        title.align = "center";
        title.alignPivot();
        title.x = 400;
        title.y = 120;
        engine.root2d.add(title);
    }
}

new Text(string) without a font uses the default font. Assigning text, font, fontSize or style lays the text out again on the next frame.

Text properties ​

PropertyMeaningDefault
textThe string.
fontThe Font.willow's bundled font
fontSizeSize in pixels.16
colorFill colour, a Color.white
align"left", "center" or "right"."left"
wrapWidthWrap lines to this width in pixels. 0 means no wrapping.0
lineHeightLine spacing multiplier.1
style"regular", "bold", "italic" or "boldItalic"."regular"
sharpnessSDF edge sharpness, 0 to 1. Higher narrows the soft edge.0
pixelSnapPuts the text on whole pixels when not rotated, which keeps small text crisp. Turn it off for slowly moving text so it glides.true
width, heightMeasured size, after the first layout.

measure() lays the text out right away and returns its { width, height }, so you can size a panel before the first frame.

Outline, glow and shadow ​

Each Text has an effects object. A width of 0 turns an effect off.

ts
import { Text } from "willow";

class Damage {
    static main(): void {
        const hit: Text = new Text("-25");
        hit.fontSize = 36;
        hit.color = Color.hex("#FF5A4F");
        hit.effects.outlineWidth = 3;
        hit.effects.outlineColor = Color.BLACK;
        hit.effects.shadowOffsetX = 2;
        hit.effects.shadowOffsetY = 3;
        hit.effects.shadowColor = Color.hex("#00000099");
        hit.x = 300;
        hit.y = 200;
        engine.root2d.add(hit);
    }
}

glowWidth and glowColor add a soft glow.

Fonts ​

FactoryLoads
Font.system()willow's bundled default font.
Font.load(path)A .fontbundle file (baked ahead of time with thicket tool fontgen).
Font.fromBuffer(buffer)A font bundle already in memory.
Font.loadTTF(path, config?)A TTF or OTF file, baked once while it loads.
Font.fromTTF(buffer, config?)TTF or OTF bytes in memory, baked once.
Font.loadTTFCached(ttf, cacheDir, options?)A TTF or OTF, baked the first time and saved as a bundle in cacheDir; later runs load the saved bundle.
Font.fromPixelFont(texture, cellW, cellH, chars)A bitmap font sheet of fixed-size cells, in row order.

Baking never happens per frame. For a shipped game, bake fonts ahead of time with fontgen or let loadTTFCached keep the result.

ts
import { Text, Font } from "willow";

class Fonts {
    static main(): void {
        const body: Font = Font.loadTTFCached("assets/fonts/Inter.ttf", ".cache/fonts", { sizes: [32], charset: "latin" });
        const pixel: Font = Font.fromPixelFont("assets/fonts/pixel.png", 8, 8, " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ");

        const a: Text = new Text("Smooth at any size", body);
        a.fontSize = 28;
        a.y = 40;
        engine.root2d.add(a);

        const b: Text = new Text("PIXEL FONT", pixel);
        b.fontSize = 16;
        b.y = 100;
        engine.root2d.add(b);
    }
}

font.measure(text, size) measures a string without making a node.

Text input ​

A Text with type = TextFieldType.INPUT takes typing while it has focus. Give it focus with engine.scene.focus = field.

ts
import { Text, TextFieldType, TextInputEvent } from "willow";

class NameEntry {
    static main(): void {
        const field: Text = new Text("");
        field.type = TextFieldType.INPUT;
        field.maxChars = 12;
        field.restrict = /[A-Za-z0-9 ]/;
        field.x = 100;
        field.y = 100;
        engine.root2d.add(field);
        engine.scene.focus = field;

        field.onTextInput((e: TextInputEvent): void => {
            if (e.text === "#") {
                e.preventDefault();
            }
        });
        field.onChange((): void => {
            console.log("name is now " + field.text);
        });
    }
}
  • maxChars limits the length; restrict is a pattern each typed character must match.
  • onTextInput runs before text is added and can cancel it with preventDefault().
  • onChange runs after typing or Backspace changes text.
  • onTextComposition reports IME pre-edit text (for languages typed through an input method). It never changes text.

A plain input Text supports typing and Backspace at the end. For a caret, selection and clipboard, use willowui's TextInput or TextArea.

Next ​