From cc19e789830ea17e5b8606f137daee9321f9e515 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 28 Mar 2013 15:00:26 +0100 Subject: [PATCH] Text: moved glyph caching into separate classes. The API is similar for both basic and distance field caching. --- src/Text/AbstractFont.h | 28 ++--- src/Text/CMakeLists.txt | 4 + src/Text/DistanceFieldGlyphCache.cpp | 66 +++++++++++ src/Text/DistanceFieldGlyphCache.h | 92 ++++++++++++++++ src/Text/FreeTypeFont.cpp | 103 +++++------------- src/Text/FreeTypeFont.h | 85 +++------------ src/Text/GlyphCache.cpp | 91 ++++++++++++++++ src/Text/GlyphCache.h | 157 +++++++++++++++++++++++++++ src/Text/HarfBuzzFont.cpp | 21 +++- src/Text/HarfBuzzFont.h | 16 +-- src/Text/Text.h | 2 + src/Text/TextRenderer.cpp | 18 +-- src/Text/TextRenderer.h | 66 ++++++----- 13 files changed, 541 insertions(+), 208 deletions(-) create mode 100644 src/Text/DistanceFieldGlyphCache.cpp create mode 100644 src/Text/DistanceFieldGlyphCache.h create mode 100644 src/Text/GlyphCache.cpp create mode 100644 src/Text/GlyphCache.h diff --git a/src/Text/AbstractFont.h b/src/Text/AbstractFont.h index 631974f24..33456bfc4 100644 --- a/src/Text/AbstractFont.h +++ b/src/Text/AbstractFont.h @@ -51,22 +51,24 @@ class MAGNUM_TEXT_EXPORT AbstractFont { explicit AbstractFont(); virtual ~AbstractFont() = 0; - /** @brief %Font texture atlas */ - inline Texture2D& texture() { return _texture; } - /** - * @brief Layout the text using fon't own layouter - * @param size %Font size - * @param text Text to layout + * @brief Create glyph cache for given character set + * @param cache Glyph cache instance + * @param characters UTF-8 characters to render + * + * Fills the cache with given characters. */ - virtual AbstractLayouter* layout(const Float size, const std::string& text) = 0; + virtual void createGlyphCache(GlyphCache* const cache, const std::string& characters) = 0; - #ifdef DOXYGEN_GENERATING_OUTPUT - private: - #else - protected: - #endif - Texture2D _texture; + /** + * @brief Layout the text using font own layouter + * @param cache Glyph cache + * @param size Font size + * @param text %Text to layout + * + * @see createGlyphCache() + */ + virtual AbstractLayouter* layout(const GlyphCache* const cache, const Float size, const std::string& text) = 0; }; /** diff --git a/src/Text/CMakeLists.txt b/src/Text/CMakeLists.txt index 517ebbea2..127a32f06 100644 --- a/src/Text/CMakeLists.txt +++ b/src/Text/CMakeLists.txt @@ -27,10 +27,14 @@ include_directories(${FREETYPE_INCLUDE_DIRS}) set(MagnumText_SRCS AbstractFont.cpp + DistanceFieldGlyphCache.cpp + GlyphCache.cpp FreeTypeFont.cpp TextRenderer.cpp) set(MagnumText_HEADERS AbstractFont.h + DistanceFieldGlyphCache.h + GlyphCache.h FreeTypeFont.h Text.h TextRenderer.h diff --git a/src/Text/DistanceFieldGlyphCache.cpp b/src/Text/DistanceFieldGlyphCache.cpp new file mode 100644 index 000000000..af4a842d7 --- /dev/null +++ b/src/Text/DistanceFieldGlyphCache.cpp @@ -0,0 +1,66 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "DistanceFieldGlyphCache.h" + +#include "Extensions.h" +#include "Image.h" +#include "TextureTools/DistanceField.h" + +namespace Magnum { namespace Text { + +namespace { + #if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_GLES3) + const AbstractTexture::InternalFormat internalFormat = AbstractTexture::InternalFormat::R8; + #else + const AbstractTexture::InternalFormat internalFormat = AbstractTexture::InternalFormat::Red; + #endif +} + +DistanceFieldGlyphCache::DistanceFieldGlyphCache(const Vector2i& originalSize, const Vector2i& distanceFieldSize, UnsignedInt radius): GlyphCache(originalSize, Vector2i(radius)), scale(Vector2(distanceFieldSize)/originalSize), radius(radius) { + #ifndef MAGNUM_TARGET_GLES + MAGNUM_ASSERT_EXTENSION_SUPPORTED(Extensions::GL::ARB::texture_rg); + #else + MAGNUM_ASSERT_EXTENSION_SUPPORTED(Extensions::GL::EXT::texture_rg); + #endif + + initialize(internalFormat, distanceFieldSize); +} + +void DistanceFieldGlyphCache::setImage(const Vector2i& offset, Image2D* const image) { + Texture2D input; + input.setWrapping(Texture2D::Wrapping::ClampToEdge) + ->setMinificationFilter(Texture2D::Filter::Linear) + ->setMagnificationFilter(Texture2D::Filter::Linear) + ->setImage(0, internalFormat, image); + + /* Create distance field from input texture */ + TextureTools::distanceField(&input, &_texture, Rectanglei::fromSize(offset*scale, image->size()*scale), radius); +} + +void DistanceFieldGlyphCache::setDistanceFieldImage(const Vector2i& offset, Image2D* const image) { + _texture.setSubImage(0, offset, image); +} + +}} diff --git a/src/Text/DistanceFieldGlyphCache.h b/src/Text/DistanceFieldGlyphCache.h new file mode 100644 index 000000000..80bcaceba --- /dev/null +++ b/src/Text/DistanceFieldGlyphCache.h @@ -0,0 +1,92 @@ +#ifndef Magnum_Text_DistanceFieldGlyphCache_h +#define Magnum_Text_DistanceFieldGlyphCache_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Class Magnum::Text::DistanceFieldGlyphCache + */ + +#include "Text/GlyphCache.h" + +namespace Magnum { namespace Text { + +/** +@brief Glyph cache with distance field rendering + +Unlike original GlyphCache converts each binary image to distance field. It is +not possible to use non-binary colors with this cache, internal texture format +is red channel only. + +@section GlyphCache-usage Usage + +Usage is similar to GlyphCache, additionaly you need to specify size of +resulting distance field texture. +@code +Text::AbstractFont* font; +Text::GlyphCache* cache = new Text::DistanceFieldGlyphCache(Vector2i(2048), Vector2i(384)); +font->createGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789 "); +@endcode + +@see TextureTools::distanceField() +*/ +class MAGNUM_TEXT_EXPORT DistanceFieldGlyphCache: public GlyphCache { + public: + /** + * @brief Constructor + * @param originalSize Original cache texture size + * @param distanceFieldSize Size of computed distance field texture + * @param radius Distance field computation radius + * + * See TextureTools::distanceField() for more information about the + * parameters. + */ + explicit DistanceFieldGlyphCache(const Vector2i& originalSize, const Vector2i& distanceFieldSize, UnsignedInt radius); + + /** + * @brief Set cache image + * + * Uploads image for one or more glyphs to given offset in original + * cache texture. The texture is then converted to distance field. + */ + void setImage(const Vector2i& offset, Image2D* const image) override; + + /** + * @brief Set distance field cache image + * + * Uploads already computed distance field image to given offset in + * distance field texture. + */ + void setDistanceFieldImage(const Vector2i& offset, Image2D* const image); + + private: + const Vector2 scale; + const UnsignedInt radius; +}; + +}} + +#endif diff --git a/src/Text/FreeTypeFont.cpp b/src/Text/FreeTypeFont.cpp index a5fd3c785..d60d86f90 100644 --- a/src/Text/FreeTypeFont.cpp +++ b/src/Text/FreeTypeFont.cpp @@ -23,6 +23,7 @@ */ #include "FreeTypeFont.h" +#include "GlyphCache.h" #include #include @@ -40,14 +41,15 @@ namespace { class FreeTypeLayouter: public AbstractLayouter { public: - explicit FreeTypeLayouter(FreeTypeFont& font, const Float size, const std::string& text); + explicit FreeTypeLayouter(FreeTypeFont& font, const GlyphCache* const cache, const Float size, const std::string& text); std::tuple renderGlyph(const Vector2& cursorPosition, const UnsignedInt i) override; private: FreeTypeFont& font; - std::vector glyphs; + const GlyphCache* const cache; const Float size; + std::vector glyphs; }; } @@ -62,34 +64,15 @@ FreeTypeFontRenderer::~FreeTypeFontRenderer() { FreeTypeFont::FreeTypeFont(FreeTypeFontRenderer& renderer, const std::string& fontFile, Float size): _size(size) { CORRADE_INTERNAL_ASSERT_OUTPUT(FT_New_Face(renderer.library(), fontFile.c_str(), 0, &_ftFont) == 0); - - finishConstruction(); + CORRADE_INTERNAL_ASSERT_OUTPUT(FT_Set_Char_Size(_ftFont, 0, _size*64, 100, 100) == 0); } FreeTypeFont::FreeTypeFont(FreeTypeFontRenderer& renderer, const unsigned char* data, std::size_t dataSize, Float size): _size(size) { CORRADE_INTERNAL_ASSERT_OUTPUT(FT_New_Memory_Face(renderer.library(), data, dataSize, 0, &_ftFont) == 0); - - finishConstruction(); -} - -void FreeTypeFont::finishConstruction() { CORRADE_INTERNAL_ASSERT_OUTPUT(FT_Set_Char_Size(_ftFont, 0, _size*64, 100, 100) == 0); - - #ifndef MAGNUM_TARGET_GLES - MAGNUM_ASSERT_EXTENSION_SUPPORTED(Extensions::GL::ARB::texture_rg); - #else - MAGNUM_ASSERT_EXTENSION_SUPPORTED(Extensions::GL::EXT::texture_rg); - #endif - - /* Set up the texture */ - _texture.setWrapping(Texture2D::Wrapping::ClampToEdge) - ->setMinificationFilter(Texture2D::Filter::Linear) - ->setMagnificationFilter(Texture2D::Filter::Linear); } -void FreeTypeFont::prerenderInternal(const std::string& characters, const Vector2i& atlasSize, const Int radius, Texture2D* output) { - glyphs.clear(); - +void FreeTypeFont::createGlyphCache(GlyphCache* const cache, const std::string& characters) { /** @bug Crash when atlas is too small */ /* Get glyph codes from characters */ @@ -107,7 +90,6 @@ void FreeTypeFont::prerenderInternal(const std::string& characters, const Vector charIndices.erase(std::unique(charIndices.begin(), charIndices.end()), charIndices.end()); /* Sizes of all characters */ - const Vector2i padding = Vector2i(radius); std::vector charSizes; charSizes.reserve(charIndices.size()); for(FT_UInt c: charIndices) { @@ -116,12 +98,11 @@ void FreeTypeFont::prerenderInternal(const std::string& characters, const Vector } /* Create texture atlas */ - const std::vector charPositions = TextureTools::atlas(atlasSize, charSizes, padding); + const std::vector charPositions = cache->reserve(charSizes); /* Render all characters to the atlas and create character map */ - glyphs.reserve(charPositions.size()); - unsigned char* pixmap = new unsigned char[atlasSize.product()](); - Image2D image(atlasSize, Image2D::Format::Red, Image2D::Type::UnsignedByte, pixmap); + unsigned char* pixmap = new unsigned char[cache->textureSize().product()](); + Image2D image(cache->textureSize(), Image2D::Format::Red, Image2D::Type::UnsignedByte, pixmap); for(std::size_t i = 0; i != charPositions.size(); ++i) { /* Load and render glyph */ /** @todo B&W only if radius != 0 */ @@ -135,63 +116,29 @@ void FreeTypeFont::prerenderInternal(const std::string& characters, const Vector CORRADE_INTERNAL_ASSERT(std::abs(bitmap.rows-charPositions[i].height()) <= 2); for(Int yin = 0, yout = charPositions[i].bottom(), ymax = bitmap.rows; yin != ymax; ++yin, ++yout) for(Int xin = 0, xout = charPositions[i].left(), xmax = bitmap.width; xin != xmax; ++xin, ++xout) - pixmap[yout*atlasSize.x() + xout] = bitmap.buffer[(bitmap.rows-yin-1)*bitmap.width + xin]; - - /* Save character texture position and texture coordinates for given character index */ - CORRADE_INTERNAL_ASSERT_OUTPUT(glyphs.insert({charIndices[i], std::make_tuple( - Rectangle::fromSize((Vector2(glyph->bitmap_left, glyph->bitmap_top-charPositions[i].height()) - Vector2(radius))/_size, - Vector2(charPositions[i].size() + 2*padding)/_size), - Rectangle(Vector2(charPositions[i].bottomLeft() - padding)/atlasSize, - Vector2(charPositions[i].topRight() + padding)/atlasSize) - )}).second); - } - - /* Set texture data */ - #ifndef MAGNUM_TARGET_GLES - output->setImage(0, Texture2D::InternalFormat::R8, &image); - #else - output->setImage(0, Texture2D::InternalFormat::Red, &image); - #endif -} - -void FreeTypeFont::prerender(const std::string& characters, const Vector2i& atlasSize) { - prerenderInternal(characters, atlasSize, 0, &_texture); -} + pixmap[yout*cache->textureSize().x() + xout] = bitmap.buffer[(bitmap.rows-yin-1)*bitmap.width + xin]; -void FreeTypeFont::prerenderDistanceField(const std::string& characters, const Vector2i& sourceAtlasSize, const Vector2i& atlasSize, Int radius) { - MAGNUM_ASSERT_EXTENSION_SUPPORTED(Extensions::GL::ARB::texture_storage); - - /* Render input texture */ - Texture2D input; - input.setWrapping(Texture2D::Wrapping::ClampToEdge) - ->setMinificationFilter(Texture2D::Filter::Linear) - ->setMagnificationFilter(Texture2D::Filter::Linear); - prerenderInternal(characters, sourceAtlasSize, radius, &input); + /* Insert glyph parameters into cache */ + cache->insert(charIndices[i], + Vector2i(glyph->bitmap_left, glyph->bitmap_top-charPositions[i].height()), + charPositions[i]); + } - /* Create distance field from input texture */ - _texture.setStorage(1, Texture2D::InternalFormat::R8, atlasSize); - TextureTools::distanceField(&input, &_texture, Rectanglei::fromSize({}, atlasSize), radius); + /* Set cache image */ + cache->setImage({}, &image); } FreeTypeFont::~FreeTypeFont() { CORRADE_INTERNAL_ASSERT_OUTPUT(FT_Done_Face(_ftFont) == 0); } -const std::tuple& FreeTypeFont::operator[](char32_t character) const { - auto it = glyphs.find(character); - - if(it == glyphs.end()) - return glyphs.at(0); - return it->second; -} - -AbstractLayouter* FreeTypeFont::layout(const Float size, const std::string& text) { - return new FreeTypeLayouter(*this, size, text); +AbstractLayouter* FreeTypeFont::layout(const GlyphCache* const cache, const Float size, const std::string& text) { + return new FreeTypeLayouter(*this, cache, size, text); } namespace { -FreeTypeLayouter::FreeTypeLayouter(FreeTypeFont& font, const Float size, const std::string& text): font(font), size(size) { +FreeTypeLayouter::FreeTypeLayouter(FreeTypeFont& font, const GlyphCache* const cache, const Float size, const std::string& text): font(font), cache(cache), size(size) { /* Get glyph codes from characters */ glyphs.reserve(text.size()); for(std::size_t i = 0; i != text.size(); ) { @@ -204,8 +151,14 @@ FreeTypeLayouter::FreeTypeLayouter(FreeTypeFont& font, const Float size, const s std::tuple FreeTypeLayouter::renderGlyph(const Vector2& cursorPosition, const UnsignedInt i) { /* Position of the texture in the resulting glyph, texture coordinates */ - Rectangle texturePosition, textureCoordinates; - std::tie(texturePosition, textureCoordinates) = font[glyphs[i]]; + Vector2i position; + Rectanglei rectangle; + std::tie(position, rectangle) = (*cache)[glyphs[i]]; + + Rectangle texturePosition = Rectangle::fromSize(Vector2(position)/font.size(), + Vector2(rectangle.size())/font.size()); + Rectangle textureCoordinates(Vector2(rectangle.bottomLeft())/cache->textureSize(), + Vector2(rectangle.topRight())/cache->textureSize()); /* Load glyph */ CORRADE_INTERNAL_ASSERT_OUTPUT(FT_Load_Glyph(font.font(), glyphs[i], FT_LOAD_DEFAULT) == 0); diff --git a/src/Text/FreeTypeFont.h b/src/Text/FreeTypeFont.h index 223698995..faa4f1d50 100644 --- a/src/Text/FreeTypeFont.h +++ b/src/Text/FreeTypeFont.h @@ -66,97 +66,50 @@ class MAGNUM_TEXT_EXPORT FreeTypeFontRenderer { /** @brief FreeType font -Contains font with characters prerendered into texture atlas. - @section FreeTypeFont-usage Usage -You need to maintain instance of FreeTypeFontRenderer during the lifetime of all FreeTypeFont -instances. The font can be created either from file or from memory location of -format supported by [FreeType](http://www.freetype.org/) library. Next step is -to prerender all the glyphs which will be used in text rendering later. +You need to maintain instance of FreeTypeFontRenderer during the lifetime of +all FreeTypeFont instances. The font can be created either from file or from +memory location of format supported by [FreeType](http://www.freetype.org/) +library. @code Text::FreeTypeFontRenderer fontRenderer; Text::FreeTypeFont font(fontRenderer, "MyFreeTypeFont.ttf", 48.0f); -font.prerender("abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "0123456789 ", Vector2i(512)); @endcode -See TextRenderer for information about text rendering. - -@section FreeTypeFont-extensions Required OpenGL functionality - -%Font texture uses one-component internal format, which requires -@extension{ARB,texture_rg} (also part of OpenGL ES 3.0 or available as -@es_extension{EXT,texture_rg} in ES 2.0). +Next step is to prerender all the glyphs which will be used in text rendering +later, see GlyphCache for more information. See TextRenderer for information +about text rendering. */ class MAGNUM_TEXT_EXPORT FreeTypeFont: public AbstractFont { public: /** * @brief Create font from file - * @param renderer %Font renderer - * @param fontFile %Font file - * @param size %Font size + * @param renderer Font renderer + * @param fontFile Font file + * @param size Font size */ explicit FreeTypeFont(FreeTypeFontRenderer& renderer, const std::string& fontFile, Float size); /** * @brief Create font from memory - * @param renderer %Font renderer - * @param data %Font data - * @param dataSize %Font data size - * @param size %Font size + * @param renderer Font renderer + * @param data Font data + * @param dataSize Font data size + * @param size Font size */ explicit FreeTypeFont(FreeTypeFontRenderer& renderer, const unsigned char* data, std::size_t dataSize, Float size); - /** - * @brief Prerender given character set - * @param characters UTF-8 characters to render - * @param atlasSize Size of resulting atlas - * - * Creates new atlas with prerendered characters, replacing the - * previous one (if any). - * @attention @p atlasSize must be large enough to contain all - * rendered glyphs. - */ - void prerender(const std::string& characters, const Vector2i& atlasSize); - - /** - * @brief Prerender given character set for use with distance-field rendering - * @param characters UTF-8 characters to render - * @param sourceAtlasSize Size of distance field source atlas - * @param atlasSize Size of resulting atlas - * @param radius Max lookup radius for distance-field creation - * - * Creates new atlas with prerendered characters, replacing the - * previous one (if any). See TextureTools::distanceField() for more - * information. - * @attention @p sourceAtlasSize must be large enough to contain all - * rendered glyphs with padding given by @p radius. - */ - void prerenderDistanceField(const std::string& characters, const Vector2i& sourceAtlasSize, const Vector2i& atlasSize, Int radius); - ~FreeTypeFont(); - /** @brief %Font size */ + /** @brief Font size */ inline Float size() const { return _size; } - /** @brief Count of prerendered glyphs in the font */ - inline std::size_t glyphCount() const { return glyphs.size(); } - - /** - * @brief Position of given character in the texture - * @param character Unicode character code (UTF-32) - * - * First returned rectangle is texture position relative to point on - * baseline, second is position of the texture in texture atlas. - */ - const std::tuple& operator[](char32_t character) const; - /** @brief FreeType font handle */ inline FT_Face font() { return _ftFont; } - AbstractLayouter* layout(const Float size, const std::string& text) override; + void createGlyphCache(GlyphCache* const cache, const std::string& characters) override; + AbstractLayouter* layout(const GlyphCache* const cache, const Float size, const std::string& text) override; #ifdef DOXYGEN_GENERATING_OUTPUT private: @@ -166,10 +119,6 @@ class MAGNUM_TEXT_EXPORT FreeTypeFont: public AbstractFont { FT_Face _ftFont; private: - void MAGNUM_TEXT_LOCAL finishConstruction(); - void MAGNUM_TEXT_LOCAL prerenderInternal(const std::string& characters, const Vector2i& atlasSize, const Int radius, Texture2D* output); - - std::unordered_map> glyphs; Float _size; }; diff --git a/src/Text/GlyphCache.cpp b/src/Text/GlyphCache.cpp new file mode 100644 index 000000000..ad6c81aee --- /dev/null +++ b/src/Text/GlyphCache.cpp @@ -0,0 +1,91 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "GlyphCache.h" + +#include "Extensions.h" +#include "Image.h" +#include "TextureTools/Atlas.h" + +namespace Magnum { namespace Text { + +namespace { + #if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_GLES3) + const AbstractTexture::InternalFormat internalFormat = AbstractTexture::InternalFormat::R8; + #else + const AbstractTexture::InternalFormat internalFormat = AbstractTexture::InternalFormat::Red; + #endif +} + +GlyphCache::GlyphCache(const Vector2i& size): _size(size) { + #ifndef MAGNUM_TARGET_GLES + MAGNUM_ASSERT_EXTENSION_SUPPORTED(Extensions::GL::ARB::texture_rg); + #else + MAGNUM_ASSERT_EXTENSION_SUPPORTED(Extensions::GL::EXT::texture_rg); + #endif + + initialize(internalFormat, size); +} + +GlyphCache::GlyphCache(const Vector2i& size, const AbstractTexture::InternalFormat internalFormat): _size(size) { + initialize(internalFormat, size); +} + +GlyphCache::GlyphCache(const Vector2i& size, const Vector2i& padding): _size(size), _padding(padding) {} + +GlyphCache::~GlyphCache() = default; + +/** @todo Delegating constructor when support for GCC 4.6 is dropped */ +void GlyphCache::initialize(const AbstractTexture::InternalFormat internalFormat, const Vector2i& size) { + #ifndef MAGNUM_TARGET_GLES + MAGNUM_ASSERT_EXTENSION_SUPPORTED(Extensions::GL::ARB::texture_storage); + #else + MAGNUM_ASSERT_EXTENSION_SUPPORTED(Extensions::GL::EXT::texture_storage); + #endif + + _texture.setWrapping(Texture2D::Wrapping::ClampToEdge) + ->setMinificationFilter(Texture2D::Filter::Linear) + ->setMagnificationFilter(Texture2D::Filter::Linear) + ->setStorage(1, internalFormat, size); +} + +std::vector GlyphCache::reserve(const std::vector& sizes) { + CORRADE_ASSERT(glyphs.empty(), "Text::GlyphCache::reserve(): reserving space in non-empty cache is not yet implemented", {}); + glyphs.reserve(glyphs.size() + sizes.size()); + return TextureTools::atlas(_size, sizes, _padding); +} + +void GlyphCache::insert(const UnsignedInt glyph, Vector2i position, Rectanglei rectangle) { + position -= _padding; + rectangle.bottomLeft() -= _padding; + rectangle.topRight() += _padding; + + glyphs.insert({glyph, {position, rectangle}}); +} + +void GlyphCache::setImage(const Vector2i& offset, Image2D* const image) { + _texture.setSubImage(0, offset, image); +} + +}} diff --git a/src/Text/GlyphCache.h b/src/Text/GlyphCache.h new file mode 100644 index 000000000..92f91544b --- /dev/null +++ b/src/Text/GlyphCache.h @@ -0,0 +1,157 @@ +#ifndef Magnum_Text_GlyphCache_h +#define Magnum_Text_GlyphCache_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Class Magnum::Text::GlyphCache + */ + +#include + +#include "Math/Geometry/Rectangle.h" +#include "Texture.h" +#include "Text/magnumTextVisibility.h" + +namespace Magnum { namespace Text { + +/** +@brief Glyph cache + +Contains font glyphs prerendered into texture atlas. + +@section GlyphCache-usage Usage + +Create %GlyphCache object with sufficient size and then call +AbstractFont::createGlyphCache() to fill it with glyphs. +@code +Text::AbstractFont* font; +Text::GlyphCache* cache = new GlyphCache(Vector2i(512)); +font->createGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789 "); +@endcode + +See TextRenderer for information about text rendering. +*/ +class MAGNUM_TEXT_EXPORT GlyphCache { + public: + /** + * @brief Constructor + * @param size Glyph cache texture size + * @param internalFormat Internal texture format + */ + explicit GlyphCache(const Vector2i& size, const Texture2D::InternalFormat internalFormat); + + /** + * @brief Constructor + * @param size Glyph cache texture size + * + * Sets internal texture format to red channel only. Requires + * @extension{ARB,texture_rg} (also part of OpenGL ES 3.0 or available + * as @es_extension{EXT,texture_rg} in ES 2.0). + */ + explicit GlyphCache(const Vector2i& size); + + virtual ~GlyphCache(); + + /** + * @brief Cache size + * + * Size of unscaled glyph cache texture. + */ + inline Vector2i textureSize() const { return _size; } + + /** @brief Count of glyphs in the cache */ + inline std::size_t glyphCount() const { return glyphs.size(); } + + /** @brief Cache texture */ + inline Texture2D* texture() { return &_texture; } + + /** + * @brief Parameters of given glyph + * @param glyph Glyph ID + * + * First tuple element is glyph position relative to point on baseline, + * second element is glyph region in texture atlas. If no glyph is + * found, glyph on zero index is returned. + */ + inline std::pair operator[](const UnsignedInt glyph) const { + auto it = glyphs.find(glyph); + return it == glyphs.end() ? glyphs.at(0) : it->second; + } + + /** + * @brief Layout glyphs with given sizes to the cache + * + * Returns non-overlapping regions in cache texture to store glyphs. + * The reserved space is reused on next call to reserve() if no glyph + * was stored there, use insert() to store actual glyph on given + * position and setImage() to upload glyph image. + * + * @attention Cache size must be large enough to contain all rendered + * glyphs. + */ + std::vector reserve(const std::vector& sizes); + + /** + * @brief Insert glyph to cache + * @param glyph Glyph ID + * @param position Position relative to point on baseline + * @param rectangle Region in texture atlas + * + * You can obtain unused non-overlapping regions with reserve(). See + * also setImage() to upload glyph image. + */ + void insert(const UnsignedInt glyph, Vector2i position, Rectanglei rectangle); + + /** + * @brief Set cache image + * + * Uploads image for one or more glyphs to given offset in cache + * texture. + */ + virtual void setImage(const Vector2i& offset, Image2D* const image); + + #ifdef DOXYGEN_GENERATING_OUTPUT + private: + #else + protected: + #endif + /* Used from DistanceFieldGlyphCache */ + explicit MAGNUM_LOCAL GlyphCache(const Vector2i& size, const Vector2i& padding); + + void MAGNUM_LOCAL initialize(const Texture2D::InternalFormat internalFormat, const Vector2i& size); + + const Vector2i _size; + Texture2D _texture; + + private: + const Vector2i _padding; + std::unordered_map> glyphs; +}; + +}} + +#endif diff --git a/src/Text/HarfBuzzFont.cpp b/src/Text/HarfBuzzFont.cpp index b3689017b..4115cfee2 100644 --- a/src/Text/HarfBuzzFont.cpp +++ b/src/Text/HarfBuzzFont.cpp @@ -26,19 +26,22 @@ #include +#include "GlyphCache.h" + namespace Magnum { namespace Text { namespace { class HarfBuzzLayouter: public AbstractLayouter { public: - explicit HarfBuzzLayouter(HarfBuzzFont& font, const Float size, const std::string& text); + explicit HarfBuzzLayouter(HarfBuzzFont& font, const GlyphCache* const cache, const Float size, const std::string& text); ~HarfBuzzLayouter(); std::tuple renderGlyph(const Vector2& cursorPosition, const UnsignedInt i) override; private: const HarfBuzzFont& font; + const GlyphCache* const cache; hb_buffer_t* buffer; hb_glyph_info_t* glyphInfo; hb_glyph_position_t* glyphPositions; @@ -64,13 +67,13 @@ HarfBuzzFont::~HarfBuzzFont() { hb_font_destroy(_hbFont); } -AbstractLayouter* HarfBuzzFont::layout(const Float size, const std::string& text) { - return new HarfBuzzLayouter(*this, size, text); +AbstractLayouter* HarfBuzzFont::layout(const GlyphCache* const cache, const Float size, const std::string& text) { + return new HarfBuzzLayouter(*this, cache, size, text); } namespace { -HarfBuzzLayouter::HarfBuzzLayouter(HarfBuzzFont& font, const Float size, const std::string& text): font(font), size(size) { +HarfBuzzLayouter::HarfBuzzLayouter(HarfBuzzFont& font, const GlyphCache* const cache, const Float size, const std::string& text): font(font), cache(cache), size(size) { /* Prepare HarfBuzz buffer */ buffer = hb_buffer_create(); hb_buffer_set_direction(buffer, HB_DIRECTION_LTR); @@ -92,8 +95,14 @@ HarfBuzzLayouter::~HarfBuzzLayouter() { std::tuple HarfBuzzLayouter::renderGlyph(const Vector2& cursorPosition, const UnsignedInt i) { /* Position of the texture in the resulting glyph, texture coordinates */ - Rectangle texturePosition, textureCoordinates; - std::tie(texturePosition, textureCoordinates) = font[glyphInfo[i].codepoint]; + Vector2i position; + Rectanglei rectangle; + std::tie(position, rectangle) = (*cache)[glyphInfo[i].codepoint]; + + Rectangle texturePosition = Rectangle::fromSize(Vector2(position)/font.size(), + Vector2(rectangle.size())/font.size()); + Rectangle textureCoordinates(Vector2(rectangle.bottomLeft())/cache->textureSize(), + Vector2(rectangle.topRight())/cache->textureSize()); /* Glyph offset and advance to next glyph in normalized coordinates */ Vector2 offset = Vector2(glyphPositions[i].x_offset, diff --git a/src/Text/HarfBuzzFont.h b/src/Text/HarfBuzzFont.h index b34b8ab77..d8e0277c6 100644 --- a/src/Text/HarfBuzzFont.h +++ b/src/Text/HarfBuzzFont.h @@ -51,18 +51,18 @@ class MAGNUM_TEXT_EXPORT HarfBuzzFont: public FreeTypeFont { public: /** * @brief Create font from file - * @param renderer %Font renderer - * @param fontFile %Font file - * @param size %Font size + * @param renderer Font renderer + * @param fontFile Font file + * @param size Font size */ explicit HarfBuzzFont(FreeTypeFontRenderer& renderer, const std::string& fontFile, Float size); /** * @brief Create font from memory - * @param renderer %Font renderer - * @param data %Font data - * @param dataSize %Font data size - * @param size %Font size + * @param renderer Font renderer + * @param data Font data + * @param dataSize Font data size + * @param size Font size */ explicit HarfBuzzFont(FreeTypeFontRenderer& renderer, const unsigned char* data, std::size_t dataSize, Float size); @@ -71,7 +71,7 @@ class MAGNUM_TEXT_EXPORT HarfBuzzFont: public FreeTypeFont { /** @brief HarfBuzz font handle */ inline hb_font_t* font() { return _hbFont; } - AbstractLayouter* layout(const Float size, const std::string& text) override; + AbstractLayouter* layout(const GlyphCache* const cache, const Float size, const std::string& text) override; private: void MAGNUM_TEXT_LOCAL finishConstruction(); diff --git a/src/Text/Text.h b/src/Text/Text.h index 7f18557d9..85087ad11 100644 --- a/src/Text/Text.h +++ b/src/Text/Text.h @@ -36,6 +36,8 @@ namespace Magnum { namespace Text { class AbstractFont; class AbstractLayouter; +class DistanceFieldGlyphCache; +class GlyphCache; class FreeTypeFontRenderer; class FreeTypeFont; diff --git a/src/Text/TextRenderer.cpp b/src/Text/TextRenderer.cpp index e471b968d..a0ee125d6 100644 --- a/src/Text/TextRenderer.cpp +++ b/src/Text/TextRenderer.cpp @@ -60,8 +60,8 @@ struct Vertex { } -std::tuple, std::vector, std::vector, Rectangle> AbstractTextRenderer::render(AbstractFont& font, Float size, const std::string& text) { - AbstractLayouter* const layouter = font.layout(size, text); +std::tuple, std::vector, std::vector, Rectangle> AbstractTextRenderer::render(AbstractFont* const font, const GlyphCache* const cache, Float size, const std::string& text) { + AbstractLayouter* const layouter = font->layout(cache, size, text); const UnsignedInt vertexCount = layouter->glyphCount()*4; /* Output data */ @@ -106,8 +106,8 @@ std::tuple, std::vector, std::vector, return std::make_tuple(std::move(positions), std::move(texcoords), std::move(indices), rectangle); } -std::tuple AbstractTextRenderer::render(AbstractFont& font, Float size, const std::string& text, Buffer* vertexBuffer, Buffer* indexBuffer, Buffer::Usage usage) { - AbstractLayouter* const layouter = font.layout(size, text); +std::tuple AbstractTextRenderer::render(AbstractFont* const font, const GlyphCache* const cache, Float size, const std::string& text, Buffer* vertexBuffer, Buffer* indexBuffer, Buffer::Usage usage) { + AbstractLayouter* const layouter = font->layout(cache, size, text); const UnsignedInt vertexCount = layouter->glyphCount()*4; const UnsignedInt indexCount = layouter->glyphCount()*6; @@ -174,9 +174,9 @@ std::tuple AbstractTextRenderer::render(AbstractFont& font, Flo return std::make_tuple(std::move(mesh), rectangle); } -template std::tuple TextRenderer::render(AbstractFont& font, Float size, const std::string& text, Buffer* vertexBuffer, Buffer* indexBuffer, Buffer::Usage usage) { +template std::tuple TextRenderer::render(AbstractFont* const font, const GlyphCache* const cache, Float size, const std::string& text, Buffer* vertexBuffer, Buffer* indexBuffer, Buffer::Usage usage) { /* Finalize mesh configuration and return the result */ - auto r = AbstractTextRenderer::render(font, size, text, vertexBuffer, indexBuffer, usage); + auto r = AbstractTextRenderer::render(font, cache, size, text, vertexBuffer, indexBuffer, usage); Mesh& mesh = std::get<0>(r); mesh.addInterleavedVertexBuffer(vertexBuffer, 0, typename Shaders::AbstractVectorShader::Position( @@ -185,7 +185,7 @@ template std::tuple TextRenderer TextRenderer::TextRenderer(AbstractFont& font, const Float size): AbstractTextRenderer(font, size) { +template TextRenderer::TextRenderer(AbstractFont* const font, const GlyphCache* const cache, const Float size): AbstractTextRenderer(font, cache, size) { /* Finalize mesh configuration */ _mesh.addInterleavedVertexBuffer(&vertexBuffer, 0, typename Shaders::AbstractVectorShader::Position(Shaders::AbstractVectorShader::Position::Components::Two), @@ -246,7 +246,7 @@ void AbstractTextRenderer::reserve(const uint32_t glyphCount, const Buffer::Usag } void AbstractTextRenderer::render(const std::string& text) { - AbstractLayouter* layouter = font.layout(size, text); + AbstractLayouter* layouter = font->layout(cache, size, text); CORRADE_ASSERT(layouter->glyphCount() <= _capacity, "Text::TextRenderer::render(): capacity" << _capacity << "too small to render" << layouter->glyphCount() << "glyphs", ); diff --git a/src/Text/TextRenderer.h b/src/Text/TextRenderer.h index bb38b5d89..2d722f681 100644 --- a/src/Text/TextRenderer.h +++ b/src/Text/TextRenderer.h @@ -52,21 +52,23 @@ class MAGNUM_TEXT_EXPORT AbstractTextRenderer { public: /** * @brief Render text - * @param font %Font to use - * @param size %Font size - * @param text %Text to render + * @param font Font + * @param cache Glyph cache + * @param size Font size + * @param text Text to render * * Returns tuple with vertex positions, texture coordinates, indices * and rectangle spanning the rendered text. */ - static std::tuple, std::vector, std::vector, Rectangle> render(AbstractFont& font, Float size, const std::string& text); + static std::tuple, std::vector, std::vector, Rectangle> render(AbstractFont* const font, const GlyphCache* const cache, Float size, const std::string& text); /** * @brief Constructor - * @param font %Font to use - * @param size %Font size + * @param font Font + * @param cache Glyph cache + * @param size Font size */ - explicit AbstractTextRenderer(AbstractFont& font, Float size); + explicit AbstractTextRenderer(AbstractFont* const font, const GlyphCache* const cache, Float size); virtual ~AbstractTextRenderer() = 0; @@ -115,13 +117,14 @@ class MAGNUM_TEXT_EXPORT AbstractTextRenderer { #else private: #endif - static std::tuple MAGNUM_LOCAL render(AbstractFont& font, Float size, const std::string& text, Buffer* vertexBuffer, Buffer* indexBuffer, Buffer::Usage usage); + static std::tuple MAGNUM_LOCAL render(AbstractFont* const font, const GlyphCache* const cache, Float size, const std::string& text, Buffer* vertexBuffer, Buffer* indexBuffer, Buffer::Usage usage); Mesh _mesh; Buffer vertexBuffer, indexBuffer; private: - AbstractFont& font; + AbstractFont* const font; + const GlyphCache* const cache; Float size; UnsignedInt _capacity; Rectangle _rectangle; @@ -140,46 +143,49 @@ methods, returning result either as data arrays or as fully configured mesh. The text can be then drawn by configuring text shader, binding font texture and drawing the mesh: @code -Text::Font font; -Shaders::VectorShader2D shader; -Buffer vertexBuffer, indexBuffer; +Text::AbstractFont* font; +Text::GlyphCache* cache; + +Shaders::VectorShader2D* shader; +Buffer *vertexBuffer, *indexBuffer; Mesh mesh; // Render the text Rectangle rectangle; -std::tie(mesh, rectangle) = Text::TextRenderer2D::render(font, 0.15f, - "Hello World!", &vertexBuffer, &indexBuffer, Buffer::Usage::StaticDraw); +std::tie(mesh, rectangle) = Text::TextRenderer2D::render(font, cache, 0.15f, + "Hello World!", vertexBuffer, indexBuffer, Buffer::Usage::StaticDraw); // Draw white text centered on the screen -shader.setTransformationProjectionMatrix(projection*Matrix3::translation(-rectangle.width()/2.0f)) +shader->setTransformationProjectionMatrix(projection*Matrix3::translation(-rectangle.width()/2.0f)) ->setColor(Color3<>(1.0f)); ->use(); -font.texture()->bind(Shaders::VectorShader2D::FontTextureLayer); +glyphCache->texture()->bind(Shaders::VectorShader2D::FontTextureLayer); mesh.draw(); @endcode -See render(Font&, Float, const std::string&) and -render(Font&, Float, const std::string&, Buffer*, Buffer*, Buffer::Usage) +See render(Font* const, const GlyphCache* const, Float, const std::string&) and +render(Font* const, const GlyphCache* const, Float, const std::string&, Buffer*, Buffer*, Buffer::Usage) for more information. While this method is sufficient for one-shot rendering of static texts, for mutable texts (e.g. FPS counters, chat messages) there is another approach that doesn't recreate everything on each text change: @code -Text::Font font; -Shaders::VectorShader2D shader; +Text::AbstractFont* font; +Text::GlyphCache* cache; +Shaders::VectorShader2D* shader; // Initialize renderer and reserve memory for enough glyphs -Text::TextRenderer2D renderer(font, 0.15f); +Text::TextRenderer2D renderer(font, cache, 0.15f); renderer.reserve(32, Buffer::Usage::DynamicDraw, Buffer::Usage::StaticDraw); // Update the text occasionally renderer.render("Hello World Countdown: 10"); // Draw the text centered on the screen -shader.setTransformationProjectionMatrix(projection*Matrix3::translation(-renderer.rectangle().width()/2.0f)) +shader->setTransformationProjectionMatrix(projection*Matrix3::translation(-renderer.rectangle().width()/2.0f)) ->setColor(Color3<>(1.0f)); ->use(); -font.texture()->bind(Shaders::VectorShader2D::FontTextureLayer); +glyphCache->texture()->bind(Shaders::VectorShader2D::FontTextureLayer); renderer.mesh().draw(); @endcode @@ -195,8 +201,9 @@ template class MAGNUM_TEXT_EXPORT TextRenderer: public A public: /** * @brief Render text - * @param font %Font to use - * @param size %Font size + * @param font Font + * @param cache Glyph cache + * @param size Font size * @param text %Text to render * @param vertexBuffer %Buffer where to store vertices * @param indexBuffer %Buffer where to store indices @@ -205,14 +212,15 @@ template class MAGNUM_TEXT_EXPORT TextRenderer: public A * Returns mesh prepared for use with Shaders::AbstractVectorShader * subclasses and rectangle spanning the rendered text. */ - static std::tuple render(AbstractFont& font, Float size, const std::string& text, Buffer* vertexBuffer, Buffer* indexBuffer, Buffer::Usage usage); + static std::tuple render(AbstractFont* const font, const GlyphCache* const cache, Float size, const std::string& text, Buffer* vertexBuffer, Buffer* indexBuffer, Buffer::Usage usage); /** * @brief Constructor - * @param font %Font to use - * @param size %Font size + * @param font Font + * @param cache Glyph cache + * @param size Font size */ - explicit TextRenderer(AbstractFont& font, Float size); + explicit TextRenderer(AbstractFont* const font, const GlyphCache* const cache, Float size); using AbstractTextRenderer::render; };