Log inGet started

.font asset type

Updated October 6, 2026

A font is a general CPU resource usable by any text surface. The onRegister hook calls the engine font.register(name, bytes) primitive, which installs the parsed face into both the 2D/3D text system (text.*) and the egui UI text system, under the family name the font file itself reports. A registration is what puts the face in the database; whether fontFamily = "<name>" selects it is the separate question of whether the shaper matches that name, and font.reconcile() answers it per family:

for _, f in ipairs(font.reconcile()) do
    if not f.selectable then
        print(f.family .. " is registered but shapes as " .. f.shapedWith)
    end
end

text.face(handle) gives the same answer for one label. A family the shaper's database holds is selectable under its own weights and for the glyphs its faces cover: a row that reads familyCoveredNoGlyph names a family the shaper picks whose faces have no glyph for the text asked of it. topics/text covers reading a label that came out in the wrong face.

Fonts are CPU resources (not GPU), so the primitive lives in the font.* namespace, the CPU analogue of renderer.texture.create.

Layout

<name>.font/
  data.zfnt         the baked payload `asset.create("font", ...)` writes
  face.json         optional: family group, role, aliases, on-demand entries

Editing a font's files

A write to a file the registration reads (face.json, data.zfnt, or a raw font file in the folder) installs the font again from what the folder now holds. The names the new face.json gives it (its group and each alias) select it on every text surface from then on, and a name the earlier descriptor gave it and the new one does not stops selecting it; a changed group moves the face to that group. Text laid out under a name the edit decides is laid out again, and a Text3D label draws it again on the next frame. For an on-demand face the same holds for the font and coverage files its entries name: the entries are declared again from their coverage files, and an entry already installed is registered again from its font file with the names the descriptor now gives.

On-demand faces

A face whose file is large and whose script most sessions never draw is declared by the codepoints it covers instead of being read at boot. Its face.json lists the font files of the folder under onDemand, each beside a coverage file:

NotoSansCJK.font/
  face.json           { group, role, aliases, license, onDemand = [ { name, file, coverage }, ... ] }
  kana.otf            a font file (TTF / OTF)
  kana.coverage       the codepoints it maps: "3000-30ff,ff01-ff5e,..."
  ...
  OFL.txt             the license the files are distributed under

onRegister reads the coverage files and calls font.declare(name, coverage, { file, family, role, aliases }) for each entry; no font file is read. The first time a label or a text.* object meets a character an entry covers and no installed face draws, the engine requests that entry: its file is read without blocking the frame and registered with font.register, and from then on every text surface draws the character from it. font.onDemand() reports each entry as declared, requested, installed or failed, and font.prefetch(text) requests the entries a text needs before it is drawn.

font.coverage(bytes) writes a coverage file from a font file's character map. Splitting one large face into several entries (by script, and by how common a character is) means a session reads only the part its text reaches: the built-in NotoSansCJK.font is cut that way by scripts/fonts/build_cjk_fallback.py, and a page of Japanese reads its kana and common-ideograph entries alone.

Every registered face, on-demand or not, is a per-glyph fallback for every family the UI holds: a character the selected family has no glyph for is drawn from the first face that has one.

In the Inspector

Selecting a font opens its Font section: the family the face registers under, its role and aliases from face.json, whether the text system has the family registered and, when it holds it, how many faces and which weights. Below the facts, a sample line is drawn in the family at 14, 20 and 32 px.

  • asset-type
  • reference