Browse Source

Text: docs for RendererCore, Renderer and RendererGL.

As well as accompanying changes in AbstractFont, AbstractShaper and
showing usage with the new renderer in all glyph cache implementations.
pull/674/head
Vladimír Vondruš 1 year ago
parent
commit
102a75872b
  1. 3
      doc/changelog.dox
  2. 111
      doc/snippets/Text-gl.cpp
  3. 389
      doc/snippets/Text.cpp
  4. 22
      src/Magnum/Text/AbstractFont.h
  5. 7
      src/Magnum/Text/AbstractGlyphCache.h
  6. 90
      src/Magnum/Text/AbstractShaper.h
  7. 5
      src/Magnum/Text/Alignment.h
  8. 15
      src/Magnum/Text/DistanceFieldGlyphCacheGL.h
  9. 17
      src/Magnum/Text/GlyphCacheGL.h
  10. 409
      src/Magnum/Text/Renderer.h
  11. 5
      src/Magnum/Text/RendererGL.h

3
doc/changelog.dox

@ -459,6 +459,9 @@ See also:
- Reworked @ref Text::AbstractGlyphCache on top of
@ref TextureTools::AtlasLandfill allowing more efficient and incremental
glyph packing together with support for texture arrays
- New @ref Text::Renderer, @ref Text::RendererGL and @ref Text::RendererCore
classes that provide high-level multi-line and multi-font text rendering
functionality
- New @ref Text::renderLineGlyphPositionsInto(),
@ref Text::renderGlyphQuadsInto(), @ref Text::glyphQuadBounds(),
@ref Text::alignRenderedLine(), @ref Text::alignRenderedBlock() and

111
doc/snippets/Text-gl.cpp

@ -32,10 +32,14 @@
#include "Magnum/PixelFormat.h"
#include "Magnum/Math/Color.h"
#include "Magnum/Math/Matrix3.h"
#include "Magnum/GL/MeshView.h"
#include "Magnum/GL/Renderer.h"
#include "Magnum/Shaders/VectorGL.h"
#include "Magnum/Shaders/DistanceFieldVectorGL.h"
#include "Magnum/Text/AbstractFont.h"
#include "Magnum/Text/AbstractShaper.h"
#include "Magnum/Text/DistanceFieldGlyphCacheGL.h"
#include "Magnum/Text/Renderer.h"
#include "Magnum/Text/RendererGL.h"
#define DOXYGEN_ELLIPSIS(...) __VA_ARGS__
@ -44,8 +48,6 @@ using namespace Magnum::Math::Literals;
namespace {
Vector2i windowSize() { return {}; }
Vector2i framebufferSize() { return {}; }
Vector2 dpiScaling() { return {}; }
}
/* Make sure the name doesn't conflict with any other snippets to avoid linker
@ -73,6 +75,17 @@ if(!font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz"
/* [AbstractGlyphCache-usage-construct] */
Text::GlyphCacheGL cache{PixelFormat::R8Unorm, {256, 256}};
/* [AbstractGlyphCache-usage-construct] */
/* [AbstractGlyphCache-usage-draw] */
Text::RendererGL renderer{cache};
DOXYGEN_ELLIPSIS()
Shaders::VectorGL2D shader;
shader
DOXYGEN_ELLIPSIS()
.bindVectorTexture(cache.texture())
.draw(renderer.mesh());
/* [AbstractGlyphCache-usage-draw] */
}
#ifndef MAGNUM_TARGET_GLES2
@ -87,6 +100,19 @@ if(!font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz"
"0123456789?!:;,. "))
Fatal{} << "Glyph cache too small to fit all characters";
/* [GlyphCacheArrayGL-usage] */
/* [GlyphCacheArrayGL-usage-draw] */
Text::RendererGL renderer{cache};
DOXYGEN_ELLIPSIS()
Shaders::VectorGL2D shader{Shaders::VectorGL2D::Configuration{}
.setFlags(Shaders::VectorGL2D::Flag::TextureArrays)
};
shader
DOXYGEN_ELLIPSIS()
.bindVectorTexture(cache.texture())
.draw(renderer.mesh());
/* [GlyphCacheArrayGL-usage-draw] */
}
#endif
@ -105,6 +131,72 @@ if(!font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz"
"0123456789?!:;,. "))
Fatal{} << "Glyph cache too small to fit all characters";
/* [DistanceFieldGlyphCacheGL-usage] */
/* [DistanceFieldGlyphCacheGL-usage-draw] */
Text::RendererGL renderer{cache};
DOXYGEN_ELLIPSIS()
Shaders::DistanceFieldVectorGL2D shader;
shader
DOXYGEN_ELLIPSIS()
.bindVectorTexture(cache.texture())
.draw(renderer.mesh());
/* [DistanceFieldGlyphCacheGL-usage-draw] */
}
{
/* [Renderer-usage-construct] */
Text::GlyphCacheGL cache{PixelFormat::R8Unorm, {256, 256}};
DOXYGEN_ELLIPSIS()
Text::RendererGL renderer{cache};
/* [Renderer-usage-construct] */
/* [Renderer-usage-draw] */
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());
/* [Renderer-usage-draw] */
}
{
PluginManager::Manager<Text::AbstractFont> manager;
Containers::Pointer<Text::AbstractFont> font = manager.loadAndInstantiate("");
Text::GlyphCacheGL cache{PixelFormat::R8Unorm, {256, 256}};
Text::RendererGL renderer{cache};
Containers::Pointer<Text::AbstractShaper> shaper = font->createShaper();
Shaders::VectorGL2D shader;
/* [Renderer-usage-blocks-draw] */
Range1Dui helloRuns = renderer
DOXYGEN_ELLIPSIS()
.render(*shaper, shaper->font().size(), "Hello,").second();
Range1Dui helloGlyphs = renderer.glyphsForRuns(helloRuns);
Range1Dui worldRuns = renderer
DOXYGEN_ELLIPSIS()
.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));
/* [Renderer-usage-blocks-draw] */
}
{
@ -161,17 +253,4 @@ shader.setTransformationProjectionMatrix(projectionMatrix)
.draw(renderer.mesh());
/* [BasicRenderer-usage2] */
}
{
/* [BasicRenderer-dpi-interface-size] */
Vector2 interfaceSize = Vector2{windowSize()}/dpiScaling();
/* [BasicRenderer-dpi-interface-size] */
/* [BasicRenderer-dpi-size-multiplier] */
Float sizeMultiplier =
(Vector2{framebufferSize()}*dpiScaling()/Vector2{windowSize()}).max();
/* [BasicRenderer-dpi-size-multiplier] */
static_cast<void>(interfaceSize);
static_cast<void>(sizeMultiplier);
}
}

