Browse Source

Text: moved glyph caching into separate classes.

The API is similar for both basic and distance field caching.
pull/278/head
Vladimír Vondruš 13 years ago
parent
commit
cc19e78983
  1. 28
      src/Text/AbstractFont.h
  2. 4
      src/Text/CMakeLists.txt
  3. 66
      src/Text/DistanceFieldGlyphCache.cpp
  4. 92
      src/Text/DistanceFieldGlyphCache.h
  5. 103
      src/Text/FreeTypeFont.cpp
  6. 85
      src/Text/FreeTypeFont.h
  7. 91
      src/Text/GlyphCache.cpp
  8. 157
      src/Text/GlyphCache.h
  9. 21
      src/Text/HarfBuzzFont.cpp
  10. 16
      src/Text/HarfBuzzFont.h
  11. 2
      src/Text/Text.h
  12. 18
      src/Text/TextRenderer.cpp
  13. 66
      src/Text/TextRenderer.h

28
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;
};
/**

4
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

66
src/Text/DistanceFieldGlyphCache.cpp

@ -0,0 +1,66 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013 Vladimír Vondruš <mosra@centrum.cz>
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);
}
}}

92
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š <mosra@centrum.cz>
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

103
src/Text/FreeTypeFont.cpp

@ -23,6 +23,7 @@
*/
#include "FreeTypeFont.h"
#include "GlyphCache.h"
#include <algorithm>
#include <ft2build.h>
@ -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<Rectangle, Rectangle, Vector2> renderGlyph(const Vector2& cursorPosition, const UnsignedInt i) override;
private:
FreeTypeFont& font;
std::vector<FT_UInt> glyphs;
const GlyphCache* const cache;
const Float size;
std::vector<FT_UInt> 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<Vector2i> 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<Rectanglei> charPositions = TextureTools::atlas(atlasSize, charSizes, padding);
const std::vector<Rectanglei> 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<Rectangle, Rectangle>& 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<Rectangle, Rectangle, Vector2> 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);

85
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<Rectangle, Rectangle>& 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<char32_t, std::tuple<Rectangle, Rectangle>> glyphs;
Float _size;
};

91
src/Text/GlyphCache.cpp

@ -0,0 +1,91 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013 Vladimír Vondruš <mosra@centrum.cz>
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<Rectanglei> GlyphCache::reserve(const std::vector<Vector2i>& 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);
}
}}

157
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š <mosra@centrum.cz>
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 <unordered_map>
#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<Vector2i, Rectanglei> 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<Rectanglei> reserve(const std::vector<Vector2i>& 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<UnsignedInt, std::pair<Vector2i, Rectanglei>> glyphs;
};
}}
#endif

21
src/Text/HarfBuzzFont.cpp

@ -26,19 +26,22 @@
#include <hb-ft.h>
#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<Rectangle, Rectangle, Vector2> 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<Rectangle, Rectangle, Vector2> 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,

16
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();

2
src/Text/Text.h

@ -36,6 +36,8 @@ namespace Magnum { namespace Text {
class AbstractFont;
class AbstractLayouter;
class DistanceFieldGlyphCache;
class GlyphCache;
class FreeTypeFontRenderer;
class FreeTypeFont;

18
src/Text/TextRenderer.cpp

@ -60,8 +60,8 @@ struct Vertex {
}
std::tuple<std::vector<Vector2>, std::vector<Vector2>, std::vector<UnsignedInt>, Rectangle> AbstractTextRenderer::render(AbstractFont& font, Float size, const std::string& text) {
AbstractLayouter* const layouter = font.layout(size, text);
std::tuple<std::vector<Vector2>, std::vector<Vector2>, std::vector<UnsignedInt>, 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<Vector2>, std::vector<Vector2>, std::vector<UnsignedInt>,
return std::make_tuple(std::move(positions), std::move(texcoords), std::move(indices), rectangle);
}
std::tuple<Mesh, Rectangle> 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<Mesh, Rectangle> 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<Mesh, Rectangle> AbstractTextRenderer::render(AbstractFont& font, Flo
return std::make_tuple(std::move(mesh), rectangle);
}
template<UnsignedInt dimensions> std::tuple<Mesh, Rectangle> TextRenderer<dimensions>::render(AbstractFont& font, Float size, const std::string& text, Buffer* vertexBuffer, Buffer* indexBuffer, Buffer::Usage usage) {
template<UnsignedInt dimensions> std::tuple<Mesh, Rectangle> TextRenderer<dimensions>::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<dimensions>::Position(
@ -185,7 +185,7 @@ template<UnsignedInt dimensions> std::tuple<Mesh, Rectangle> TextRenderer<dimens
return std::move(r);
}
AbstractTextRenderer::AbstractTextRenderer(AbstractFont& font, Float size): vertexBuffer(Buffer::Target::Array), indexBuffer(Buffer::Target::ElementArray), font(font), size(size), _capacity(0) {
AbstractTextRenderer::AbstractTextRenderer(AbstractFont* const font, const GlyphCache* const cache, Float size): vertexBuffer(Buffer::Target::Array), indexBuffer(Buffer::Target::ElementArray), font(font), cache(cache), size(size), _capacity(0) {
#ifndef MAGNUM_TARGET_GLES
MAGNUM_ASSERT_EXTENSION_SUPPORTED(Extensions::GL::ARB::map_buffer_range);
#else
@ -200,7 +200,7 @@ AbstractTextRenderer::AbstractTextRenderer(AbstractFont& font, Float size): vert
AbstractTextRenderer::~AbstractTextRenderer() {}
template<UnsignedInt dimensions> TextRenderer<dimensions>::TextRenderer(AbstractFont& font, const Float size): AbstractTextRenderer(font, size) {
template<UnsignedInt dimensions> TextRenderer<dimensions>::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<dimensions>::Position(Shaders::AbstractVectorShader<dimensions>::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", );

66
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<Vector2>, std::vector<Vector2>, std::vector<UnsignedInt>, Rectangle> render(AbstractFont& font, Float size, const std::string& text);
static std::tuple<std::vector<Vector2>, std::vector<Vector2>, std::vector<UnsignedInt>, 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<Mesh, Rectangle> MAGNUM_LOCAL render(AbstractFont& font, Float size, const std::string& text, Buffer* vertexBuffer, Buffer* indexBuffer, Buffer::Usage usage);
static std::tuple<Mesh, Rectangle> 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<UnsignedInt dimensions> 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<UnsignedInt dimensions> 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<Mesh, Rectangle> render(AbstractFont& font, Float size, const std::string& text, Buffer* vertexBuffer, Buffer* indexBuffer, Buffer::Usage usage);
static std::tuple<Mesh, Rectangle> 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;
};

Loading…
Cancel
Save