Text rendering

Met.3D’s text rendering is implemented in MTextManager (gxfw/textmanager.h). It manages text layout, resources such as text atlas textures, and additional meta-data.

It supports multiple fonts, generally all fonts available through Qt. For each font, a text atlas is generated on demand.

To render text, a MLabel has to be created first, by calling MTextManager::addText(). It contains all information on how to render the text.

To actually render the text, call MTextManager::renderLabelList().

Text atlas

The text manager stores a text atlas for each used font. It is implemented in MTextManager::TextAtlas. All generated text atlases are stored in the map MTextManager::fontAtlases, which maps a font name to the corresponding atlas.

A text atlas contains a texture with format GL_R8, that can contain 1024 individual character glyphs at a given resolution. The text manager, by default, generates the visible ASCII character set (from ASCII character 32 to 127).

When a text contains a missing character, that character is added to the text atlas on demand. The text atlas stores information about that character in a map MTextManager::TextAtlas::characterInfos. For each character in the atlas, it contains dimensions in pixels, texture coordinates, and advance values for layouting.

The glyphs stored in the text atlas texture are rasterized using QRawFont. It handles text rasterization and is platform independent. It also supports all fonts that the Qt system provides.

Note

Currently, a text atlas has a fixed size of 1024 characters. This could be improved in the future by having a 3D text atlas, where a new layer is created when the last one is filled.

Text layout

Currently, the text manager only supports single line text layouts.

The text can be anchored on any of the four corners of its bounding box, the centers of its bounding box edges, or the center of the bounding box.

The text layout uses QTextLayout to generate a glyph run. It contains the character glyphs that are used to render the text. Any missing glyphs are inserted into the text atlas.

Note

We currently do not utilize QTextLayout itself to layout and place the text. This could be changed in the future, as it would allow us to layout multi-line text.

The glyph run is used to actually generate the text’s geometry. For every character in the text, a quad is generated that will render the letter. It is represented by six vertices, where the first three generate a triangle, and the second three generate the other triangle. The coordinates of the quads are local to the text, meaning it is not yet placed on the screen, nor offset by its anchor. Each vertex for the quads also stores the texture coordinates of the glyph being rendered. After generating the quad, we advance a cursor, which determines the positions of the next quad. The amount to advance the cursor by depends on the glyph.

../../_images/text_layout.svg

Fig. 56 An example of the geometry generated for a text. The red lines are edges of the geometry.

Additionally, the layout process tracks the min and max bounds of the text, to generate a bounding box geometry. This is also a quad, its vertices are prepended before the text vertices, though this time only four vertices with additional padding applied.

Next, the anchor is applied to all vertices. This is simply an offset from the already calculated vertex positions. After this operation, the vertex at the anchor is positioned at (0, 0).

Lastly a MLabel is created, which stores the data of the created text. The vertices are uploaded to a vertex buffer object, which is stored as part of the label.

The text manager keeps track of all labels in MTextManager::labelPool.

Note

The vertex buffer of the label is not managed by MGLResourcesManager. This is for legacy reasons and could be modernized.

Cleanup

Generated labels have to be removed manually by calling MTextManager::removeText(). This deletes the vertex buffer of the label and the label object itself.

Scoped label

It is possible to convert a MLabel to a smart-pointer that automatically cleans up, once the containing scope is cleared. Use MTextManager::makeScoped(), which returns a MLabelScoped. It is simply a unique pointer with a custom deleter.