389
doc/snippets/Text.cpp

@ -55,14 +55,22 @@
#include "Magnum/Text/AbstractShaper.h"
#include "Magnum/Text/Direction.h"
#include "Magnum/Text/Feature.h"
#include "Magnum/Text/Renderer.h"
#include "Magnum/Text/Script.h"
#include "Magnum/TextureTools/Atlas.h"
#define DOXYGEN_ELLIPSIS(...) __VA_ARGS__
#define DOXYGEN_IGNORE(...) __VA_ARGS__
using namespace Magnum;
using namespace Magnum::Math::Literals;
namespace {
Vector2i windowSize() { return {}; }
Vector2i framebufferSize() { return {}; }
Vector2 dpiScaling() { return {}; }
}
namespace MyNamespace {
struct MyFont: Text::AbstractFont {
@ -317,10 +325,7 @@ PluginManager::Manager<Text::AbstractFont> manager;
Containers::Pointer<Text::AbstractFont> font = DOXYGEN_ELLIPSIS(manager.loadAndInstantiate("SomethingWhatever"));
Containers::Pointer<Text::AbstractShaper> shaper = font->createShaper();
/* Set text properties and shape it */
shaper->setScript(Text::Script::Latin);
shaper->setDirection(Text::ShapeDirection::LeftToRight);
shaper->setLanguage("en");
/* Shape a piece of text */
shaper->shape("Hello, world!");
/* Get the glyph info back */
@ -336,6 +341,13 @@ shaper->glyphOffsetsAdvancesInto(
stridedArrayView(glyphs).slice(&GlyphInfo::offset),
stridedArrayView(glyphs).slice(&GlyphInfo::advance));
/* [AbstractShaper-shape] */
/* [AbstractShaper-shape-properties] */
shaper->setScript(Text::Script::Latin);
shaper->setDirection(Text::ShapeDirection::LeftToRight);
shaper->setLanguage("en");
shaper->shape("Hello, world!");
/* [AbstractShaper-shape-properties] */
}
{
@ -369,10 +381,11 @@ Containers::Pointer<Text::AbstractShaper> shaper = font->createShaper();
Containers::Pointer<Text::AbstractShaper> boldShaper = boldFont->createShaper();
DOXYGEN_ELLIPSIS()
Containers::StringView text = "Hello, world!";
Containers::Array<GlyphInfo> glyphs;
/* Shape "Hello, " with a regular font */
shaper->shape("Hello, world!", 0, 7);
shaper->shape(text, 0, 7);
Containers::StridedArrayView1D<GlyphInfo> glyphs1 =
arrayAppend(glyphs, NoInit, shaper->glyphCount());
shaper->glyphIdsInto(
@ -382,7 +395,7 @@ shaper->glyphOffsetsAdvancesInto(
glyphs1.slice(&GlyphInfo::advance));
/* Append "world" shaped with a bold font */
boldShaper->shape("Hello, world!", 7, 12);
boldShaper->shape(text, 7, 12);
Containers::StridedArrayView1D<GlyphInfo> glyphs2 =
arrayAppend(glyphs, NoInit, boldShaper->glyphCount());
shaper->glyphIdsInto(
@ -392,7 +405,7 @@ shaper->glyphOffsetsAdvancesInto(
glyphs2.slice(&GlyphInfo::advance));
/* Finally shape "!" with a regular font again */
shaper->shape("Hello, world!", 12, 13);
shaper->shape(text, 12, 13);
Containers::StridedArrayView1D<GlyphInfo> glyphs3 =
arrayAppend(glyphs, NoInit, shaper->glyphCount());
shaper->glyphIdsInto(
@ -410,7 +423,7 @@ shaper->glyphOffsetsAdvancesInto(
PluginManager::Manager<Text::AbstractFont> manager;
Containers::Pointer<Text::AbstractFont> font = manager.loadAndInstantiate("SomethingWhatever");
Containers::Pointer<Text::AbstractShaper> shaper = font->createShaper();
/* [AbstractShaper-shape-clusters] */
/* [AbstractShaper-shape-clusters-to-bytes] */
Containers::StringView text = DOXYGEN_ELLIPSIS({});
shaper->shape(text);
@ -420,8 +433,364 @@ Containers::Array<UnsignedInt> clusters{NoInit, shaper->glyphCount()};
shaper->glyphClustersInto(clusters);
Containers::StringView selection = text.slice(clusters[2], clusters[5]);
/* [AbstractShaper-shape-clusters] */
static_cast<void>(selection);
/* [AbstractShaper-shape-clusters-to-bytes] */
/* [AbstractShaper-shape-bytes-to-clusters] */
Containers::Pair<UnsignedInt, UnsignedInt> selectionGlyphs =
Text::glyphRangeForBytes(clusters, selection.begin() - text.begin(),
selection.end() - text.begin());
/* [AbstractShaper-shape-bytes-to-clusters] */
static_cast<void>(selectionGlyphs);
}
{
struct: Text::AbstractGlyphCache {
using Text::AbstractGlyphCache::AbstractGlyphCache;
Text::GlyphCacheFeatures doFeatures() const override { return {}; }
} cache{PixelFormat::R8Unorm, Vector2i{256}};
PluginManager::Manager<Text::AbstractFont> manager;
Containers::Pointer<Text::AbstractFont> font = manager.loadAndInstantiate("");
Containers::Pointer<Text::AbstractShaper> shaperPointer = font->createShaper();
Text::AbstractShaper& shaper = *shaperPointer;
Float size{};
/* [RendererCore-usage] */
Text::RendererCore renderer{cache};
renderer.render(shaper, size, "Hello, world!");
/* [RendererCore-usage] */
/* [RendererCore-usage-quads] */
Range1Dui runs = renderer.render(DOXYGEN_ELLIPSIS(shaper, size, "Hello, world!")).second();
struct Vertex {
Vector2 position;
Vector2 textureCoordinates; /* or Vector3 for an array glyph cache */
};
Containers::Array<Vertex> vertices;
for(UnsignedInt run = runs.min(); run != runs.max(); ++run) {
Range1Dui glyphs = renderer.glyphsForRuns({run, run + 1});
Containers::StridedArrayView1D<Vertex> runVertices =
arrayAppend(vertices, NoInit, glyphs.size());
Text::renderGlyphQuadsInto(renderer.glyphCache(),
renderer.runScales()[run],
renderer.glyphPositions().slice(glyphs.min(), glyphs.max()),
renderer.glyphIds().slice(glyphs.min(), glyphs.max()),
runVertices.slice(&Vertex::position),
runVertices.slice(&Vertex::textureCoordinates));
}
/* [RendererCore-usage-quads] */
}
{
struct: Text::AbstractGlyphCache {
using Text::AbstractGlyphCache::AbstractGlyphCache;
Text::GlyphCacheFeatures doFeatures() const override { return {}; }
} cache{PixelFormat::R8Unorm, Vector2i{256}};
/* [RendererCore-allocators-static] */
struct Glyph {
Vector2 position;
UnsignedInt id;
Vector2 advance;
} glyphs[256];
struct Run {
Float scale;
UnsignedInt end;
} runs[16];
Text::RendererCore renderer{cache,
[](void* state, UnsignedInt glyphCount,
Containers::StridedArrayView1D<Vector2>& glyphPositions,
Containers::StridedArrayView1D<UnsignedInt>& glyphIds,
Containers::StridedArrayView1D<UnsignedInt>*,
Containers::StridedArrayView1D<Vector2>& glyphAdvances
) {
Containers::ArrayView<Glyph> glyphs = *static_cast<Glyph(*)[256]>(state);
CORRADE_INTERNAL_ASSERT(glyphCount <= glyphs.size());DOXYGEN_IGNORE(static_cast<void>(glyphCount));
glyphPositions = stridedArrayView(glyphs).slice(&Glyph::position);
glyphIds = stridedArrayView(glyphs).slice(&Glyph::id);
glyphAdvances = stridedArrayView(glyphs).slice(&Glyph::advance);
}, glyphs,
[](void* state, UnsignedInt runCount,
Containers::StridedArrayView1D<Float>& runScales,
Containers::StridedArrayView1D<UnsignedInt>& runEnds
) {
Containers::ArrayView<Run> runs = *static_cast<Run(*)[16]>(state);
CORRADE_INTERNAL_ASSERT(runCount <= runs.size());DOXYGEN_IGNORE(static_cast<void>(runCount));
runScales = Containers::stridedArrayView(runs).slice(&Run::scale);
runEnds = Containers::stridedArrayView(runs).slice(&Run::end);
}, runs
};
/* [RendererCore-allocators-static] */
}
{
struct: Text::AbstractGlyphCache {
using Text::AbstractGlyphCache::AbstractGlyphCache;
Text::GlyphCacheFeatures doFeatures() const override { return {}; }
} cache{PixelFormat::R8Unorm, Vector2i{256}};
struct Glyph {
Vector2 position;
UnsignedInt id;
Vector2 advance;
};
PluginManager::Manager<Text::AbstractFont> manager;
Containers::Pointer<Text::AbstractFont> font = manager.loadAndInstantiate("");
Containers::Pointer<Text::AbstractShaper> shaperPointer = font->createShaper();
Text::AbstractShaper& shaper = *shaperPointer;
Float size{};
/* [RendererCore-allocators-redirect] */
struct Allocation {
UnsignedInt current = 0;
/* Using just a fixed set of texts for brevity */
Containers::Array<Glyph> texts[5];
} allocation;
Text::RendererCore renderer{cache,
[](void* state, UnsignedInt glyphCount,
Containers::StridedArrayView1D<Vector2>& glyphPositions,
Containers::StridedArrayView1D<UnsignedInt>& glyphIds,
Containers::StridedArrayView1D<UnsignedInt>*,
Containers::StridedArrayView1D<Vector2>& glyphAdvances
) {
auto& allocation = *static_cast<Allocation*>(state);
Containers::Array<Glyph>& glyphs = allocation.texts[allocation.current];
if(glyphCount > glyphs.size())
arrayResize(glyphs, glyphCount);
glyphPositions = stridedArrayView(glyphs).slice(&Glyph::position);
glyphIds = stridedArrayView(glyphs).slice(&Glyph::id);
glyphAdvances = stridedArrayView(glyphs).slice(&Glyph::advance);
}, &allocation,
/* Text runs use the renderer's default allocator */
nullptr, nullptr
};
DOXYGEN_ELLIPSIS()
/* Updating text 3 */
allocation.current = 3;
renderer
.clear()
.render(shaper, size, "Hello, world!");
/* Updating text 1 */
allocation.current = 1;
renderer
.clear()
.render(shaper, size, "This doesn't replace text 3!");
/* [RendererCore-allocators-redirect] */
}
{
struct: Text::AbstractGlyphCache {
using Text::AbstractGlyphCache::AbstractGlyphCache;
Text::GlyphCacheFeatures doFeatures() const override { return {}; }
} cache{PixelFormat::R8Unorm, Vector2i{256}};
PluginManager::Manager<Text::AbstractFont> manager;
/* [Renderer-usage-fill] */
Containers::Pointer<Text::AbstractFont> font = DOXYGEN_ELLIPSIS(manager.loadAndInstantiate(""));
if(!font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789?!:;,. "))
Fatal{} << "Glyph cache too small to fit all characters";
/* [Renderer-usage-fill] */
Text::Renderer renderer{cache};
/* [Renderer-usage-render] */
renderer.render(*font->createShaper(), font->size(), "Hello, world!");
/* [Renderer-usage-render] */
/* [Renderer-usage-layout-options] */
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!");
/* [Renderer-usage-layout-options] */
/* [Renderer-usage-shape-properties] */
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!");
/* [Renderer-usage-shape-properties] */
/* [Renderer-usage-shape-features] */
renderer.render(*shaper, shaper->font().size(), "Hello, world!", {
{Text::Feature::SmallCapitals, 7, 12}
});
/* [Renderer-usage-shape-features] */
{
Containers::Pointer<Text::AbstractShaper> shaper = font->createShaper();
/* [Renderer-usage-blocks] */
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!");
/* [Renderer-usage-blocks] */
}
{
Containers::Pointer<Text::AbstractFont> boldFont = DOXYGEN_ELLIPSIS(manager.loadAndInstantiate(""));
/* [Renderer-usage-runs] */
Containers::Pointer<Text::AbstractShaper> shaper = font->createShaper();
Containers::Pointer<Text::AbstractShaper> boldShaper = boldFont->createShaper();
DOXYGEN_ELLIPSIS()
renderer
.add(*shaper, shaper->font().size(), "Hello, ")
.add(*boldShaper, boldShaper->font().size(), "world")
.add(*shaper, shaper->font().size(), "!")
.render();
/* [Renderer-usage-runs] */
/* [Renderer-usage-runs-begin-end] */
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();
/* [Renderer-usage-runs-begin-end] */
}
}
{
PluginManager::Manager<Text::AbstractFont> manager;
Float size{};
struct: Text::AbstractGlyphCache {
using Text::AbstractGlyphCache::AbstractGlyphCache;
Text::GlyphCacheFeatures doFeatures() const override { return {}; }
} cache{PixelFormat::R8Unorm, Vector2i{256}};
Text::Renderer renderer{cache};
/* [Renderer-dpi-supersampling] */
Containers::Pointer<Text::AbstractFont> font = DOXYGEN_ELLIPSIS(manager.loadAndInstantiate(""));
if(!font->openFile("font.ttf", size*2.0f)) /* Supersample 2x */
DOXYGEN_ELLIPSIS({})
DOXYGEN_ELLIPSIS()
renderer.render(*font->createShaper(), size, DOXYGEN_ELLIPSIS(""));
/* [Renderer-dpi-supersampling] */
}
{
/* [Renderer-dpi-interface-size] */
Vector2 interfaceSize = Vector2{windowSize()}/dpiScaling();
/* [Renderer-dpi-interface-size] */
/* [Renderer-dpi-size-multiplier] */
Float sizeMultiplier =
(Vector2{framebufferSize()}*dpiScaling()/Vector2{windowSize()}).max();
/* [Renderer-dpi-size-multiplier] */
static_cast<void>(interfaceSize);
static_cast<void>(sizeMultiplier);
}
{
struct: Text::AbstractGlyphCache {
using Text::AbstractGlyphCache::AbstractGlyphCache;
Text::GlyphCacheFeatures doFeatures() const override { return {}; }
} cache{PixelFormat::R8Unorm, Vector2i{256}};
PluginManager::Manager<Text::AbstractFont> manager;
Containers::Pointer<Text::AbstractFont> font = manager.loadAndInstantiate("");
Containers::StringView text;
/* [Renderer-clusters] */
Text::Renderer renderer{cache, Text::RendererFlag::GlyphPositionsClusters};
Range1Dui runs = renderer.render(DOXYGEN_ELLIPSIS(*font->createShaper(), 0.0f), 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());
/* [Renderer-clusters] */
static_cast<void>(selectionGlyphs);
}
{
struct: Text::AbstractGlyphCache {
using Text::AbstractGlyphCache::AbstractGlyphCache;
Text::GlyphCacheFeatures doFeatures() const override { return {}; }
} cache{PixelFormat::R8Unorm, Vector2i{256}};
/* [Renderer-allocators-vertex] */
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(DOXYGEN_ELLIPSIS()).second();
Range1Dui glyphs = renderer.glyphsForRuns(runs);
for(Vertex& vertex: vertices.slice(glyphs.min()*4, glyphs.max()*4))
vertex.color = 0x3bd267_rgbf;
/* [Renderer-allocators-vertex] */
}
{
struct: Text::AbstractGlyphCache {
using Text::AbstractGlyphCache::AbstractGlyphCache;
Text::GlyphCacheFeatures doFeatures() const override { return {}; }
} cache{PixelFormat::R8Unorm, Vector2i{256}};
/* [Renderer-allocators-index] */
/* 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());DOXYGEN_IGNORE(static_cast<void>(size));
}, indices,
nullptr, nullptr
};
/* [Renderer-allocators-index] */
}
}

22
src/Magnum/Text/AbstractFont.h

@ -102,8 +102,9 @@ MAGNUM_TEXT_EXPORT Debug& operator<<(Debug& debug, FontFeatures value);
/**
@brief Base for font plugins
Provides interface for opening fonts, filling a glyph cache and layouting the
glyphs.
Provides interface for opening font files, filling a glyph cache with
rasterized glyphs and shaping a Unicode text into a sequence of glyph IDs and
their positions.
@section Text-AbstractFont-usage Usage
@ -124,9 +125,8 @@ that the characters won't all fit, which should be checked by the application:
See @ref plugins for more information about general plugin usage and the list
of @m_class{m-doc} <a href="#derived-classes">derived classes</a> for available
font plugins. See @ref AbstractGlyphCache for more information about glyph
caches, @ref BasicRenderer "Renderer*D" for high-level text rendering, and
@ref AbstractShaper for low-level access to the font text shaping
functionality.
caches, @ref Renderer for high-level text rendering, and @ref AbstractShaper
for low-level access to the font text shaping functionality.
@section Text-AbstractFont-font-size Font size
@ -140,16 +140,18 @@ properties are specified *in pixels* in @ref lineHeight(), @ref ascent() and
@ref descent().
The font size used when opening the font affects how large the glyphs will be
when rendered into the glyph cache. Actual text rendering with
@ref BasicRenderer "Renderer*D" however uses its own font size, and the
rendered size is then additionally depending on the actual projection used.
when rasterized into the glyph cache. Actual text rendering with @ref Renderer
then uses its own font size, and the actual size that's visible on the screen
is then additionally depending on the actual projection used when drawing the
rendered mesh.
This decoupling of font sizes is useful for example in case of
@ref DistanceFieldGlyphCacheGL, where a single prerendered glyph size can be
used to render arbitrarily large font sizes without becoming blurry or jaggy.
When not using a distance field glyph cache, it's usually desirable to have the
font size and the actual rendered size match. See
@ref Text-BasicRenderer-usage-font-size "the Renderer*D documentation" for
further information about picking font sizes.
@ref Text-Renderer-usage-font-size "the Renderer documentation" for further
information about picking font sizes.
@section Text-AbstractFont-glyph-cache Glyph cache filling options

7
src/Magnum/Text/AbstractGlyphCache.h

@ -134,7 +134,12 @@ won't all fit, which should be checked by the application:
As long as the cache size allows, you can call
@ref AbstractFont::fillGlyphCache() multiple times with additional glyphs and
other fonts. See the @ref Text-AbstractFont-glyph-cache "AbstractFont documentation"
for more options for glyph cache filling.
for more options for glyph cache filling. Finally, assuming a @ref RendererGL
is used with this cache for rendering the text, its
@relativeref{RendererGL,mesh()} can be then drawn using @ref Shaders::VectorGL,
together with binding @ref GlyphCacheGL::texture() for drawing:
@snippet Text-gl.cpp AbstractGlyphCache-usage-draw
@section Text-AbstractGlyphCache-filling Filling the glyph cache directly

90
src/Magnum/Text/AbstractShaper.h

@ -44,9 +44,11 @@ namespace Magnum { namespace Text {
@brief Base for text shapers
@m_since_latest
Returned from @ref AbstractFont::createShaper(), provides an interface for
* *shaping* text with the @ref AbstractFont it originated from. Meant to be
(privately) subclassed by @ref AbstractFont plugin implementations.
Returned from @ref AbstractFont::createShaper(), provides a low-level interface
for *shaping* text with the @ref AbstractFont it originated from. Meant to be
(privately) subclassed by @ref AbstractFont plugin implementations. For common
text rendering you'll likely want to use the high-level @ref Renderer, which
then invokes @ref AbstractShaper internally.
* *Shaping* is a process of converting a sequence of Unicode codepoints to a
visual form, i.e. a list of glyphs of a particular font, their offsets and horizontal or vertical advances. Shaping is often not a 1:1 mapping from
@ -69,27 +71,38 @@ shaped text.
@snippet Text.cpp AbstractShaper-shape
For best results, it's recommended to call (a subset of) @ref setScript(),
@ref setLanguage() and @ref setDirection() if at least some properties of the
input text are known, as shown above. Without these, the font plugin may
attempt to autodetect the properties, which might not always give a correct
result. If a particular font plugin doesn't implement given script, language or
@subsection Text-AbstractShaper-usage-properties Specifying shaping properties
By default, and depending on the font plugin capabilities, the shaper
autodetects the script, language and direction of the shaped text. It's
possible to call (a subset of) @ref setScript(), @ref setLanguage() and
@ref setDirection() 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:
@snippet Text.cpp AbstractShaper-shape-properties
If a particular font plugin doesn't implement given script, language or
direction or if it doesn't have any special handling for it, given function
will return @cpp false @ce. The @ref script() const, @ref language() const and
@ref direction() const can be used to inspect results of autodetection after
@ref shape() has been called. The set of supported scripts, languages and
directions and exact behavior for unsupported values is plugin-specific --- it
may for example choose a fallback instead, or it may ignore the setting
altogeter. See documentation of particular @ref AbstractFont subclasses for
more information.
@ref shape() has been called. Setting @ref Script::Unspecified, an empty
language string and @ref ShapeDirection::Unspecified makes the implementation
go back to autodetection for the next shaping operation.
The set of supported scripts, languages and directions and exact behavior for
unsupported values is plugin-specific --- it may for example choose a fallback
instead, or it may ignore the setting altogeter. See documentation of
particular @ref AbstractFont subclasses for more information.
@subsection Text-AbstractShaper-usage-features Enabling and disabling typographic features
In the above snippet, the whole text is shaped using typographic features that
are default in the font. For example, assuming the font would support small
capitals (and the particular @ref AbstractFont plugin would recognize and use
the feature), we could render the "world" part with small caps, resulting in
"Hello, ᴡᴏʀʟᴅ!".
are default in the font. The last argument to @ref shape() takes a list of
@ref FeatureRange items to override those. For example, assuming the font would
support small capitals (and the particular @ref AbstractFont plugin would
recognize and use the feature), we could render the "world" part with small
caps, resulting in "Hello, ᴡᴏʀʟᴅ!".
@snippet Text.cpp AbstractShaper-shape-features
@ -100,25 +113,24 @@ argument. The range, if present, is always given in *bytes* of the UTF-8 input.
Capabilities of typographic features are rather broad, see the @ref Feature
enum and documentation linked from it for exhaustive information.
@subsection Text-AbstractShaper-usage-multiple Combining different shapers
@section Text-AbstractShaper-multiple Combining different shapers
Sometimes it's desirable to render different parts of the text with different
fonts, not just different features of the same font. A variation of the above
example could be rendering the "world" part with a bold font:
If it's desirable to render different parts of the text with different fonts,
the output from multiple shapers can be combined togeter. The following code is
a variation of the above example, shaping the "world" part with a bold font,
although in a quite verbose way compared to
@ref Text-Renderer-usage-runs "the same achieved with the high-level Renderer":
@snippet Text.cpp AbstractShaper-shape-multiple
The resulting `glyphs` array is usable the same way as in the above case, with
a difference that the glyph IDs have to be looked up in an
@ref AbstractGlyphCache with a font ID corresponding to the range they're in.
Also note that the whole text is passed every time and a begin & end is
specified for it instead of passing just the slice alone. While possibly not
having any visible effect in this particular case, in general it allows the
shaper to make additional decisions based on surrounding context, for example
picking glyphs that are better connected to their neighbors in handwriting
fonts.
Similarly as with the linked higher-level example, the whole text is passed
every time and a begin & end is specified for it instead of passing just the
slice alone, to allow the shaper to get additional context if needed.
@subsection Text-AbstractShaper-usage-instances Managing multiple instances
@section Text-AbstractShaper-instances Managing multiple instances
As shown above, a particular @ref AbstractShaper instance is reusable, i.e.
it's possible to call @ref shape() (and potentially also @ref setScript(),
@ -134,7 +146,7 @@ every time. Or for example have a few persistent @ref AbstractShaper instances
for dynamic text that changes every frame, or have dedicated preconfigured
per-font, per-script or per-language instances.
@subsection Text-AbstractShaper-usage-clusters Mapping between input text and shaped glyphs
@section Text-AbstractShaper-clusters Mapping between input text and shaped glyphs
For implementing text selection or editing, mapping from screen position to
concrete glyphs can be done using the advances returned from
@ -145,16 +157,20 @@ rarely a 1:1 mapping from the shaped glyphs back to the input text.
The mapping from glyph IDs to bytes of the text passed to @ref shape() can be
retrieved using @ref glyphClustersInto(). In the following example, a range
between glyphs 2 and 5 is mapped to the input text bytes, for example to copy
it as a selection to clipboard:
between glyphs @cpp 2 @ce and @cpp 5 @ce is mapped to the input text bytes, for
example to copy it as a selection to clipboard:
@snippet Text.cpp AbstractShaper-shape-clusters
@snippet Text.cpp AbstractShaper-shape-clusters-to-bytes
In the other direction, picking a range of glyphs corresponding to a range of
input bytes, involves finding cluster IDs with a lower and upper bound for
given byte positions. See the documentation of @ref glyphClustersInto() for
concrete examples of how retrieved cluster IDs may look like depending on what
operations the shaper performs.
input bytes, involves finding cluster IDs matching given byte positions, which
is doable with the @ref glyphRangeForBytes() utility:
@snippet Text.cpp AbstractShaper-shape-bytes-to-clusters
See the documentation of @ref glyphClustersInto() for concrete examples of how
retrieved cluster IDs may look like depending on what operations the shaper
performs.
@section Text-AbstractShaper-subclassing Subclassing
@ -299,7 +315,7 @@ class MAGNUM_TEXT_EXPORT AbstractShaper {
* @p text this allows the implementation to perform shaping aware of
* surrounding context, such as picking correct glyphs for beginning,
* middle or end of a word or a paragraph.
* @see @ref Text-AbstractShaper-usage-multiple
* @see @ref Text-AbstractShaper-multiple
*/
#ifdef DOXYGEN_GENERATING_OUTPUT
UnsignedInt shape(Containers::StringView text, UnsignedInt begin, UnsignedInt end, Containers::ArrayView<const FeatureRange> features = {});

5
src/Magnum/Text/Alignment.h

@ -86,9 +86,8 @@ respectively, if @ref ShapeDirection::LeftToRight is passed to
@ref ShapeDirection::RightToLeft is set (or detected for
@ref ShapeDirection::Unspecified), they're swapped, i.e. `*Begin` becomes
`*Right` and `*End` becomes `*Left`.
@see @ref BasicRenderer::render() "Renderer*D::render()",
@ref BasicRenderer::BasicRenderer() "Renderer*D::Renderer*D()",
@see @ref alignmentForDirection()
@see @ref Renderer::render(AbstractShaper&, Float, Containers::StringView, Containers::ArrayView<const FeatureRange>) "Renderer::render()",
@ref Renderer::add(), @ref alignmentForDirection()
*/
enum class Alignment: UnsignedByte {
/**

15
src/Magnum/Text/DistanceFieldGlyphCacheGL.h

@ -71,9 +71,18 @@ channels.
@snippet Text-gl.cpp DistanceFieldGlyphCacheGL-usage
See the @ref BasicRenderer "Renderer*D" class for information about text
rendering. The @ref AbstractGlyphCache base class has more information about
general glyph cache usage.
As long as the cache size allows, you can call
@ref AbstractFont::fillGlyphCache() multiple times with additional glyphs and
other fonts, each time the input will be incrementally converted to a distance
field texture. See the @ref Text-AbstractFont-glyph-cache "AbstractFont documentation"
for more options for glyph cache filling. The @ref AbstractGlyphCache base
class has more information about general glyph cache usage. Finally, assuming a
@ref RendererGL is used with this cache for rendering the text, its
@relativeref{RendererGL,mesh()} can be then drawn using
@ref Shaders::DistanceFieldVectorGL, together with binding @ref texture() for
drawing:
@snippet Text-gl.cpp DistanceFieldGlyphCacheGL-usage-draw
@section Text-DistanceFieldGlyphCacheGL-internal-format Internal texture format

17
src/Magnum/Text/GlyphCacheGL.h

@ -48,10 +48,10 @@ namespace Magnum { namespace Text {
Implementation of an @ref AbstractGlyphCache backed by a @ref GL::Texture2D.
See the @ref AbstractGlyphCache class documentation for information about
setting up an instance of this class and filling it with glyphs. See the
@ref DistanceFieldGlyphCacheGL subclass for a variant that adds distance field
processing on top, @ref GlyphCacheArrayGL is then using a @ref GL::Texture2DArray
instead.
setting up an instance of this class, filling it with glyphs and drawing the
text with it. See the @ref DistanceFieldGlyphCacheGL subclass for a variant
that adds distance field processing on top, @ref GlyphCacheArrayGL is then
using a @ref GL::Texture2DArray instead.
@section Text-GlyphCacheGL-internal-format Internal texture format
@ -211,11 +211,18 @@ Implementation of an @ref AbstractGlyphCache backed by a
@ref GL::Texture2DArray, other than that equivalent to @ref GlyphCacheGL. See
the @ref AbstractGlyphCache class documentation for information about setting
up a glyph cache instance and filling it with glyphs, and @ref GlyphCacheGL for
details on how the internal texture format is picked. The usage differs from
details on how the internal texture format is picked. The setup differs from
@ref GlyphCacheGL only in specifying one extra dimension for size:
@snippet Text-gl.cpp GlyphCacheArrayGL-usage
Assuming a @ref RendererGL is used with this cache for rendering the text, its
@relativeref{RendererGL,mesh()} can be then drawn using @ref Shaders::VectorGL
that has @ref Shaders::VectorGL::Flag::TextureArrays enabled, together with
binding @ref texture() for drawing:
@snippet Text-gl.cpp GlyphCacheArrayGL-usage-draw
@requires_gl30 Extension @gl_extension{EXT,texture_array}
@requires_gles30 Texture arrays are not available in OpenGL ES 2.0.
@requires_webgl20 Texture arrays are not available in WebGL 1.0.

409
src/Magnum/Text/Renderer.h

@ -98,6 +98,66 @@ MAGNUM_TEXT_EXPORT Debug& operator<<(Debug& output, RendererCoreFlags value);
/**
@brief Text renderer core
@m_since_latest
Implements essential logic for rendering text formed from multiple runs, lines
and fonts, providing access to glyph positions, glyph IDs and glyph cluster
information that can be subsequently used to show the text on the screen and
perform cursor or selection placement.
See the higher-level @ref Renderer subclass for full usage documentation ---
the input interface is mostly the same between the two, with just the output
being something else. The rest of this documentation thus only highlights the
differences between the two. For lowest-level functionality see the
@ref AbstractShaper class, which exposes text shaping capabilities implemented
directly by particular font plugins.
@section Text-RendererCore-usage Usage
A @ref RendererCore instance is created and populated the same way as a
@ref Renderer or @ref RendererGL, with @ref add() and @ref render() behaving
exactly the same:
@snippet Text.cpp RendererCore-usage
Once rendered, the @ref glyphPositions() and @ref glyphIds() views provide
access to rendered glyph data, and @ref runScales() with @ref runEnds()
describe which scale is applied to particular glyph ranges in order to place
the glyphs at given positions apprpriately scaled. You can then use the
low-level @ref renderGlyphQuadsInto() utility to create textured quads, or feed
the data to some entirely different system that renders the text without
needing textured quads at all.
@snippet Text.cpp RendererCore-usage-quads
@section Text-RendererCore-clusters Mapping between input text and shaped glyphs
For implementing text selection or editing, if
@ref RendererCoreFlag::GlyphClusters is enabled on @ref RendererCore
construction, the renderer exposes also the glyph cluster information for each
run via @ref glyphClusters(). See the @ref Text-Renderer-clusters "relevant Renderer documentation"
for a detailed explanation of how the data get used.
@section Text-RendererCore-allocators Providing custom glyph and run data allocators
For more control over memory allocations or for very customized use, it's
possible to hook up custom allocators for glyph and run data. For example, if
you always use the @ref RendererCore to render only up to a fixed amount of
glyphs, you can direct it to statically sized arrays:
@snippet Text.cpp RendererCore-allocators-static
A behavior worth mentioning is that on @ref clear() or @ref reset() the
allocators get called with the count being @cpp 0 @ce, which can be used to
"redirect" the allocation to some entirely different memory. That allows you to
use a single renderer instance to independently render and replace several
different text blocks, instead of having a dedicated renderer instance for each
or having to copy the rendered data out every time:
@snippet Text.cpp RendererCore-allocators-redirect
Expected allocator behavior is fully documented in the @ref RendererCore()
constructor, note especially the special casing for glyph advances. The
@ref Text-Renderer-allocators "Renderer subclass then provides two additional allocator hooks for index and vertex data."
*/
class MAGNUM_TEXT_EXPORT RendererCore {
public:
@ -683,6 +743,286 @@ MAGNUM_TEXT_EXPORT Debug& operator<<(Debug& output, RendererFlags value);
/**
@brief Text renderer
@m_since_latest
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 @ref RendererGL subclass, which directly populates a
@ref GL::Mesh instance with the rendered data.
The @ref 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 @ref AbstractShaper
class, which exposes text shaping capabilities implemented directly by
particular font plugins.
@section Text-Renderer-usage Usage
Assuming you'll want to subsequently render the text with OpenGL, construct the
renderer using the @ref RendererGL subclass and an OpenGL glyph cache
implementation, such as a @ref GlyphCacheGL.
@snippet Text-gl.cpp Renderer-usage-construct
Before rendering with a particular @ref AbstractFont, the glyph cache has to be
filled with desired glyphs from it, if not done already, as shown below. The
@ref AbstractFont class documentation shows additional ways how to fill the
glyph cache.
@snippet Text.cpp Renderer-usage-fill
To render a text, pass an @ref AbstractShaper instance created from the font
together with desired font size and actual text to
@ref render(AbstractShaper&, Float, Containers::StringView, Containers::ArrayView<const FeatureRange>) "render()":
@snippet Text.cpp Renderer-usage-render
Once rendered, the @ref RendererGL::mesh() contains the rendered glyphs as
textured quads. The mesh can be drawn using @ref Shaders::VectorGL together
with binding @ref GlyphCacheGL::texture() for drawing. Usually you'll also want
to set up @ref GL::Renderer::Feature::Blending "alpha blending" so overlapping
glyph quads don't cut into each other. Assuming the drawing is performed in a
@ref Platform::Sdl2Application "Platform::*Application" subclass, the code
below sets up the drawing that the font pixel size matches the window pixels by
specifying @ref Platform::Sdl2Application::windowSize() "Platform::*Application::windowSize()"
as the projection size, and it appears in the center of the window. With
@ref GlyphCacheArrayGL or @ref DistanceFieldGlyphCacheGL the drawing setup is
slightly different, see their documentation for examples.
@snippet Text-gl.cpp Renderer-usage-draw
If you use just the base @ref Renderer, the rendered index and vertex data are
exposed through @ref indices(), @ref vertexPositions() and
@ref vertexTextureCoordinates() / @ref vertexTextureArrayCoordinates(), which
you can pass to a custom renderer, for example. Likewise, the glyph cache can
be a custom @ref 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 @ref Text-Renderer-allocators section down below.
@subsection Text-Renderer-usage-layout-options 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
@cpp {0.0f, 0.0f} @ce with a @ref Text::Alignment::MiddleCenter and line
advance matching @ref AbstractFont::lineHeight(). This can be overriden using
@ref setCursor(), @ref setAlignment() and @ref 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:
@snippet Text.cpp Renderer-usage-layout-options
The renderer supports only horizontal text layout right now. The
@ref setLayoutDirection() API is currently just a placeholder for when vertical
layout is supported in the future as well.
@subsection Text-Renderer-usage-shape-properties Font script, language and shaping direction
The @ref 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
@ref Text-AbstractShaper-instances "reuse allocated resources". It's also
possible to call @ref AbstractShaper::setScript(),
@relativeref{AbstractShaper,setLanguage()} and
@relativeref{AbstractShaper,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:
@snippet Text.cpp Renderer-usage-shape-properties
The @ref Text-AbstractShaper-usage-properties "AbstractShaper documentation"
has additional info about how the shaping properties are handled and how to
check what's supported by a particular font plugin.
@subsection Text-Renderer-usage-shape-features Font features
The last argument to @ref render(AbstractShaper&, Float, Containers::StringView, Containers::ArrayView<const FeatureRange>) "render()"
allows enabling and disabling typographic features. For example, assuming the
font would support small capitals (and the particular @ref AbstractFont plugin
would recognize and use the feature), we could render the "world" part with
small caps, resulting in "Hello, ᴡᴏʀʟᴅ!":
@snippet Text.cpp Renderer-usage-shape-features
The behavior is equivalent to font features passed to @ref AbstractShaper::shape(),
see the @ref Text-AbstractShaper-usage-features "AbstractShaper documentation"
for further details.
@subsection Text-Renderer-usage-blocks Rendering multiple text blocks
Each call to @ref render(AbstractShaper&, Float, Containers::StringView, Containers::ArrayView<const FeatureRange>) "render()"
doesn't replace the previously rendered but *appends* to it, starting again
from the position specified with @ref 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:
@snippet Text.cpp Renderer-usage-blocks
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
@ref render(AbstractShaper&, Float, Containers::StringView, Containers::ArrayView<const FeatureRange>) "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 @ref 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
@ref GL::MeshView instances spanning just the corresponding glyph range:
@snippet Text-gl.cpp Renderer-usage-blocks-draw
Finally, @ref clear() discards all text rendered so far, meaning that the next
@ref render(AbstractShaper&, Float, Containers::StringView, Containers::ArrayView<const FeatureRange>) "render()"
will start from an empty state. The @ref reset() function additionally resets
also all other state like cursor position and alignment to default values.
@subsection Text-Renderer-usage-runs 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 @ref add(),
which, compared to @ref render(AbstractShaper&, Float, Containers::StringView, Containers::ArrayView<const FeatureRange>) "render()", continues shaping where
the previous @ref add() ended instead of going back to the cursor specified
with @ref setCursor(). When done, a call to the parameter-less @ref render()
performs final alignment and wraps up the rendering. In the following snippet,
a bold font is used to render the "world" part:
@snippet Text.cpp Renderer-usage-runs
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:
@snippet Text.cpp Renderer-usage-runs-begin-end
@section Text-Renderer-usage-font-size Font size
So far, for simplicity, the snippets above passed @ref AbstractFont::size() to
@ref render() or @ref 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 @ref Platform::Sdl2Application::windowSize() "Platform::*Application::windowSize()",
and results in e.g. a 12 pt font matching a 12 pt font in other applications.
With the regular @ref GlyphCacheGL / @ref 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.
@snippet Text.cpp Renderer-dpi-supersampling
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 @ref 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.
@subsection Text-Renderer-usage-font-size-dpi 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 @ref render(AbstractShaper&, Float, Containers::StringView, Containers::ArrayView<const FeatureRange>) "render()"
or @ref 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 @ref AbstractFont and
@ref GlyphCacheGL should be chosen with respect to the actual physical
pixels.
When using for example @ref Platform::Sdl2Application or other `*Application`
implementations, you usually have three values at your disposal ---
@ref Platform::Sdl2Application::windowSize() "windowSize()",
@ref Platform::Sdl2Application::framebufferSize() "framebufferSize()" and
@ref Platform::Sdl2Application::dpiScaling() "dpiScaling()". Their relation is
documented thoroughly in @ref Platform-Sdl2Application-dpi, for this particular
case a scaled interface size, used instead of window size for the projection,
would be calculated like this:
@snippet Text.cpp Renderer-dpi-interface-size
And a multiplier for the @ref AbstractFont and @ref GlyphCacheGL font size like
this. The @ref render(AbstractShaper&, Float, Containers::StringView, Containers::ArrayView<const FeatureRange>) "render()"
or @ref add() keeps using the size without this multiplier.
@snippet Text.cpp Renderer-dpi-size-multiplier
@section Text-Renderer-performance 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 @ref reserve() with expected glyph and run count.
For the mesh the @ref Renderer by default uses @ref MeshIndexType::UnsignedByte,
and changes it to a larger type each time the @ref glyphCapacity() exceeds the
range representable in given type. Call @ref 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 @ref RendererGL, @ref MeshIndexType::UnsignedShort is used by
default instead, as 8-bit indices are discouraged on contemporary GPUs.
@todo update this once @ref AbstractShaper has @ref reserve() as well
@section Text-Renderer-clusters Mapping between input text and shaped glyphs
For implementing text selection or editing, if
@ref RendererFlag::GlyphPositionsClusters is enabled on @ref Renderer
construction, the renderer exposes also glyph position and cluster information
for each run via @ref glyphPositions() and @ref glyphClusters(). The clusters
are used the same way @ref Text-AbstractShaper-clusters "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:
@snippet Text.cpp Renderer-clusters
When using @ref RendererGL, the flag is
@ref RendererGLFlag::GlyphPositionsClusters instead, for @ref RendererCore it's
just @ref RendererCoreFlag::GlyphClusters as glyph positions are available
always.
@section Text-Renderer-allocators Providing custom index and vertex data allocators
If you're using @ref Renderer directly and not the @ref 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 @ref render() and colors get filled in after, by querying the vertex
range corresponding to the produced glyph run produced using
@ref glyphsForRuns():
@snippet Text.cpp Renderer-allocators-vertex
In case of indices you can for example use a single statically-allocated memory
across all renderers, to avoid each allocating its own copy:
@snippet Text.cpp Renderer-allocators-index
Expected allocator behavior is fully documented in the @ref Renderer()
constructor, note especially the differences when array glyph caches are used.
The @ref Text-RendererCore-allocators section in the @ref RendererCore
documentation shows more use cases with examples for the remaining two
allocators.
*/
class MAGNUM_TEXT_EXPORT Renderer: public RendererCore {
public:
@ -1568,75 +1908,6 @@ that doesn't recreate everything on each text change:
@snippet Text-gl.cpp BasicRenderer-usage2
@subsection Text-BasicRenderer-usage-font-size Font size
As mentioned in @ref Text-AbstractFont-font-size "AbstractFont class documentation",
the size at which the font is loaded is decoupled from the size at which a
concrete text is rendered. In particular, with a concrete projection matrix,
the size you pass to either @ref render() or to the @ref BasicRenderer()
constructor will always result in the same size of the rendered text,
independently of the size the font was loaded in. Size of the loaded font is
the size at which the glyphs get prerendered into the glyph cache, affecting
visual quality.
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 results in e.g. a 12 pt
font matching a 12 pt font in other applications, and is what's shown in the
above snippets. The most straightforward way to achieve that is to set up the
projection matrix size to match actual window pixels, such as @ref Platform::Sdl2Application::windowSize() "Platform::*Application::windowSize()".
If using the regular @ref GlyphCacheGL, for best visual quality it should be
created with the @ref AbstractFont loaded at the same size as the text to be
rendered, although often a double supersampling achieves a crisper result.
I.e., loading the font with 24 pt, but rendering with 12 pt. See below for
@ref Text-BasicRenderer-usage-font-size-dpi "additional considerations for proper DPI awareness".
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 @ref DistanceFieldGlyphCacheGL is the better match, as it
can provide text at different sizes with minimal quality loss. See its
documentation for details about picking the right font size and other
parameters for best results.
@subsection Text-BasicRenderer-usage-font-size-dpi 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 used by the @ref BasicRenderer "Renderer*D"
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 @ref AbstractFont and
@ref GlyphCacheGL should be chosen with respect to the actual physical
pixels.
When using for example @ref Platform::Sdl2Application or other `*Application`
implementations, you usually have three values at your disposal ---
@ref Platform::Sdl2Application::windowSize() "windowSize()",
@ref Platform::Sdl2Application::framebufferSize() "framebufferSize()" and
@ref Platform::Sdl2Application::dpiScaling() "dpiScaling()". Their relation is
documented thoroughly in @ref Platform-Sdl2Application-dpi, for this particular
case a scaled interface size, used instead of window size for the projection,
would be calculated like this:
@snippet Text-gl.cpp BasicRenderer-dpi-interface-size
And a multiplier for the @ref AbstractFont and @ref GlyphCacheGL font size like
this. The @ref BasicRenderer "Renderer*D" keeps using the size without this
multiplier.
@snippet Text-gl.cpp BasicRenderer-dpi-size-multiplier
@section Text-BasicRenderer-required-opengl-functionality Required OpenGL functionality
Mutable text rendering requires @gl_extension{ARB,map_buffer_range} on desktop

5
src/Magnum/Text/RendererGL.h

@ -96,6 +96,11 @@ MAGNUM_TEXT_EXPORT Debug& operator<<(Debug& output, RendererGLFlags value);
@brief OpenGL text renderer
@m_since_latest
Specialization of a @ref Renderer that uploads index and vertex data to a
@ref GL::Mesh. See the @ref Renderer class documentation for information about
setting up an instance of this class, filling it with data and drawing the text
with it.
@note This class is available only if Magnum is compiled with
@ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features
for more information.

Loading…
Cancel
Save