Magnum::Text::Renderer class new in Git master

Text renderer.

Implements logic for rendering text formed from multiple runs, lines and fonts, resulting in a textured quad mesh, optionally with glyph cluster information that can be used to perform cursor and selection placement. You'll likely use the renderer through the RendererGL subclass, which directly populates a GL::Mesh instance with the rendered data.

The RendererCore base implements just glyph positioning and layout, to be used in scenarios where forming textured quads is deferred to later or not needed at all. For lowest-level functionality see the AbstractShaper class, which exposes text shaping capabilities implemented directly by particular font plugins.

Usage

Assuming you'll want to subsequently render the text with OpenGL, construct the renderer using the RendererGL subclass and an OpenGL glyph cache implementation, such as a GlyphCacheGL.

Text::GlyphCacheGL cache{PixelFormat::R8Unorm, {256, 256}};


Text::RendererGL renderer{cache};

Before rendering with a particular AbstractFont, the glyph cache has to be filled with desired glyphs from it, if not done already, as shown below. The AbstractFont class documentation shows additional ways how to fill the glyph cache.

Containers::Pointer<Text::AbstractFont> font = ;

if(!font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz"
                                "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                                "0123456789?!:;,. "))
    Fatal{} << "Glyph cache too small to fit all characters";

To render a text, pass an AbstractShaper instance created from the font together with desired font size and actual text to render():

renderer.render(*font->createShaper(), font->size(), "Hello, world!");

Once rendered, the RendererGL::mesh() contains the rendered glyphs as textured quads. The mesh can be drawn using Shaders::VectorGL together with binding GlyphCacheGL::texture() for drawing. Usually you'll also want to set up alpha blending so overlapping glyph quads don't cut into each other. Assuming the drawing is performed in a Platform::*Application subclass, the code below sets up the drawing that the font pixel size matches the window pixels by specifying Platform::*Application::windowSize() as the projection size, and it appears in the center of the window. With GlyphCacheArrayGL or DistanceFieldGlyphCacheGL the drawing setup is slightly different, see their documentation for examples.

GL::Renderer::enable(GL::Renderer::Feature::Blending);
GL::Renderer::setBlendFunction(
    GL::Renderer::BlendFunction::One,
    GL::Renderer::BlendFunction::OneMinusSourceAlpha);

Shaders::VectorGL2D shader;
shader
    .setTransformationProjectionMatrix(Matrix3::projection(Vector2{windowSize()}))
    .bindVectorTexture(cache.texture())
    .draw(renderer.mesh());

If you use just the base Renderer, the rendered index and vertex data are exposed through indices(), vertexPositions() and vertexTextureCoordinates() / vertexTextureArrayCoordinates(), which you can pass to a custom renderer, for example. Likewise, the glyph cache can be a custom AbstractGlyphCache subclass that uploads the rasterized glyph data to a texture in a custom renderer. For more control over data layout in custom use cases see the Providing custom index and vertex data allocators section down below.

Cursor, alignment and other layouting options

Internally, the renderer splits the passed text to individual lines and shapes each separately, placing them together at a concrete cursor position with specific alignment and line advance. The initial cursor position is at {0.0f, 0.0f} with a Text::Alignment::MiddleCenter and line advance matching AbstractFont::lineHeight(). This can be overriden using setCursor(), setAlignment() and setLineAdvance() before rendering a particular piece of text. The following snippet renders the same text as above but wrapped to two lines, aligned to bottom right and positioned ten pixels from the bottom right corner of the window:

renderer
    .setCursor({+windowSize().x()*0.5f - 10.0f,
                -windowSize().y()*0.5f + 10.0f})
    .setAlignment(Text::Alignment::BottomRight)
    .render(*font->createShaper(), font->size(), "Hello,\nworld!");

The renderer supports only horizontal text layout right now. The setLayoutDirection() API is currently just a placeholder for when vertical layout is supported in the future as well.

Font script, language and shaping direction

The AbstractShaper instance contains internal font-specific state used for actual text shaping. If you create an instance and reuse it multiple times instead of creating it on the fly, it allows the implementation to reuse allocated resources. It's also possible to call AbstractShaper::setScript(), setLanguage() and setDirection() on the instance if at least some properties of the input text are known, which can make the shaping process faster, or help in cases the properties can't be unambiguously detected from the input:

Containers::Pointer<Text::AbstractShaper> shaper = font->createShaper();
shaper->setScript(Text::Script::Latin);
shaper->setLanguage("en");
shaper->setDirection(Text::ShapeDirection::LeftToRight);

renderer.render(*shaper, shaper->font().size(), "Hello, world!");

The AbstractShaper documentation has additional info about how the shaping properties are handled and how to check what's supported by a particular font plugin.

Font features

The last argument to render() allows enabling and disabling typographic features. For example, assuming the font would support small capitals (and the particular AbstractFont plugin would recognize and use the feature), we could render the "world" part with small caps, resulting in "Hello, ᴡᴏʀʟᴅ!":

renderer.render(*shaper, shaper->font().size(), "Hello, world!", {
    {Text::Feature::SmallCapitals, 7, 12}
});

The behavior is equivalent to font features passed to AbstractShaper::shape(), see the AbstractShaper documentation for further details.

Rendering multiple text blocks

Each call to render() doesn't replace the previously rendered but appends to it, starting again from the position specified with setCursor(). Ultimately that means you can render multiple blocks of text into the same mesh. The following snippet places the two parts of the text to bottom left and bottom right window corners:

renderer
    .setCursor({-windowSize().x()*0.5f + 10.0f,
                -windowSize().y()*0.5f + 10.0f})
    .setAlignment(Text::Alignment::BottomLeft)
    .render(*shaper, shaper->font().size(), "Hello,");

renderer
    .setCursor({+windowSize().x()*0.5f - 10.0f,
                -windowSize().y()*0.5f + 10.0f})
    .setAlignment(Text::Alignment::BottomRight)
    .render(*shaper, shaper->font().size(), "world!");

Then you can either draw everything at once, with the same shader setup as listed above, or draw particular parts with different settings. For that, the render() function returns two values — a bounding box that can be used for various placement and alignment purposes, and a range of text runs the rendered text spans. A run is a range of glyphs together with an associated scale, and the run offsets can be converted to glyph offsets using glyphsForRuns(). Glyph offsets then directly correspond to vertex and index offsets, as each glyph is a quad consisting of four vertices and six indices. The following code draws each of the two pieces with a different color by making temporary GL::MeshView instances spanning just the corresponding glyph range:

Range1Dui helloRuns = renderer
    
    .render(*shaper, shaper->font().size(), "Hello,").second();
Range1Dui helloGlyphs = renderer.glyphsForRuns(helloRuns);

Range1Dui worldRuns = renderer
    
    .render(*shaper, shaper->font().size(), "world!").second();
Range1Dui worldGlyphs = renderer.glyphsForRuns(worldRuns);

shader
    .setTransformationProjectionMatrix(Matrix3::projection(Vector2{windowSize()}))
    .bindVectorTexture(cache.texture())
    .setColor(0x3bd267_rgbf)
    .draw(GL::MeshView{renderer.mesh()}
        .setIndexOffset(helloGlyphs.min()*6)
        .setCount(helloGlyphs.size()*6))
    .setColor(0x2f83cc_rgbf)
    .draw(GL::MeshView{renderer.mesh()}
        .setIndexOffset(worldGlyphs.min()*6)
        .setCount(worldGlyphs.size()*6));

Finally, clear() discards all text rendered so far, meaning that the next render() will start from an empty state. The reset() function additionally resets also all other state like cursor position and alignment to default values.

Rendering multiple text runs together

Besides rendering the whole text at once with a single font, it's possible to render different parts of the text with different fonts, sizes or even just differently configured shaper instances. This is done by using add(), which, compared to render(), continues shaping where the previous add() ended instead of going back to the cursor specified with setCursor(). When done, a call to the parameter-less render() performs final alignment and wraps up the rendering. In the following snippet, a bold font is used to render the "world" part:

Containers::Pointer<Text::AbstractShaper> shaper = font->createShaper();
Containers::Pointer<Text::AbstractShaper> boldShaper = boldFont->createShaper();


renderer
    .add(*shaper, shaper->font().size(), "Hello, ")
    .add(*boldShaper, boldShaper->font().size(), "world")
    .add(*shaper, shaper->font().size(), "!")
    .render();

Fonts can be switched anywhere, not just at word boundaries. However, to make use of the full shaping capabilities of a certain font implementation, it's recommended to supply the text with additional context so the shaper can perform kerning, glyph substitution and other operations even across the individual pieces. This is done by passing the whole text every time and specifying the range to shape by a begin and end byte offset into it, allowing the shaper to peek outside of the actually shaped piece of text:

Containers::StringView text = "Hello, world!";

renderer
    .add(*shaper, shaper->font().size(), text, 0, 7)
    .add(*boldShaper, boldShaper->font().size(), text, 7, 12)
    .add(*shaper, shaper->font().size(), text, 12, 13)
    .render();

Font size

So far, for simplicity, the snippets above passed AbstractFont::size() to render() or add(), making the text rendered at exactly the size the font glyphs were rasterized into the cache. The size at which the glyphs are rasterized into the cache and the size at which a text is drawn on the screen don't have to match, however.

When rendering the text, there are two common approaches — either setting up the size to match a global user interface scale, or having the text size proportional to the window size. The first approach is what's shown in the above snippets, with a projection that matches Platform::*Application::windowSize(), and results in e.g. a 12 pt font matching a 12 pt font in other applications. With the regular GlyphCacheGL / GlyphCacheArrayGL, an even crisper result may be achieved by doubly supersampling the rasterized font compared to the size it's drawn at, which is shown in the following snippet. Additional considerations for proper DPI awareness are described further below.

Containers::Pointer<Text::AbstractFont> font = ;
if(!font->openFile("font.ttf", size*2.0f)) /* Supersample 2x */
    


renderer.render(*font->createShaper(), size, );

The second approach, with text size being relative to the window size, is for cases where the text is meant to match surrounding art, such as in a game menu. In this case the projection size is usually something arbitrary that doesn't match window pixels, and the text point size then has to be relative to that. For this use case a DistanceFieldGlyphCacheGL is the better match, as it can provide text at different sizes without the scaling causing blurriness or aliased edges. See its documentation for details about picking the right font size and other parameters for best results.

DPI awareness

To achieve crisp rendering and/or text size matching other applications on HiDPI displays, additional steps need to be taken. There are two separate concepts for DPI-aware rendering:

  • Interface size — size at which the interface elements are positioned on the screen. Often, for simplicity, the interface is using some "virtual units", so a 12 pt font is still a 12 pt font independently of how the interface is scaled compared to actual display properties (for example by setting a global 150% scale in the desktop environment, or by zooming a browser window). The size passed to render() or add() should match these virtual units.
  • Framebuffer size — how many pixels is actually there. If a 192 DPI display has a 200% interface scale, a 12 pt font would be 32 pixels. But if it only has a 150% scale, all interface elements will be smaller, and a 12 pt font would be only 24 pixels. The size used by the AbstractFont and GlyphCacheGL should be chosen with respect to the actual physical pixels.

When using for example Platform::Sdl2Application or other *Application implementations, you usually have three values at your disposal — windowSize(), framebufferSize() and dpiScaling(). Their relation is documented thoroughly in DPI awareness, for this particular case a scaled interface size, used instead of window size for the projection, would be calculated like this:

Vector2 interfaceSize = Vector2{windowSize()}/dpiScaling();

And a multiplier for the AbstractFont and GlyphCacheGL font size like this. The render() or add() keeps using the size without this multiplier.

Float sizeMultiplier =
    (Vector2{framebufferSize()}*dpiScaling()/Vector2{windowSize()}).max();

Rendering and mesh drawing performance

To avoid repeated reallocations when rendering a larger chunk of text, when the text is combined of many small runs or when rendering many separate text blocks together, call reserve() with expected glyph and run count.

For the mesh the Renderer by default uses MeshIndexType::UnsignedByte, and changes it to a larger type each time the glyphCapacity() exceeds the range representable in given type. Call setIndexType() if you want to use a larger type right from the start even if the glyph capacity doesn't need it. In case of RendererGL, MeshIndexType::UnsignedShort is used by default instead, as 8-bit indices are discouraged on contemporary GPUs.

Mapping between input text and shaped glyphs

For implementing text selection or editing, if RendererFlag::GlyphPositionsClusters is enabled on Renderer construction, the renderer exposes also glyph position and cluster information for each run via glyphPositions() and glyphClusters(). The clusters are used the same way as described in the AbstractShaper documentation, but additionally with mapping a particular text run to a concrete range of glyphs for which to query the runs:

Text::Renderer renderer{cache, Text::RendererFlag::GlyphPositionsClusters};

Range1Dui runs = renderer.render(, text).second();
Range1Dui glyphs = renderer.glyphsForRuns(runs);
Containers::StridedArrayView1D<const UnsignedInt> clusters =
    renderer.glyphClusters().slice(glyphs.min(), glyphs.max());

/* Input text corresponding to glyphs 2 to 5 */
Containers::StringView selection = text.slice(clusters[2], clusters[5]);

/* Or glyphs corresponding to a concrete text selection */
Containers::Pair<UnsignedInt, UnsignedInt> selectionGlyphs =
    Text::glyphRangeForBytes(clusters, selection.begin() - text.begin(),
                                       selection.end() - text.begin());

When using RendererGL, the flag is RendererGLFlag::GlyphPositionsClusters instead, for RendererCore it's just RendererCoreFlag::GlyphClusters as glyph positions are available always.

Providing custom index and vertex data allocators

If you're using Renderer directly and not the RendererGL subclass, it's possible to hook up custom allocators for index and vertex data. Let's say that you want to have per-vertex colors in addition to positions and texture coordinates. The text renderer produces position and texture coordinate data inside render() and colors get filled in after, by querying the vertex range corresponding to the produced glyph run produced using glyphsForRuns():

struct Vertex {
    Vector2 position;
    Vector2 textureCoordinates;
    Color4 color;
};
Containers::Array<Vertex> vertices;

Text::Renderer renderer{cache,
    /* Glyphs, runs and indices use renderer's default allocators */
    nullptr, nullptr,
    nullptr, nullptr,
    nullptr, nullptr,
    [](void* state, UnsignedInt vertexCount,
       Containers::StridedArrayView1D<Vector2>& vertexPositions,
       Containers::StridedArrayView1D<Vector2>& vertexTextureCoordinates
    ) {
        auto& vertices = *static_cast<Containers::Array<Vertex>*>(state);
        if(vertexCount > vertices.size())
            arrayResize(vertices, vertexCount);

        vertexPositions = stridedArrayView(vertices).slice(&Vertex::position);
        vertexTextureCoordinates =
            stridedArrayView(vertices).slice(&Vertex::textureCoordinates);
    }, &vertices
};

/* Render a text and fill vertex colors. Each glyph quad is four vertices. */
Range1Dui runs = renderer.render().second();
Range1Dui glyphs = renderer.glyphsForRuns(runs);
for(Vertex& vertex: vertices.slice(glyphs.min()*4, glyphs.max()*4))
    vertex.color = 0x3bd267_rgbf;

In case of indices you can for example use a single statically-allocated memory across all renderers, to avoid each allocating its own copy:

/* A 2-byte index type can index at most 65k vertices, which is enough for 16k
   glyph quads, and each glyph quad needs six indices */
char indices[2*16384*6];

Text::Renderer renderer{cache,
    nullptr, nullptr,
    nullptr, nullptr,
    [](void* state, UnsignedInt size, Containers::ArrayView<char>& indices) {
        indices = *static_cast<char(*)[2*16384*6]>(state);
        CORRADE_INTERNAL_ASSERT(size <= indices.size());;
    }, indices,
    nullptr, nullptr
};

Expected allocator behavior is fully documented in the Renderer() constructor, note especially the differences when array glyph caches are used. The Providing custom glyph and run data allocators section in the RendererCore documentation shows more use cases with examples for the remaining two allocators.

Base classes

class RendererCore new in Git master
Text renderer core.

Derived classes

class RendererGL new in Git master
OpenGL text renderer.

Constructors, destructors, conversion operators

Renderer(const AbstractGlyphCache& glyphCache, RendererFlags flags = {}) explicit
Construct.
Renderer(const AbstractGlyphCache& glyphCache, void(*)(void*state, UnsignedInt glyphCount, Containers::StridedArrayView1D<Vector2>&glyphPositions, Containers::StridedArrayView1D<UnsignedInt>&glyphIds, Containers::StridedArrayView1D<UnsignedInt>*glyphClusters, Containers::StridedArrayView1D<Vector2>&glyphAdvances) glyphAllocator, void* glyphAllocatorState, void(*)(void*state, UnsignedInt runCount, Containers::StridedArrayView1D<Float>&runScales, Containers::StridedArrayView1D<UnsignedInt>&runEnds) runAllocator, void* runAllocatorState, void(*)(void*state, UnsignedInt size, Containers::ArrayView<char>&indices) indexAllocator, void* indexAllocatorState, void(*)(void*state, UnsignedInt vertexCount, Containers::StridedArrayView1D<Vector2>&vertexPositions, Containers::StridedArrayView1D<Vector2>&vertexTextureCoordinates) vertexAllocator, void* vertexAllocatorState, RendererFlags flags = {}) explicit
Construct with external allocators.
Renderer(NoCreateT) explicit noexcept new in Git master
Construct without creating the internal state.
Renderer(Renderer&) deleted
Copying is not allowed.
Renderer(Renderer&&) noexcept
Move constructor.

Public functions

auto operator=(Renderer&) -> Renderer& deleted
Copying is not allowed.
auto operator=(Renderer&&) -> Renderer& noexcept
Move assignment.
auto flags() const -> RendererFlags
Flags.
auto glyphIndexCapacity() const -> UnsignedInt
Glyph index capacity.
auto glyphVertexCapacity() const -> UnsignedInt
Glyph vertex capacity.
auto indexType() const -> MeshIndexType
Index type.
auto setIndexType(MeshIndexType atLeast) -> Renderer&
Set index type.
auto glyphPositions() const -> Containers::StridedArrayView1D<const Vector2>
Glyph positions.
auto glyphIds() const -> Containers::StridedArrayView1D<const UnsignedInt> deleted
Glyph IDs are not accessible.
auto glyphClusters() const -> Containers::StridedArrayView1D<const UnsignedInt>
Glyph cluster IDs.
auto indices() const -> Containers::StridedArrayView2D<const char>
Type-erased glyph quad indices.
template<class T>
auto indices() const -> Containers::ArrayView<const T>
Glyph quad indices.
auto vertexPositions() const -> Containers::StridedArrayView1D<const Vector2>
Vertex positions.
auto vertexTextureCoordinates() const -> Containers::StridedArrayView1D<const Vector2>
Vertex texture coordinates.
auto vertexTextureArrayCoordinates() const -> Containers::StridedArrayView1D<const Vector3>
Vertex texture array coordinates.
auto reserve(UnsignedInt glyphCapacity, UnsignedInt runCapacity) -> Renderer&
Reserve capacity for given glyph count.
auto clear() -> Renderer&
Clear rendered glyphs, runs and vertices.
auto reset() -> Renderer&
Reset internal renderer state.
auto render() -> Containers::Pair<Range2D, Range1Dui>
Wrap up rendering of all text added so far.

Function documentation

Magnum::Text::Renderer::Renderer(const AbstractGlyphCache& glyphCache, RendererFlags flags = {}) explicit

Construct.

Parameters
glyphCache Glyph cache to use
flags Opt-in feature flags

By default, the renderer allocates the memory for glyph, run, index and vertex data internally. Use the overload below to supply external allocators.

Magnum::Text::Renderer::Renderer(const AbstractGlyphCache& glyphCache, void(*)(void*state, UnsignedInt glyphCount, Containers::StridedArrayView1D<Vector2>&glyphPositions, Containers::StridedArrayView1D<UnsignedInt>&glyphIds, Containers::StridedArrayView1D<UnsignedInt>*glyphClusters, Containers::StridedArrayView1D<Vector2>&glyphAdvances) glyphAllocator, void* glyphAllocatorState, void(*)(void*state, UnsignedInt runCount, Containers::StridedArrayView1D<Float>&runScales, Containers::StridedArrayView1D<UnsignedInt>&runEnds) runAllocator, void* runAllocatorState, void(*)(void*state, UnsignedInt size, Containers::ArrayView<char>&indices) indexAllocator, void* indexAllocatorState, void(*)(void*state, UnsignedInt vertexCount, Containers::StridedArrayView1D<Vector2>&vertexPositions, Containers::StridedArrayView1D<Vector2>&vertexTextureCoordinates) vertexAllocator, void* vertexAllocatorState, RendererFlags flags = {}) explicit

Construct with external allocators.

Parameters
glyphCache Glyph cache to use for glyph ID mapping
glyphAllocator Glyph allocator function or nullptr
glyphAllocatorState State pointer to pass to glyphAllocator
runAllocator Run allocator function or nullptr
runAllocatorState State pointer to pass to runAllocator
indexAllocator Index allocator function or nullptr
indexAllocatorState State pointer to pass to indexAllocator
vertexAllocator Vertex allocator function or nullptr
vertexAllocatorState State pointer to pass to vertexAllocator
flags Opt-in feature flags

The glyphAllocator gets called with desired glyphCount every time glyphCount() reaches glyphCapacity(). Size of passed-in glyphPositions, glyphIds and glyphClusters views matches glyphCount(). The glyphAdvances view is a temporary storage with contents that don't need to be preserved on reallocation and is thus passed in empty. If the renderer wasn't constructed with RendererFlag::GlyphPositionsClusters, the glyphClusters is nullptr to indicate it's not meant to be allocated. The allocator is expected to replace all passed views with new views that are larger by at least glyphCount, pointing to a reallocated memory with contents from the original view preserved. Initially glyphCount() is 0 and the views are all passed in empty, every subsequent time the views match a prefix of views previously returned by the allocator. To save memory, the renderer guarantees that glyphIds and glyphClusters are only filled once glyphAdvances were merged into glyphPositions. In other words, the glyphAdvances can alias a suffix of glyphIds and glyphClusters.

The runAllocator gets called with desired runCount every time runCount() reaches runCapacity(). Size of passed-in runScales and runEnds views matches runCount(). The allocator is expected to replace the views with new views that are larger by at least runCount, pointing to a reallocated memory with contents from the original views preserved. Initially runCount() is 0 and the views are passed in empty, every subsequent time the views match a prefix of views previously returned by the allocator.

The indexAllocator gets called with desired size every time glyphCapacity() increases. Size of passed-in indices array either matches glyphCapacity() times 6 times size of indexType() if the index type stays the same, or is empty if the index type changes (and the whole index array is going to get rebuilt with a different type, thus no contents need to be preserved). The allocator is expected to replace the passed view with a new view that's larger by at least size, pointing to a reallocated memory with contents from the original view preserved. Initially glyphCapacity() is 0 and the view is passed in empty, every subsequent time the view matches a prefix of the view previously returned by the allocator.

The vertexAllocator gets called with vertexCount every time glyphCount() reaches glyphCapacity(). Size of passed-in vertexPositions and vertexTextureCoordinates views matches glyphCount() times 4. The allocator is expected to replace the views with new views that are larger by at least vertexCount, pointing to a reallocated memory with contents from the original views preserved. Initially glyphCount() is 0 and the views are passed in empty, every subsequent time the views match a prefix of views previously returned by the allocator. If the glyphCache is an array, the allocator is expected to (re)allocate vertexTextureCoordinates for a Vector3 type even though the view points to just the first two components of each texture coordinates.

The renderer always requests only exactly the desired size and the growth strategy is up to the allocators themselves — the returned glyph and run views can be larger than requested and aren't all required to all have the same size. The minimum of size increases across all views is then treated as the new glyphCapacity(), glyphIndexCapacity(), glyphVertexCapacity() and runCapacity().

As a special case, when clear() or reset() is called, the allocators are called with empty views and glyphCount / runCount / size / vertexCount being 0. This is to allow the allocators to perform any needed reset as well.

If glyphAllocator, runAllocator, indexAllocator or vertexAllocator is nullptr, glyphAllocatorState, runAllocatorState, indexAllocatorState or vertexAllocatorState is ignored and default builtin allocator get used for either. Passing nullptr for all is equivalent to calling the Renderer(const AbstractGlyphCache&, RendererFlags) constructor.

Magnum::Text::Renderer::Renderer(NoCreateT) explicit noexcept new in Git master

Construct without creating the internal state.

The constructed instance is equivalent to moved-from state, i.e. no APIs can be safely called on the object. Useful in cases where you will overwrite the instance later anyway. Move another object over it to make it useful.

Note that this is a low-level and a potentially dangerous API, see the documentation of NoCreate for alternatives.

Magnum::Text::Renderer::Renderer(Renderer&&) noexcept

Move constructor.

Performs a destructive move, i.e. the original object isn't usable afterwards anymore.

UnsignedInt Magnum::Text::Renderer::glyphIndexCapacity() const

Glyph index capacity.

Describes how many glyphs can be rendered into the index buffer. The actual index count is six times the capacity.

UnsignedInt Magnum::Text::Renderer::glyphVertexCapacity() const

Glyph vertex capacity.

Describes how many glyphs can be rendered into the vertex buffer. The actual vertex count is four times the capacity.

MeshIndexType Magnum::Text::Renderer::indexType() const

Index type.

The smallest type that can describe vertices for all glyphCapacity() glyphs and isn't smaller than what was set in setIndexType(). Initially set to MeshIndexType::UnsignedByte, a lerger type is automatically switched to once the capacity exceeds 64 and 16384 glyphs.

Renderer& Magnum::Text::Renderer::setIndexType(MeshIndexType atLeast)

Set index type.

Returns Reference to self (for method chaining)

Sets the smallest possible index type to be used. Initially MeshIndexType::UnsignedByte, a larger type is automatically switched to once glyphCapacity() exceeds 64 and 16384 glyphs. Set to a larger type if you want it to be used even if the glyph capacity is smaller. Setting it back to a smaller type afterwards uses the type only if the glyph capacity allows it.

Containers::StridedArrayView1D<const Vector2> Magnum::Text::Renderer::glyphPositions() const

Glyph positions.

Expects that the renderer was constructed with RendererFlag::GlyphPositionsClusters. The returned view has a size of glyphCount(). Note that the contents are not guaranteed to be meaningful if custom glyph allocator is used, as the user code is free to perform subsequent operations on those.

Containers::StridedArrayView1D<const UnsignedInt> Magnum::Text::Renderer::glyphIds() const deleted

Glyph IDs are not accessible.

Unlike with RendererCore, to save memory, glyph IDs are retrieved only to a temporary location to produce glyph quads and are subsequently overwritten by vertex data.

Containers::StridedArrayView1D<const UnsignedInt> Magnum::Text::Renderer::glyphClusters() const

Glyph cluster IDs.

Expects that the renderer was constructed with RendererFlag::GlyphPositionsClusters. The returned view has a size of glyphCount(). Note that the contents are not guaranteed to be meaningful if custom glyph allocator is used, as the user code is free to perform subsequent operations on those.

Containers::StridedArrayView2D<const char> Magnum::Text::Renderer::indices() const

Type-erased glyph quad indices.

The returned view is contiguous with a size of glyphCount() times 6, the second dimension having a size of indexType(). The values index the vertexPositions() and vertexTextureCoordinates() / vertexTextureArrayCoordinates() arrays. Note that the contents are not guaranteed to be meaningful if custom index allocator is used, as the user code is free to perform subsequent operations on those.

Use the templated overload below to get the indices in a concrete type.

template<class T>
Containers::ArrayView<const T> Magnum::Text::Renderer::indices() const

Glyph quad indices.

Expects that T is either UnsignedByte, UnsignedShort or UnsignedInt and matches indexType(). The returned view has a size of glyphCount() times 6. Note that the contents are not guaranteed to be meaningful if custom index allocator is used, as the user code is free to perform subsequent operations on those.

Use the non-templated overload above to get a type-erased view on the indices.

Containers::StridedArrayView1D<const Vector2> Magnum::Text::Renderer::vertexPositions() const

Vertex positions.

The returned view has a size of glyphCount() times 4. Note that the contents are not guaranteed to be meaningful if custom vertex allocator is used, as the user code is free to perform subsequent operations on those.

Containers::StridedArrayView1D<const Vector2> Magnum::Text::Renderer::vertexTextureCoordinates() const

Vertex texture coordinates.

Expects that the renderer was constructed with a non-array AbstractGlyphCache, i.e. with a depth equal to 1. The returned view has a size of glyphCount() times 4. Note that the contents are not guaranteed to be meaningful if custom vertex allocator is used, as the user code is free to perform subsequent operations on those.

Containers::StridedArrayView1D<const Vector3> Magnum::Text::Renderer::vertexTextureArrayCoordinates() const

Vertex texture array coordinates.

Expects that the renderer was constructed with an array AbstractGlyphCache, i.e. with a depth larger than 1. The returned view has a size of glyphCount() times 4. Note that the contents are not guaranteed to be meaningful if custom vertex allocator is used, as the user code is free to perform subsequent operations on those.

Renderer& Magnum::Text::Renderer::reserve(UnsignedInt glyphCapacity, UnsignedInt runCapacity)

Reserve capacity for given glyph count.

Returns Reference to self (for method chaining)

Calls RendererCore::reserve() and additionally reserves capacity also for the corresponding index and vertex memory. Note that while reserved index and vertex capacity is derived from glyphCapacity and indexType(), their actually allocated capacity doesn't need to match glyphCapacity() and is exposed through glyphIndexCapacity() and glyphVertexCapacity().

Renderer& Magnum::Text::Renderer::clear()

Clear rendered glyphs, runs and vertices.

Returns Reference to self (for method chaining)

Calls RendererCore::clear(). The glyphCount() and runCount() becomes 0 after this call and any in-progress rendering is discarded, making isRendering() return false. If custom glyph, run or vertex allocators are used, they get called with empty views and zero sizes. Custom index allocator isn't called however, as the index buffer only needs updating when its capacity isn't large enough.

Depending on allocator used, glyphCapacity(), glyphVertexCapacity() and runCapacity() may stay non-zero. The cursor(), alignment(), lineAdvance() and layoutDirection() are left untouched, use reset() to reset those to their default values as well.

Renderer& Magnum::Text::Renderer::reset()

Reset internal renderer state.

Returns Reference to self (for method chaining)

Calls clear(), and additionally cursor(), alignment(), lineAdvance() and layoutDirection() are reset to their default values. Apart from glyphCapacity(), glyphVertexCapacity() and runCapacity() which may stay non-zero depending on allocator used, and glyphIndexCapacity() plus indexType() which are left untouched, the instance is equivalent to a default-constructed state.

Containers::Pair<Range2D, Range1Dui> Magnum::Text::Renderer::render()

Wrap up rendering of all text added so far.

Calls RendererCore::render() and populates also index and vertex data, subsequently available through indices(), vertexPositions() and vertexTextureCoordinates() / vertexTextureArrayCoordinates().

The function uses renderGlyphQuadsInto() and renderGlyphQuadIndicesInto() internally, see their documentation for more information.