From b26d694d13623691d9f3ae48c66bea7651ec6e56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 9 Mar 2025 15:38:05 +0100 Subject: [PATCH] Text: random improvements to font and glyph cache docs. Not touching DistanceFieldGlyphCacheGL docs yet, but the rest was so bad that I just had to go and fix it all. --- doc/snippets/Text-gl.cpp | 38 ++++------- doc/snippets/Text.cpp | 52 ++++++++++++++- src/Magnum/Text/AbstractFont.h | 55 ++++++++++++---- src/Magnum/Text/AbstractGlyphCache.h | 70 ++++++++++++--------- src/Magnum/Text/DistanceFieldGlyphCacheGL.h | 4 +- src/Magnum/Text/GlyphCacheGL.h | 28 +++------ src/Magnum/Text/Renderer.h | 12 ++-- 7 files changed, 163 insertions(+), 96 deletions(-) diff --git a/doc/snippets/Text-gl.cpp b/doc/snippets/Text-gl.cpp index 5727ff1fb..630ac4ea1 100644 --- a/doc/snippets/Text-gl.cpp +++ b/doc/snippets/Text-gl.cpp @@ -61,17 +61,18 @@ Containers::Pointer font = if(!font->openFile("font.ttf", 12.0f)) Fatal{} << "Can't open font.ttf with StbTrueTypeFont"; -Text::GlyphCacheGL cache{PixelFormat::R8Unorm, Vector2i{128}}; -font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "0123456789?!:;,. "); +Text::GlyphCacheGL cache{PixelFormat::R8Unorm, {256, 256}}; +if(!font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789?!:;,. ")) + Fatal{} << "Glyph cache too small to fit all characters"; /* [AbstractFont-usage] */ } { -/* [AbstractGlyphCache-filling-construct] */ -Text::GlyphCacheGL cache{PixelFormat::R8Unorm, Vector2i{512}}; -/* [AbstractGlyphCache-filling-construct] */ +/* [AbstractGlyphCache-usage-construct] */ +Text::GlyphCacheGL cache{PixelFormat::R8Unorm, {256, 256}}; +/* [AbstractGlyphCache-usage-construct] */ } { @@ -84,28 +85,13 @@ Containers::Pointer font = DOXYGEN_ELLIPSIS(manager.loadAndI font->openFile("font.ttf", 96.0f); Text::DistanceFieldGlyphCacheGL cache{Vector2i{1024}, Vector2i{128}, 12}; -font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "0123456789?!:;,. "); +if(!font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789?!:;,. ")) + Fatal{} << "Glyph cache too small to fit all characters"; /* [DistanceFieldGlyphCacheGL-usage] */ } -{ -/* -Wnonnull in GCC 11+ "helpfully" says "this is null" if I don't initialize - the font pointer. I don't care, I just want you to check compilation errors, - not more! */ -PluginManager::Manager manager; -/* [GlyphCacheGL-usage] */ -Containers::Pointer font = DOXYGEN_ELLIPSIS(manager.loadAndInstantiate("")); -font->openFile("font.ttf", 12.0f); - -Text::GlyphCacheGL cache{PixelFormat::R8Unorm, Vector2i{128}}; -font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "0123456789?!:;,. "); -/* [GlyphCacheGL-usage] */ -} - { /* -Wnonnull in GCC 11+ "helpfully" says "this is null" if I don't initialize the font pointer. I don't care, I just want you to check compilation errors, diff --git a/doc/snippets/Text.cpp b/doc/snippets/Text.cpp index ac7655e12..b7d038e21 100644 --- a/doc/snippets/Text.cpp +++ b/doc/snippets/Text.cpp @@ -43,6 +43,7 @@ #include #include "Magnum/FileCallback.h" +#include "Magnum/Image.h" #include "Magnum/ImageView.h" #include "Magnum/PixelFormat.h" #include "Magnum/Math/Color.h" @@ -98,6 +99,33 @@ CORRADE_PLUGIN_REGISTER(MyFontConverter, MyNamespace::MyFontConverter, avoid -Wmisssing-prototypes */ void mainText(); void mainText() { +{ +PluginManager::Manager manager; +Containers::Pointer font = + manager.loadAndInstantiate("StbTrueTypeFont"); +struct: Text::AbstractGlyphCache { + using Text::AbstractGlyphCache::AbstractGlyphCache; + + Text::GlyphCacheFeatures doFeatures() const override { return {}; } +} cache{PixelFormat::R8Unorm, Vector2i{256}}; +/* [AbstractFont-glyph-cache-by-id] */ +CORRADE_INTERNAL_ASSERT_OUTPUT(font->fillGlyphCache(cache, { + font->glyphForName("fi"), + font->glyphForName("f_f"), + font->glyphForName("fl"), + DOXYGEN_ELLIPSIS() +})); +/* [AbstractFont-glyph-cache-by-id] */ + +/* [AbstractFont-glyph-cache-all] */ +Containers::Array glyphs{NoInit, font->glyphCount()}; +for(UnsignedInt i = 0; i != font->glyphCount(); ++i) + glyphs[i] = i; + +CORRADE_INTERNAL_ASSERT_OUTPUT(font->fillGlyphCache(cache, glyphs)); +/* [AbstractFont-glyph-cache-all] */ +} + { PluginManager::Manager manager; Containers::Pointer font = @@ -179,6 +207,26 @@ font->setFileCallback([](const std::string& filename, /* [AbstractFont-setFileCallback-template] */ } +{ +/* -Wnonnull in GCC 11+ "helpfully" says "this is null" if I don't initialize + the font pointer. I don't care, I just want you to check compilation errors, + not more! */ +PluginManager::Manager manager; +struct: Text::AbstractGlyphCache { + using Text::AbstractGlyphCache::AbstractGlyphCache; + + Text::GlyphCacheFeatures doFeatures() const override { return {}; } +} cache{PixelFormat::R8Unorm, Vector2i{256}}; +/* [AbstractGlyphCache-usage-fill] */ +Containers::Pointer font = DOXYGEN_ELLIPSIS(manager.loadAndInstantiate("")); + +if(!font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789?!:;,. ")) + Fatal{} << "Glyph cache too small to fit all characters"; +/* [AbstractGlyphCache-usage-fill] */ +} + { struct: Text::AbstractGlyphCache { using Text::AbstractGlyphCache::AbstractGlyphCache; @@ -186,7 +234,7 @@ struct: Text::AbstractGlyphCache { Text::GlyphCacheFeatures doFeatures() const override { return {}; } } cache{PixelFormat::R8Unorm, Vector2i{256}}; /* [AbstractGlyphCache-filling-images] */ -Containers::ArrayView images = DOXYGEN_ELLIPSIS({}); +Containers::Array images = DOXYGEN_ELLIPSIS({}); /* or ImageView2D, Trade::ImageData2D... */ /* [AbstractGlyphCache-filling-images] */ /* [AbstractGlyphCache-filling-font] */ @@ -200,7 +248,7 @@ cache.atlas().clearFlags( TextureTools::AtlasLandfillFlag::RotatePortrait| TextureTools::AtlasLandfillFlag::RotateLandscape); Containers::Optional range = cache.atlas().add( - stridedArrayView(images).slice(&ImageView2D::size), + stridedArrayView(images).slice(&Image2D::size), offsets); CORRADE_INTERNAL_ASSERT(range); /* [AbstractGlyphCache-filling-atlas] */ diff --git a/src/Magnum/Text/AbstractFont.h b/src/Magnum/Text/AbstractFont.h index cd6df019f..d963b43eb 100644 --- a/src/Magnum/Text/AbstractFont.h +++ b/src/Magnum/Text/AbstractFont.h @@ -114,17 +114,21 @@ glyphs will be rasterized. Then it stays open until the font is destroyed, @ref close() or another font is opened. In the following example a font is loaded from the filesystem using the -@ref StbTrueTypeFont plugin, prerendering all needed glyphs, completely with -all error handling: +@ref StbTrueTypeFont plugin, prerendering desired glyphs into a +@ref GlyphCacheGL, completely with all error handling --- depending on the +font file, font plugin implementation, font size and cache size, it may happen +that the characters won't all fit, which should be checked by the application: @snippet Text-gl.cpp AbstractFont-usage See @ref plugins for more information about general plugin usage and the list of @m_class{m-doc} derived classes for available -font plugins. See @ref GlyphCache for more information about glyph caches and -@ref BasicRenderer "Renderer*D" for information about actual text rendering. +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. -@subsection Text-AbstractFont-usage-font-size Font size +@section Text-AbstractFont-font-size Font size Font libraries specify font size in *points*, where 1 pt = ~1.333 px at 96 DPI, so in the above snippet a 12 pt font corresponds to 16 px on a 96 DPI display. @@ -136,18 +140,47 @@ 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 @ref GlyphCache. Actual text rendering with +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. This decoupling of font sizes is useful for example in case of -@ref DistanceFieldGlyphCache, 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 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. -@subsection Text-AbstractFont-usage-callbacks Loading data from memory, using file callbacks +@section Text-AbstractFont-glyph-cache Glyph cache filling options + +Besides filling the cache with glyphs corresponding to individual characters in +a UTF-8 string, it's possible to directly specify glyph IDs with +@ref fillGlyphCache(AbstractGlyphCache&, const Containers::StridedArrayView1D&). +That's useful for example with ligatures like *fl* or *ff* that can get used by +a particular font implementation instead of individual letters, and which thus +don't have a clear mapping to a Unicode codepoint. You can use some font +introspection utility to retrieve either directly the glyph IDs, or get just +glyph names and match them with IDs using @ref glyphForName() at runtime. The +following snippet prerenders a few of the named ligatures present in the +[Source Sans](https://github.com/adobe-fonts/source-sans) font that this +website uses. Again explicitly checking that the operation succeeds, this time +with a @ref CORRADE_INTERNAL_ASSERT_OUTPUT() macro: + +@snippet Text.cpp AbstractFont-glyph-cache-by-id + +Subsequent calls to @ref fillGlyphCache() add to already existing glyphs, +allowing for incremental filling based on which glyphs are needed by actual +text. Such as by passing the output of @ref AbstractShaper::glyphIdsInto() +through @ref AbstractGlyphCache::glyphIdsInto() and adding all glyphs which it +didn't find. Note that, however, the glyph cache packing is the most efficient +when all glyphs are added at once. + +Finally, it's possible to use this overload to populate the glyph cache with +all glyphs in the font: + +@snippet Text.cpp AbstractFont-glyph-cache-all + +@section Text-AbstractFont-usage-callbacks Loading data from memory, using file callbacks Besides loading data directly from the filesystem using @ref openFile() like shown above, it's possible to use @ref openData() to import data from memory. diff --git a/src/Magnum/Text/AbstractGlyphCache.h b/src/Magnum/Text/AbstractGlyphCache.h index edd42c2d9..2abaf8cf0 100644 --- a/src/Magnum/Text/AbstractGlyphCache.h +++ b/src/Magnum/Text/AbstractGlyphCache.h @@ -107,37 +107,44 @@ MAGNUM_TEXT_EXPORT Debug& operator<<(Debug& output, GlyphCacheFeatures value); @m_since{2019,10} A GPU-API-agnostic base for glyph caches, supporting multiple fonts and both 2D -and 2D array textures. See the @ref GlyphCache and @ref DistanceFieldGlyphCache -subclasses for concrete OpenGL implementations. The base class provides a -common interface for adding fonts, glyph properties, uploading glyph data and -retrieving glyph properties back. +and 2D array textures. Provides a common interface for adding fonts, glyph +properties, uploading glyph data and retrieving glyph properties back, the +@ref GlyphCacheGL and @ref DistanceFieldGlyphCacheGL subclasses then provide +concrete implementations backed with an OpenGL texture. -@section Text-AbstractGlyphCache-filling Filling the glyph cache +@section Text-AbstractGlyphCache-usage Basic usage A glyph cache is constructed through the concrete GPU-API-specific subclasses, -namely the @ref GlyphCache and @ref DistanceFieldGlyphCache classes mentioned -above. Depending on the use case and platform capabilities it's also possible -to create glyph caches with 2D texture arrays, for example when large alphabets -are used or when the cache may get filled on-demand with many additional -glyphs. - -A glyph cache is created in an appropriate @ref PixelFormat and a size. -@ref PixelFormat::R8Unorm is the usual choice, @ref PixelFormat::RGBA8Unorm is -useful for emoji fonts or when arbitrary icon data are put into the cache. - -@snippet Text-gl.cpp AbstractGlyphCache-filling-construct - -The rest of this section describes low level usage of the glyph cache filling -APIs, which are useful mainly when implementing an @ref AbstractFont itself or -when adding arbitrary other image data to the cache. When using the glyph cache -with an existing @ref AbstractFont instance, often the high level use involves -just calling @ref AbstractFont::fillGlyphCache(), which does all of the -following on its own. - -Let's say we want to fill the glyph cache with a custom set of images that -don't necessarily need to be a font per se. Assuming the input images are -stored in a simple array, and the goal is to put them all together into the -cache and reference them later simply by their array indices. +such as @ref GlyphCacheGL. Its constructor takes a @ref PixelFormat and desired +texture size. @ref PixelFormat::R8Unorm is the usual choice, +@ref PixelFormat::RGBA8Unorm is useful for emoji fonts or when arbitrary icon +data are put into the cache. + +@snippet Text-gl.cpp AbstractGlyphCache-usage-construct + +Afterwards, assuming there's an opened @ref AbstractFont instance and a known +set of glyphs to prerender, the high-level use involves just calling +@ref AbstractFont::fillGlyphCache(), which then does all packing and data +copying on its own. Note that depending on the font file, font plugin +implementation, font size and cache size, it may happen that the characters +won't all fit, which should be checked by the application: + +@snippet Text.cpp AbstractGlyphCache-usage-fill + +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. + +@section Text-AbstractGlyphCache-filling Filling the glyph cache directly + +This section describes low level usage of the glyph cache filling APIs, which +are useful mainly when implementing an @ref AbstractFont itself or when adding +arbitrary other image data to the cache. Let's say we want to fill the glyph +cache with a custom set of images that don't necessarily need to be a font per +se. Assuming the input images are stored in a simple array, and the goal is to +put them all together into the cache and reference them later simply by their +array indices. @snippet Text.cpp AbstractGlyphCache-filling-images @@ -192,7 +199,7 @@ letters *j* or *q* that reach below the baseline). Important is to call @ref flushImage() at the end, which makes the glyph cache update its actual GPU-side texture based on what area of the image was updated. -In case of @ref DistanceFieldGlyphCache for example it also triggers distance +In case of @ref DistanceFieldGlyphCacheGL for example it also triggers distance field generation for given area. If the images put into the cache are meant to be used with general meshes, the @@ -852,8 +859,9 @@ class MAGNUM_TEXT_EXPORT AbstractGlyphCache { * @param[in] fontGlyphIds Glyph IDs in given font * @param[out] glyphIds Resulting cache-global glyph IDs * - * A batch variant of @ref glyphId(), mainly meant to be used to index - * the @ref glyphOffsets() const, @ref glyphLayers() const and + * A batch variant of @ref glyphId(), mainly meant to be used with the + * output of @ref AbstractShaper::glyphIdsInto() to then index the + * @ref glyphOffsets() const, @ref glyphLayers() const and * @ref glyphRectangles() const views. * * The @p fontId is expected to be less than @ref fontCount(), all diff --git a/src/Magnum/Text/DistanceFieldGlyphCacheGL.h b/src/Magnum/Text/DistanceFieldGlyphCacheGL.h index bc3c63969..17c9d62bc 100644 --- a/src/Magnum/Text/DistanceFieldGlyphCacheGL.h +++ b/src/Magnum/Text/DistanceFieldGlyphCacheGL.h @@ -44,8 +44,8 @@ namespace Magnum { namespace Text { @m_since_latest Unlike the base @ref GlyphCacheGL, this class converts each binary image to a -distance field. It's not possible to only use this cache for monochrome glyphs -as the internal texture format is single-channel. +distance field. It's possible to only use this cache for monochrome glyphs as +the internal texture format is single-channel. @section Text-DistanceFieldGlyphCacheGL-usage Usage diff --git a/src/Magnum/Text/GlyphCacheGL.h b/src/Magnum/Text/GlyphCacheGL.h index 773df468d..735938340 100644 --- a/src/Magnum/Text/GlyphCacheGL.h +++ b/src/Magnum/Text/GlyphCacheGL.h @@ -40,28 +40,21 @@ namespace Magnum { namespace Text { /** -@brief OpenGL glyph cache +@brief OpenGL implementation of a glyph cache @m_since_latest -Contains font glyphs rendered into a texture atlas. - -@section Text-GlyphCacheGL-usage Usage - -Create the @ref GlyphCacheGL object with sufficient size and then call -@ref AbstractFont::createGlyphCache() to fill it with glyphs. - -@snippet Text-gl.cpp GlyphCacheGL-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. +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. @section Text-GlyphCacheGL-internal-format Internal texture format The @ref GL::TextureFormat used by @ref texture() is implicitly coming from -@ref GL::textureFormat(Magnum::PixelFormat) applied to @ref format(), or if -@ref GlyphCacheFeature::ImageProcessing is supported, to @ref processedFormat() -instead. +@ref GL::textureFormat(Magnum::PixelFormat) applied to the @ref format() that +was passed at construction time, or if @ref GlyphCacheFeature::ImageProcessing +is supported, to @ref processedFormat() instead. If @ref PixelFormat::R8Unorm is used for @ref format() or if @ref GlyphCacheFeature::ImageProcessing is supported and @@ -195,8 +188,7 @@ class MAGNUM_TEXT_EXPORT GlyphCacheGL: public AbstractGlyphCache { private: MAGNUM_TEXT_LOCAL GlyphCacheFeatures doFeatures() const override; - /** @todo make those MAGNUM_TEXT_LOCAL again once MagnumFont doesn't - need to subclass anything anymore */ + /* These are not MAGNUM_TEXT_LOCAL because the test makes a subclass */ void doSetImage(const Vector2i& offset, const ImageView2D& image) override; /* Used if a subclass advertises GlyphCacheFeature::ImageProcessing / ProcessedImageDownload in its doFeatures() */ diff --git a/src/Magnum/Text/Renderer.h b/src/Magnum/Text/Renderer.h index 9743822d1..1e95c3e5f 100644 --- a/src/Magnum/Text/Renderer.h +++ b/src/Magnum/Text/Renderer.h @@ -524,7 +524,7 @@ that doesn't recreate everything on each text change: @subsection Text-BasicRenderer-usage-font-size Font size -As mentioned in @ref Text-AbstractFont-usage-font-size "AbstractFont class documentation", +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() @@ -539,7 +539,7 @@ 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 GlyphCache, for best visual quality it should be +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 @@ -549,8 +549,8 @@ 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 DistanceFieldGlyphCache is the better match, as it can -provide text at different sizes with minimal quality loss. See its +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. @@ -571,7 +571,7 @@ concepts for DPI-aware rendering: 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 GlyphCache should be chosen with respect to the actual physical + @ref GlyphCacheGL should be chosen with respect to the actual physical pixels. When using for example @ref Platform::Sdl2Application or other `*Application` @@ -585,7 +585,7 @@ would be calculated like this: @snippet Text-gl.cpp BasicRenderer-dpi-interface-size -And a multiplier for the @ref AbstractFont and @ref GlyphCache font size like +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.