Skip to content

Font

Generated from engine/render/font.h at 862e08b. The text under each declaration is the header's own comment, word for word. About the reference says how these pages are made.

Browse the render module

#include "engine/render/font.h" · namespace labrador

A font, as engine data.

WHY IT IS ENGINE DATA AND NOT A PHANTOM TYPE. Leave a Font to the backend and the pen arithmetic, the glyph table, the line spacing and the measurement of every string this engine draws sit inside whatever library that backend links. A second backend then has to find or write all of it again with no way to check its answers match, because the answers are nowhere written down.

So a Font is what a SpriteSheet already is: engine data over a TextureHandle. The atlas is still the backend's - it is a texture, and only the backend knows what one of those is - but where each glyph sits in it, how far the pen moves and how tall a line is are the engine's, in this file, in arithmetic a backend cannot get wrong because it never performs any of it. Texture remains phantom; Font does not.

THE ARITHMETIC IS DirectXTK'S, DELIBERATELY AND EXACTLY. Every quirk below is SpriteFont::Impl::ForEachGlyph's, unchanged, and RenderPixelTests pins what they produce. They are written down here rather than merely reproduced, so the next person to simplify one is doing it on purpose:

- x_advance is an ADJUSTMENT to the advance, not the advance. The pen moves by the glyph's width plus x_advance, so a negative value - which most glyphs in a Courier atlas have - is normal and not corruption. - x_offset is a left bearing, and it DOES accumulate: for_each_glyph adds it to the running pen and then advances from there, so glyph N's bearing is inside glyph N+1's position. The walk and measure() agree because both do this, so a change to one is a change to both. - a whitespace glyph no larger than one texel in both axes steps the pen and draws nothing. MakeSpriteFont writes exactly such a glyph for U+0020, and it is not required to be transparent. - a line is the taller of the glyph's own extent and the font's line spacing, so a line of full stops measures as tall as a line of capitals.

ONE THING UPSTREAM HAS THAT THIS DOES NOT. SpriteFont's walk takes an ignoreWhitespace flag; both callers in this engine want the same value, so there is no flag (T3). MeasureDrawBounds, the upstream caller that wants the other value, has no counterpart here.

struct Glyph

One character's cell in a font's atlas.

Public fields and no accessors: this is a record read straight out of a file, and the four numbers beside the rectangle are the only reason a glyph is not just a rectangle.

char32_t character = 0;

Where in the atlas, in texels.

float x_offset = 0.0f;

Added to the pen before drawing, and carried forward with it: the walk writes it into the pen variable itself and nothing takes it out again. See the paragraph at the top of this file.

float y_offset = 0.0f;

Added to the line's y before drawing, and not carried forward.

float x_advance = 0.0f;

Added to the glyph's width to make the step to the next pen position. Usually negative - see the file header.

class Font
Font(TextureHandle atlas, std::vector<Glyph> glyphs,
float line_spacing);

glyphs need not be sorted; this sorts them, because the lookup is a binary search and a file is not obliged to be in order.

TextureHandle atlas() const;

The texture every glyph is cut from. A handle, so a device loss that empties the slot and a reload that refills it are invisible here - which is why a Font is not itself a device resource.

float line_spacing() const;
const Glyph* find(char32_t character) const;

The glyph the font has for character, with no substitution, or nullptr. This is the question can_render asks of every character that is looked up at all - which is every character but the two is_layout_control names.

bool contains(char32_t character) const;
const Glyph& drawn(char32_t character) const;

The glyph to draw for character: its own, or the stand-in.

Throws std::runtime_error naming the character when the font has neither - which is a font with no stand-in installed, and means an atlas that is not a text font at all.

void set_stand_in(char32_t character);

What drawn falls back to. Throws std::out_of_range naming the character if the font has no glyph for it, which is the throw the stand-in exists to prevent and so must not itself cause.

mattmath::Vector2F measure(std::wstring_view text) const;

The unscaled extent of text: the width of the longest line, and the bottom of the lowest one.

size_t first_unrenderable(std::wstring_view text) const;

Where the first character with no glyph of its own is, or std::wstring_view::npos. Per UTF-16 unit, so a character outside the basic plane is two and reports at the first of them.

LAYOUT CONTROLS ARE NOT LOOKED UP, HERE OR ANYWHERE. No atlas holds a glyph for U+000A, because the walk below answers a line feed itself before it ever looks a character up. So asking the atlas about one would condemn every string with a newline in it - and a caller asking "will this string draw" about text it has laid out in two lines wants "yes", which is also the truth.

template <typename Action>
void for_each_glyph(std::wstring_view text, Action action) const;

The pen walk measure() above and every draw_text share. first_ unrenderable does not: it walks the string against the glyph table and never moves a pen.

action is called as action(glyph, pen) for each glyph that is to be drawn, where pen.x is the pen position with the glyph's left bearing already added and pen.y is the TOP OF THE LINE - the glyph's own y_offset is not in it, because measurement and drawing want it at different moments. Whitespace that draws nothing is not reported.

A template, and therefore in the header, because it is the one piece of this file the frame path walks: a HUD redraws every string it shows every frame, and an out-of-line call plus an indirection per glyph is a cost T8 does not ask anyone to pay for a callback that is always one of two lambdas.

Named in the public declarations above: RectangleI, Vector2F.

The files that include this header directly. A file can also reach it through another header.

Development documentation (unreleased). Built from Labrador 862e08b of 2026-10-08.