Text and fonts
Drawing and measuring text, bitmap and MSDF fonts, and localization.
Files: text_primitives.odin, fonts.odin, font_compile.odin (#+build !js), localization.odin.
File overviews
Section titled “File overviews”fonts.odin
Section titled “fonts.odin”Fonts besides TrueType: bitmap fonts (BMFont) and MSDF fonts. Every text the engine draws goes
through DrawText / MeasureText with a font’s path, so Labels, Menus, Buttons and RichText
use them with no change.
- Bitmap fonts: a
.fntfile (AngelCode BMFont, text or XML, the format Hiero, BMFont, Littera and Snow’s tools write) and its page images beside it. Each glyph is a picture: right for pixel-art lettering, drawn at whole multiples of its size (sizescales it; the font’s own size draws it as made). - MSDF fonts: a TrueType font whose asset says
msdf="true"(asset_import.odin) gets an MSDF atlas made by the editor (msdf-atlas-gen:<font>.msdf.bmpand<font>.msdf.jsonbeside it). On the GPU renderer, text in it is drawn from the atlas through an MSDF shader (shaders/text/msdf.frag): sharp at any size and zoom, and turned with its node. Elsewhere (no GPU renderer, the web) the font draws as a TrueType font, the same letters.
Both are laid out by the engine (fontLayout): lines broken at newlines and wrapped at spaces,
kerning pairs, the line height and ascent the font gives. A glyph the font lacks draws as
nothing (an MSDF font’s atlas has the characters it was made with: printable ASCII and Latin-1
by default).
font_compile.odin
Section titled “font_compile.odin”Making MSDF fonts’ atlases (fonts.odin) with msdf-atlas-gen, one of the shader tools
(tools/build_shader_tools.sh builds it into shader_tools/<os>_<arch>/): a TrueType or OpenType
font becomes <font>.msdf.bmp (three distance channels) and <font>.msdf.json (where each glyph
is, its metrics, the kerning) beside it. Editors call it when a font asset’s MSDF setting is on,
and again when the font file changes; exports ship both files.
localization.odin
Section titled “localization.odin”Localization: a game’s text in several languages, from CSV tables like Godot’s.
A table’s first row names the languages (the first column, the keys, is named anything); each
row after it is a key and its text in each language. Rows with an empty key or one starting
with # are comments. Quotes allow commas and line breaks (standard CSV):
key,en,fr,demenu.play,Play,Jouer,Spielenmenu.quit,Quit,Quitter,Beenden# The HUDhud.coins,"Coins: %d","Pièces : %d","Münzen: %d"A project’s tables are its assets of kind “Translation” (<asset path="i18n/strings.csv" kind="Translation"/>, format 13), loaded in their order, and it may pick the language
(locale="fr"); without it the player’s system language is used when
a table has it, else the first column’s. Tr(key) gives a key’s text in the current language
(falling back to the first column’s, then the key itself), and Labels and Menu items whose
text starts with @ are keys (“@menu.play”), translated as they draw, so switching the
language (Locale_Set, from an options menu) changes them at once. “@@” starts text with a
literal “@”.
Example
// Given: coinCount: int, item: ^e.MenuItemcoins := fmt.tprintf(e.Tr("hud.coins"), coinCount)_ = coins// An options menu's language selecte.Locale_Set(game, e.Locale_List(game)[item.selectedOption])GlyphFont
Section titled “GlyphFont”GlyphFont :: struct { msdf: bool, // Pixels per em the glyphs were made at (sizes scale from it) size: f32, // At that size: from one line to the next, and from a line's top to its baseline lineHeight, ascent: f32, glyphs: map[rune]FontGlyph, kerning: map[[2]rune]f32, // The pictures, by page pages: [dynamic]^sdl.Texture, // MSDF: the atlas's distance range in its pixels, and the shader drawing it pxRange: f32, material: ^ShaderMaterial,}A font drawn from pictures of its glyphs: a bitmap font, or an MSDF font’s atlas
struct. Source: fonts.odin:41
FontGlyph
Section titled “FontGlyph”FontGlyph :: struct { page: int, // Its picture in the page, in pixels src: sdl.FRect, // Where the picture goes from the pen (x) and the line's top (y), and its size offset, size: [2]f32, // How far the pen moves after it advance: f32,}One glyph of a GlyphFont, at the font’s size
struct. Source: fonts.odin:57
BMFontData
Section titled “BMFontData”BMFontData :: struct { // `info size` (pixels per em; positive), `common lineHeight`, `base` size, lineHeight, base: f32, // The page images, relative to the .fnt file pages: [dynamic]string, // The `char` lines chars: [dynamic]BMFontChar, // The `kerning` lines kernings: [dynamic]BMFontKerning,}A BMFont file’s contents (ParseBMFont)
struct. Source: fonts.odin:68
BMFontChar
Section titled “BMFontChar”BMFontChar :: struct { // The character id: rune, // The picture's place and size on its page; where it's drawn from the pen (x) and the // line's top (y); how far the pen moves after it (pixels at the font's size) x, y, width, height, xoffset, yoffset, xadvance: f32, // The page image it's on (an index into `BMFontData.pages`) page: int,}A BMFont file’s char line: one glyph and where its picture is
struct. Source: fonts.odin:80
BMFontKerning
Section titled “BMFontKerning”BMFontKerning :: struct { // The character on the left, and the one after it first, second: rune, // What's added to the pen's move between them (pixels; negative is closer) amount: f32,}A BMFont file’s kerning line: a pair of characters drawn closer or further apart
struct. Source: fonts.odin:91
Localization
Section titled “Localization”Localization :: struct { // Languages, in the order the tables named them (the first is the fallback) locales: [dynamic]string, // Each key's text, one per language ("" where a table has none); shorter when a later table // added languages texts: map[string][]string, // The language in use: an index into `locales` current: int,}A game’s translation tables, merged (Game.localization; in the game’s arena)
struct. Source: localization.odin:42
Procedures
Section titled “Procedures”DrawText
Section titled “DrawText”DrawText :: proc(rm: ^ResourceManager, pos: sdl.FPoint, text: string, font: string, size: f32, color: sdl.Color)Draws a line of text (newlines break it), its top-left corner at pos.
Inputs
- rm: The resource manager the font is loaded through (
&game.resourceManager) - pos: The text’s top-left corner, in local coordinates
- text: The text (UTF-8)
- font: A font file path, or
DEFAULT_FONT - size: The point size (scaled by the draw transform, rounded to whole points)
- color: The color (multiplied by the node’s tint)
Example
e.DrawText(&game.resourceManager, {16, 16}, "Score: 100", e.DEFAULT_FONT, 24, {255, 255, 255, 255})procedure. Source: text_primitives.odin:29
DrawTextWrapped
Section titled “DrawTextWrapped”DrawTextWrapped :: proc(rm: ^ResourceManager, pos: sdl.FPoint, text: string, font: string, size: f32, color: sdl.Color, wrapWidth: f32)Draws text wrapped to fit a width, its top-left corner at pos.
Inputs
- rm: The resource manager the font is loaded through (
&game.resourceManager) - pos: The text’s top-left corner, in local coordinates
- text: The text (UTF-8)
- font: A font file path, or
DEFAULT_FONT - size: The point size (scaled by the draw transform, rounded to whole points)
- color: The color (multiplied by the node’s tint)
- wrapWidth: The width to wrap lines at, in local units. 0 only breaks on newlines
Example
e.DrawTextWrapped(&game.resourceManager, {16, 16}, "A long line of dialogue...", e.DEFAULT_FONT, 18, {255, 255, 255, 255}, 300)procedure. Source: text_primitives.odin:48
DrawTextCentered
Section titled “DrawTextCentered”DrawTextCentered :: proc(rm: ^ResourceManager, pos: sdl.FPoint, text: string, font: string, size: f32, color: sdl.Color)Draws a line of text centered on pos, for titles and buttons.
Inputs
- rm: The resource manager the font is loaded through (
&game.resourceManager) - pos: The text’s center, in local coordinates
- text: The text (UTF-8)
- font: A font file path, or
DEFAULT_FONT - size: The point size
- color: The color (multiplied by the node’s tint)
Example
e.DrawTextCentered(&game.resourceManager, sdl.FPoint(game.size * 0.5), "PAUSED", e.DEFAULT_FONT, 48, {255, 255, 255, 255})procedure. Source: text_primitives.odin:99
MeasureText
Section titled “MeasureText”MeasureText :: proc(rm: ^ResourceManager, text: string, font: string, size: f32, wrapWidth: f32 = 0) -> sdl.FPointMeasures the text without drawing it.
Inputs
- rm: The resource manager the font is loaded through (
&game.resourceManager) - text: The text (UTF-8)
- font: A font file path, or
DEFAULT_FONT - size: The point size
- wrapWidth: The width to wrap lines at, in pixels. 0 only breaks on newlines
Returns
- The width and height, in pixels at that size (no draw transform applied); {0, 0} for empty text or a font that can’t be loaded
procedure. Source: text_primitives.odin:118
Font_IsBitmap
Section titled “Font_IsBitmap”Font_IsBitmap :: proc(path: string) -> boolWhether a font path is a bitmap font
procedure. Source: fonts.odin:99
ParseBMFont
Section titled “ParseBMFont”ParseBMFont :: proc(text: string, allocator := context.allocator) -> (data: BMFontData, ok: bool)Reads a BMFont file, text or XML (binary .fnt files aren’t read: export text or XML).
Inputs
- text: The file’s contents
- allocator: For the lists and page names
Returns
- The font’s data (free with
BMFontData_Destroy), and false when it isn’t a BMFont file
procedure. Source: fonts.odin:113
BMFontData_Destroy
Section titled “BMFontData_Destroy”BMFontData_Destroy :: proc(data: ^BMFontData, allocator := context.allocator)Frees what ParseBMFont made
procedure. Source: fonts.odin:188
ResourceManager_SetFontMSDF
Section titled “ResourceManager_SetFontMSDF”ResourceManager_SetFontMSDF :: proc(rm: ^ResourceManager, path: string, on: bool)Turns a font’s MSDF atlas on or off (asset_import.odin: Game_LoadProject does it for the
project’s font assets with msdf="true"). The atlas files are read the first time text in the
font draws on the GPU renderer.
Inputs
- rm: The resource manager
- path: The font, as text is drawn with it (the project folder joined)
- on: Whether it has an atlas
procedure. Source: fonts.odin:206
ResourceManager_ForgetGlyphFont
Section titled “ResourceManager_ForgetGlyphFont”ResourceManager_ForgetGlyphFont :: proc(rm: ^ResourceManager, path: string)Forgets a font’s glyph pictures, so the next text reads its files again (an editor made a new MSDF atlas, or saved a bitmap font).
procedure. Source: fonts.odin:225
Font_MakeMSDF
Section titled “Font_MakeMSDF”Font_MakeMSDF :: proc(tools: ShaderTools, font, scratch: string, charset := MSDF_DEFAULT_CHARSET, size := MSDF_DEFAULT_SIZE, force := false) -> (messages: string, ok: bool, made: bool)Makes a font’s MSDF atlas beside it, when the font is newer than its atlas (or force).
Inputs
- tools: The shader tools (
ShaderTools_Find), with msdf-atlas-gen - font: The
.ttf/.otffile - scratch: A folder for the charset file (made when missing)
- charset: msdf-atlas-gen’s charset syntax: ranges
[32, 126], characters'A', strings"abc" - size: Pixels per em in the atlas
- force: Make it even when it’s up to date
Returns
- The tool’s messages when it failed, whether it worked, and whether it made the atlas (false: up to date)
procedure, only in builds with #+build !js. Source: font_compile.odin:38
Locale_LoadCSV
Section titled “Locale_LoadCSV”Locale_LoadCSV :: proc(game: ^Game, text: string, source := "translations") -> boolReads a translation table (CSV, as above) into the game’s, adding its keys and languages to those already loaded (a key loaded again takes the new text).
Inputs
- game: The game
- text: The CSV
- source: Its name, for messages
Returns
- false (logged) when the table can’t be parsed
Example
e.Locale_LoadCSV(game, "key,en,fr\nmenu.play,Play,Jouer\n", "builtin")procedure. Source: localization.odin:67
Locale_LoadFile
Section titled “Locale_LoadFile”Locale_LoadFile :: proc(game: ^Game, path: string) -> boolReads a translation table from a file (the data pack first: platform layer), as Locale_LoadCSV.
Inputs
- game: The game
- path: The file
Returns
- false (logged) when it can’t be read or parsed
Example
e.Locale_LoadFile(game, "i18n/extra.csv")procedure. Source: localization.odin:135
Locale_Set
Section titled “Locale_Set”Locale_Set :: proc(game: ^Game, locale: string) -> boolSwitches the language: an exact name (“pt_BR”), else one of the same language (“fr” for “fr_CA”, or “fr_FR” for “fr”).
Inputs
- game: The game (nil: the one playing)
- locale: The language’s name, as the tables’ first row has it
Returns
- false when no table has it (the language stays)
Example
if !e.Locale_Set(game, "fr") do log.warn("no French text")procedure. Source: localization.odin:159
Locale_Get
Section titled “Locale_Get”Locale_Get :: proc(game: ^Game = nil) -> stringThe language in use.
Inputs
- game: The game (nil: the one playing)
Returns
- Its name as the tables’ first row has it (“” when no table is loaded); the game’s memory
procedure. Source: localization.odin:184
Locale_List
Section titled “Locale_List”Locale_List :: proc(game: ^Game = nil) -> []stringThe languages the tables have, in their order (the first is the fallback).
Inputs
- game: The game (nil: the one playing)
Returns
- The names (the game’s: don’t keep the slice past it, or past loading another table); nil without a game
procedure. Source: localization.odin:199
Locale_System
Section titled “Locale_System”Locale_System :: proc() -> stringThe player’s preferred language, as SDL reports it (“fr_CA”, or “fr”), temp allocated; “” when unknown.
procedure. Source: localization.odin:209
Tr :: proc(key: string, game: ^Game = nil) -> stringA key’s text in the current language; the first language’s when it has none there, and the key itself when no table has it (so missing text shows up as its key).
Inputs
- key: The key, as the tables’ first column has it
- game: The game (nil: the one playing)
Returns
- The text (the game’s memory: valid while it runs)
Example
e.CreateLabel(scene, e.Tr("title.press_start"), {640, 500}, 24, {255, 255, 255, 255})procedure. Source: localization.odin:233
Tr_Text
Section titled “Tr_Text”Tr_Text :: proc(text: string, game: ^Game = nil) -> stringText as Labels and Menu items show it: “@key” is Tr(key), “@@…” is the text from its second
character (a literal “@”), anything else is itself.
Inputs
- text: The text
- game: The game (nil: the one playing)
Returns
- The text to show (a translation, or part of
text: nothing is allocated)
procedure. Source: localization.odin:255
Constants and variables
Section titled “Constants and variables”BMFONT_EXTENSION
Section titled “BMFONT_EXTENSION”BMFONT_EXTENSION :: ".fnt"A bitmap font file’s extension
constant. Source: fonts.odin:34
MSDF_ATLAS_SUFFIX
Section titled “MSDF_ATLAS_SUFFIX”MSDF_ATLAS_SUFFIX :: ".msdf.json"What an MSDF font’s atlas files end with, after the font’s own name (“Font.ttf.msdf.json”)
constant. Source: fonts.odin:36
MSDF_IMAGE_SUFFIX
Section titled “MSDF_IMAGE_SUFFIX”MSDF_IMAGE_SUFFIX :: ".msdf.bmp"What an MSDF font’s atlas picture ends with, after the font’s own name (“Font.ttf.msdf.bmp”)
constant. Source: fonts.odin:38
MSDF_DEFAULT_CHARSET
Section titled “MSDF_DEFAULT_CHARSET”MSDF_DEFAULT_CHARSET :: "[32, 126], [160, 255]"The characters an MSDF atlas holds by default: printable ASCII and Latin-1
constant, only in builds with #+build !js. Source: font_compile.odin:17
MSDF_DEFAULT_SIZE
Section titled “MSDF_DEFAULT_SIZE”MSDF_DEFAULT_SIZE :: 48Pixels per em the atlas’s glyphs are made at (bigger: finer corners, a bigger picture)
constant, only in builds with #+build !js. Source: font_compile.odin:19
MSDF_DEFAULT_RANGE
Section titled “MSDF_DEFAULT_RANGE”MSDF_DEFAULT_RANGE :: 4The distance range in the atlas’s pixels (the shader’s pxRange)
constant, only in builds with #+build !js. Source: font_compile.odin:21
TRANSLATION_ASSET_KIND
Section titled “TRANSLATION_ASSET_KIND”TRANSLATION_ASSET_KIND :: "Translation"The asset kind of translation tables in project.xml
constant. Source: localization.odin:39
