DEMIENGINEDOCUMENTATION Everything is free
UI & HUD · Create

Fonts, variables, localization, and styles.

Text, theme & locale

This page covers HUD fonts, variables, theme styles, and localization. The text module can parse rich-text markup for scripts, but HUD nodes currently render plain text.

Fonts

A font is a Font2D asset. Reference it by URI on any text-bearing node:

{
  "id": "title",
  "type": "label",
  "text": "Skyforge",
  "font": "asset://fonts/head",
  "font_size": 48
}
  • font — an asset:// URI naming a Font2D asset. Optional; the default is the embedded Inter Variable face.
  • font_size — height in canvas units. Scales with the canvas like every other dimension.

Change them at runtime with Hud.set_font(id, uri) and Hud.set_font_size(id, n); see HUD from Lua.

Wrapping, alignment, overflow

Long text needs a policy: how it breaks, where it sits in its box, and what happens when it still does not fit.

{
  "id": "objective",
  "type": "text",
  "text": "Hold the forge until the gate seals.",
  "text_wrap": "word",
  "text_alignment": "start",
  "text_vertical_alignment": "center",
  "line_spacing": 4,
  "max_lines": 3,
  "text_overflow": "ellipsis",
  "size": [420, 96]
}
FieldValuesMeaning
text_wrapnone, word, graphemeNever break; break at word boundaries; break between any two characters (for narrow CJK columns).
text_alignmentstart, center, endHorizontal placement of each line in the box.
text_vertical_alignmentstart, center, endWhere the text block sits vertically in the box.
line_spacingnumberExtra canvas units between lines.
max_linesnumberCaps how many lines are drawn.
text_overflowvisible, clip, ellipsisDraw past the box; cut at the box edge; cut with “…”.

The example renders up to three wrapped lines, centered vertically, left-aligned, and elides with “…” if the objective text still overflows.

${variables} interpolation

Text can reference named variables. At draw time each ${name} is replaced with the variable’s current value.

{
  "format_version": 1,
  "variables": ["score"],
  "children": [
    { "id": "score", "type": "label", "text": "Score: ${score}" }
  ]
}
  • Top-level variables in the HUD document is an array of variable names — never a name→value map. Values are runtime strings set from Lua with Hud.set_variable / Hud.set_variables; unresolved names stay visible as ${name}.
  • Node text may mix literal words and ${name} markers anywhere in the string.
  • Interpolation happens when text is displayed, so one node definition can show changing values.
local Hud = require("demi.hud")

Hud.set_variable("score", "1200")
Hud.set_variables({ score = "1300", game_title = "Skyforge II" })
local has = Hud.has_variable("score")      -- boolean
local v = Hud.get_variable("score")        -- string or nil
Hud.reset_variables()                      -- clear runtime values
  • Hud.set_variable(name, value) sets one value (a string). Returns true when something changed, plus an error string.
  • Hud.set_variables(table) sets several at once. Same return pair.
  • Hud.has_variable(name) returns a boolean.
  • Hud.get_variable(name) returns the current string, or nil when the name is unknown.
  • Hud.reset_variables() clears every runtime value; unresolved names render as ${name}. No return value.

The score label redraws with the new number — no need to rebuild the node.

Rich text tags

Text.parse_rich(markup, strict?) parses supported tags into plain text, spans, and diagnostics for scripts. HUD labels do not consume those spans, so putting tags directly in a node's text does not style it.

local Text = require("demi.text")
local parsed = Text.parse_rich("[strong]Forge tip:[/strong] hold guard")
-- parsed.text, parsed.spans, parsed.diagnostics
TagParsed metadata
[color=…][/color]A color span with a value such as #ffd27a.
[em][/em]An emphasis span.
[strong][/strong]A strong span.
[link=…][/link]A link span with a value.
[icon=…]A zero-length icon span with a value.

The parser only describes spans; it does not make HUD text bold, italic, linked, or insert an image.

Theme styles

A theme is a JSON document of reusable styles. Reference one from the HUD and apply it per node with style.

{
  "format_version": 1,
  "styles": {
    "menu_button": {
      "font_size": 28,
      "color": "#f2f4ff",
      "background_color": "#2a2f45ff",
      "padding": [24, 12, 24, 12],
      "min_size": [280, 52]
    },
    "title": {
      "font_size": 48,
      "color": "#ffffff"
    }
  },
  "action_map": { "submit": "ui_ok" }
}
  • styles — map of style name to supported fields: color, background_color, text_color, padding, gap, font_size, min_size, control_height, and row_height. Set font directly on the node.
  • action_map — optional map from HUD navigation slots (next, previous, submit, cancel) to project input action names.

Applying a style in a HUD document:

{
  "format_version": 1,
  "theme": "../ui/themes/dark.json",
  "children": [
    { "id": "title", "type": "label", "text": "${game_title}", "style": "title" },
    { "id": "start", "type": "button", "text": "Start", "style": "menu_button", "action": "ui_ok" }
  ]
}
  • Top-level theme names the theme file path, relative to the HUD file.
  • style on a node copies that style’s fields onto the node.
  • Explicit node text_color, font_size, and min_size take precedence. Styles can still replace node color and background_color; they fill padding and gap when those values are zero.

Why themes: one change to menu_button restyles every menu button in Skyforge. Individual exceptions stay readable on the node.

Localization

A localization file supplies initially loaded strings. The HUD references one file; each node pulls its string by key when the document loads. Runtime locale switching uses the HUD's embedded locales map.

{
  "format_version": 1,
  "localization": {
    "play": "Spielen",
    "quit": "Beenden"
  }
}
{
  "format_version": 1,
  "localization_file": "../ui/de.localization.json",
  "children": [
    { "id": "start", "type": "button", "text": "Start", "localization_key": "play", "style": "menu_button" },
    { "id": "seed", "type": "text_input", "placeholder_localization_key": "seed_hint" }
  ]
}
  • The file holds a flat localization object mapping string keys to translated text. A HUD loads only the file named by localization_file.
  • localization_file on the HUD names that file, relative to the HUD file.
  • A node’s localization_key (and placeholder_localization_key for text inputs) selects the string; its value replaces the node’s text (or placeholder) when the document loads.
  • For runtime switching, embed a locales object plus a default locale in the HUD:
{
  "format_version": 1,
  "locale": "en",
  "locales": {
    "en": { "play": "Start", "quit": "Quit" },
    "de": { "play": "Spielen", "quit": "Beenden" }
  },
  "children": [
    { "id": "start", "type": "button", "text": "Start", "localization_key": "play" }
  ]
}
local Hud = require("demi.hud")

Hud.set_locale("de")             -- reapply every localization key in German
  • Hud.set_locale(code) selects a loaded entry in locales and reapplies localization keys. It cannot load another localization_file. Returns true on success, plus an error string; the last valid locale is kept on failure.
  • ${name} variables are a separate native-variable mechanism — they are interpolated, not localized.

Pseudo-locale QA

Translated text is often longer than English. Pseudo-localization brackets strings and repeats vowels so some layout bugs show up before real translations arrive.

Hud.set_pseudo_locale(true)      -- stress-test layout with fake translations
  • Hud.set_pseudo_locale(enabled) takes a boolean and switches displayed strings to a pseudo-locale build of the current strings. No return value.
  • Localized strings render with doubled vowels inside brackets (English “Start” becomes “[Staart]”), making some truncation and wrapping problems easier to see.
  • Run Skyforge’s menu in pseudo-locale before each release; fix anything that clips or overflows.

Next