diff --git a/doc/compilation-speedup.dox b/doc/compilation-speedup.dox index ca42d8774..bfd68db8a 100644 --- a/doc/compilation-speedup.dox +++ b/doc/compilation-speedup.dox @@ -24,6 +24,7 @@ available, each namespace has its own: - Physics/Physics.h - SceneGraph/SceneGraph.h - Shaders/Shaders.h + - Text/Text.h @section compilation-speedup-templates Templates diff --git a/src/Text/CMakeLists.txt b/src/Text/CMakeLists.txt index e69de29bb..2bdebfb92 100644 --- a/src/Text/CMakeLists.txt +++ b/src/Text/CMakeLists.txt @@ -0,0 +1,23 @@ +find_package(Freetype REQUIRED) +find_package(HarfBuzz REQUIRED) + +include_directories(${FREETYPE_INCLUDE_DIRS} ${HARFBUZZ_INCLUDE_DIRS}) + +set(MagnumText_SRCS + Font.cpp + FontRenderer.cpp + TextRenderer.cpp) +set(MagnumText_HEADERS + Font.h + FontRenderer.h + Text.h + TextRenderer.h + + magnumTextVisibility.h) + +add_library(MagnumText SHARED ${MagnumText_SRCS}) + +target_link_libraries(MagnumText Magnum MagnumMeshTools MagnumTextureTools ${FREETYPE_LIBRARIES} ${HARFBUZZ_LIBRARIES}) + +install(TARGETS MagnumText DESTINATION ${MAGNUM_LIBRARY_INSTALL_DIR}) +install(FILES ${MagnumText_HEADERS} DESTINATION ${MAGNUM_INCLUDE_INSTALL_DIR}/Text) diff --git a/src/Text/Font.cpp b/src/Text/Font.cpp new file mode 100644 index 000000000..2161f4be7 --- /dev/null +++ b/src/Text/Font.cpp @@ -0,0 +1,145 @@ +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +#include "Font.h" + +#include +#include FT_FREETYPE_H +#include + +#include "Image.h" +#include "TextureTools/Atlas.h" + +namespace Magnum { namespace Text { + +Font::Font(FontRenderer& renderer, const std::string& fontFile, GLfloat size): _size(size) { + /** @todo Use delegating constructor when GCC 4.6 support is dropped */ + create(renderer, fontFile); +} + +Font::Font(FontRenderer& renderer, const std::string& fontFile, GLfloat size, const std::string& characters, const Vector2i& atlasSize): _size(size) { + /** @todo Use delegating constructor when GCC 4.6 support is dropped */ + create(renderer, fontFile); + + /** @bug Crash when atlas is too small */ + /** @todo Get rid of duplicate characters */ + + /* Get character codes from string */ + /** @todo proper UTF-8 decoding */ + std::vector charCodes(characters.begin(), characters.end()); + + /* Get character indices */ + std::vector charIndices; + charIndices.push_back(0); + for(std::uint32_t charCode: charCodes) + charIndices.push_back(FT_Get_Char_Index(_ftFont, charCode)); + + /* Sizes of all characters */ + std::vector charSizes; + charSizes.reserve(charIndices.size()); + for(FT_UInt c: charIndices) { + CORRADE_INTERNAL_ASSERT(FT_Load_Glyph(_ftFont, c, FT_LOAD_DEFAULT) == 0); + charSizes.push_back((Vector2i(_ftFont->glyph->metrics.width, _ftFont->glyph->metrics.height))/64); + } + + /* Create texture atlas */ + std::vector charPositions = TextureTools::atlas(atlasSize, 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); + for(std::size_t i = 0; i != charPositions.size(); ++i) { + /* Load and render glyph */ + /** @todo B&W only */ + FT_GlyphSlot glyph = _ftFont->glyph; + CORRADE_INTERNAL_ASSERT(FT_Load_Glyph(_ftFont, charIndices[i], FT_LOAD_DEFAULT) == 0); + CORRADE_INTERNAL_ASSERT(FT_Render_Glyph(glyph, FT_RENDER_MODE_NORMAL) == 0); + + /* Copy rendered bitmap to texture image */ + const FT_Bitmap& bitmap = glyph->bitmap; + CORRADE_INTERNAL_ASSERT(std::abs(bitmap.width-charPositions[i].width()) <= 2); + CORRADE_INTERNAL_ASSERT(std::abs(bitmap.rows-charPositions[i].height()) <= 2); + for(std::int32_t yin = 0, yout = charPositions[i].bottom(), ymax = bitmap.rows; yin != ymax; ++yin, ++yout) + for(std::int32_t 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 */ + glyphs.insert({charIndices[i], std::make_tuple( + Rectangle::fromSize(Vector2(glyph->bitmap_left, glyph->bitmap_top-charPositions[i].height())/size, + Vector2::from(charPositions[i].size())/size), + Rectangle(Vector2::from(charPositions[i].bottomLeft())/atlasSize, + Vector2::from(charPositions[i].topRight())/atlasSize) + )}); + } + + /* Set texture data */ + _texture.setData(0, Texture2D::InternalFormat::R8, &image); +} + +void Font::create(FontRenderer& renderer, const std::string& fontFile) { + /* Create FreeType font */ + CORRADE_INTERNAL_ASSERT(FT_New_Face(renderer.library(), fontFile.c_str(), 0, &_ftFont) == 0); + CORRADE_INTERNAL_ASSERT(FT_Set_Char_Size(_ftFont, 0, _size*64, 100, 100) == 0); + + /* Create Harfbuzz font */ + _hbFont = hb_ft_font_create(_ftFont, nullptr); + + /* Set up the texture */ + _texture.setWrapping(Texture2D::Wrapping::ClampToEdge) + ->setMinificationFilter(Texture2D::Filter::LinearInterpolation) + ->setMagnificationFilter(Texture2D::Filter::LinearInterpolation); +} + +void Font::destroy() { + if(!_ftFont) return; + + hb_font_destroy(_hbFont); + FT_Done_Face(_ftFont); +} + +void Font::move() { + _hbFont = nullptr; + _ftFont = nullptr; +} + +Font::~Font() { destroy(); } + +Font::Font(Font&& other): glyphs(std::move(other.glyphs)), _texture(std::move(other._texture)), _ftFont(other._ftFont), _hbFont(other._hbFont), _size(other._size) { + other.move(); +} + +Font& Font::operator=(Font&& other) { + destroy(); + + glyphs = std::move(other.glyphs); + _texture = std::move(other._texture); + _ftFont = other._ftFont; + _hbFont = other._hbFont; + _size = other._size; + + other.move(); + return *this; +} + +const std::tuple& Font::operator[](std::uint32_t character) const { + auto it = glyphs.find(character); + + if(it == glyphs.end()) + return glyphs.at(0); + return it->second; +} + +}} diff --git a/src/Text/Font.h b/src/Text/Font.h new file mode 100644 index 000000000..326a58ca6 --- /dev/null +++ b/src/Text/Font.h @@ -0,0 +1,111 @@ +#ifndef Magnum_Text_Font_h +#define Magnum_Text_Font_h +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +/** @file + * @brief Class Magnum::Text::Font + */ + +#include + +#include "Math/Geometry/Rectangle.h" +#include "Texture.h" +#include "Text/FontRenderer.h" + +#include "magnumTextVisibility.h" + +#ifndef DOXYGEN_GENERATING_OUTPUT +struct FT_FaceRec_; +typedef FT_FaceRec_* FT_Face; +struct hb_font_t; +#endif + +namespace Magnum { namespace Text { + +/** +@brief %Font + +Contains font with characters prerendered into texture atlas. +*/ +class MAGNUM_TEXT_EXPORT Font { + Font(const Font&) = delete; + Font& operator=(const Font&) = delete; + + public: + /** + * @brief Empty font constructor + * @param renderer %Font renderer + * @param fontFile %Font file + * @param size %Font size + * + * Creates font with no prerendered characters. + */ + Font(FontRenderer& renderer, const std::string& fontFile, GLfloat size); + + /** + * @brief Create font for given character set + * @param renderer %Font renderer + * @param fontFile %Font file + * @param size %Font size + * @param characters Characters to render + * @param atlasSize Size of resulting atlas + */ + Font(FontRenderer& renderer, const std::string& fontFile, GLfloat size, const std::string& characters, const Vector2i& atlasSize); + + ~Font(); + + /** @brief Move constructor */ + Font(Font&& other); + + /** @brief Move assignment */ + Font& operator=(Font&& other); + + /** @brief %Font size */ + inline GLfloat 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[](std::uint32_t character) const; + + /** @brief %Font texture atlas */ + inline Texture2D& texture() { return _texture; } + + /** @brief HarfBuzz font handle */ + inline hb_font_t* font() { return _hbFont; } + + private: + void MAGNUM_TEXT_LOCAL create(FontRenderer& renderer, const std::string& fontFile); + void MAGNUM_TEXT_LOCAL destroy(); + void MAGNUM_TEXT_LOCAL move(); + + std::unordered_map> glyphs; + Texture2D _texture; + FT_Face _ftFont; + hb_font_t* _hbFont; + GLfloat _size; +}; + +}} + +#endif diff --git a/src/Text/FontRenderer.cpp b/src/Text/FontRenderer.cpp new file mode 100644 index 000000000..94c5fde25 --- /dev/null +++ b/src/Text/FontRenderer.cpp @@ -0,0 +1,32 @@ +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +#include "FontRenderer.h" + +#include +#include FT_FREETYPE_H +#include + +namespace Magnum { namespace Text { + +FontRenderer::FontRenderer() { + CORRADE_INTERNAL_ASSERT(FT_Init_FreeType(&_library) == 0); +} + +FontRenderer::~FontRenderer() { + FT_Done_FreeType(_library); +} + +}} diff --git a/src/Text/FontRenderer.h b/src/Text/FontRenderer.h new file mode 100644 index 000000000..0164edf32 --- /dev/null +++ b/src/Text/FontRenderer.h @@ -0,0 +1,51 @@ +#ifndef Magnum_Text_FontRenderer_h +#define Magnum_Text_FontRenderer_h +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +/** @file + * @brief Class Magnum::Text::FontRenderer + */ + +#include "magnumTextVisibility.h" + +#ifndef DOXYGEN_GENERATING_OUTPUT +struct FT_LibraryRec_; +typedef FT_LibraryRec_* FT_Library; +#endif + +namespace Magnum { namespace Text { + +/** +@brief Font renderer + +Contains global instance of font renderer used by Font class. +*/ +class MAGNUM_TEXT_EXPORT FontRenderer { + public: + explicit FontRenderer(); + + ~FontRenderer(); + + /** @brief FreeType library handle */ + inline FT_Library library() { return _library; } + + private: + FT_Library _library; +}; + +}} + +#endif diff --git a/src/Text/Text.h b/src/Text/Text.h new file mode 100644 index 000000000..982b73d75 --- /dev/null +++ b/src/Text/Text.h @@ -0,0 +1,32 @@ +#ifndef Magnum_Text_Text_h +#define Magnum_Text_Text_h +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +/** @file + * @brief Forward declarations for Magnum::Text namespace + */ + +#include + +namespace Magnum { namespace Text { + +class Font; +class FontRenderer; +/* TextRenderer used only statically */ + +}} + +#endif diff --git a/src/Text/TextRenderer.cpp b/src/Text/TextRenderer.cpp new file mode 100644 index 000000000..053cb5845 --- /dev/null +++ b/src/Text/TextRenderer.cpp @@ -0,0 +1,161 @@ +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +#include "TextRenderer.h" + +#include +#include + +#include "Math/Point2D.h" +#include "Math/Point3D.h" +#include "IndexedMesh.h" +#include "Swizzle.h" +#include "MeshTools/CompressIndices.h" +#include "MeshTools/Interleave.h" +#include "Shaders/AbstractTextShader.h" +#include "Text/Font.h" + +namespace Magnum { namespace Text { + +namespace { + +std::tuple, std::vector, std::vector> renderInternal(Font& font, GLfloat size, const std::string& text) { + /* Prepare HarfBuzz buffer */ + hb_buffer_t *buffer = hb_buffer_create(); + hb_buffer_set_unicode_funcs(buffer, hb_icu_get_unicode_funcs()); + hb_buffer_set_direction(buffer, HB_DIRECTION_LTR); + hb_buffer_set_script(buffer, HB_SCRIPT_LATIN); + hb_buffer_set_language(buffer, hb_language_from_string("en", 2)); + + /* Layout the text */ + hb_buffer_add_utf8(buffer, text.c_str(), -1, 0, -1); + hb_shape(font.font(), buffer, nullptr, 0); + + std::uint32_t glyphCount; + hb_glyph_info_t* glyphInfo = hb_buffer_get_glyph_infos(buffer, &glyphCount); + hb_glyph_position_t* glyphPositions = hb_buffer_get_glyph_positions(buffer, &glyphCount); + + /* Output data */ + std::vector positions, texcoords; + std::vector indices; + positions.reserve(glyphCount*4); + texcoords.reserve(glyphCount*4); + indices.reserve(glyphCount*6); + + /* Starting cursor position */ + Vector2 cursorPosition; + + /* Create quads for all glyphs */ + for(std::uint32_t i = 0; i != glyphCount; ++i) { + /* Position of the texture in the resulting glyph, texture coordinates */ + Rectangle texturePosition, textureCoordinates; + std::tie(texturePosition, textureCoordinates) = font[glyphInfo[i].codepoint]; + + /* Glyph offset and advance to next glyph in normalized coordinates */ + Vector2 offset = Vector2(glyphPositions[i].x_offset, + glyphPositions[i].y_offset)/(64*font.size()); + Vector2 advance = Vector2(glyphPositions[i].x_advance, + glyphPositions[i].y_advance)/(64*font.size()); + + /* Absolute quad position, composed from cursor position, glyph offset + and texture position, denormalized to requested text size */ + Rectangle quadPosition = Rectangle::fromSize( + (cursorPosition + offset + Vector2(texturePosition.left(), texturePosition.bottom()))*size, + texturePosition.size()*size); + + /* 0---2 2 + | / /| + | / / | + |/ / | + 1 1---3 */ + + /* Quad with texture coordinates */ + indices.insert(indices.end(), { + std::uint32_t(positions.size()), + std::uint32_t(positions.size()+1), + std::uint32_t(positions.size()+2), + std::uint32_t(positions.size()+1), + std::uint32_t(positions.size()+3), + std::uint32_t(positions.size()+2) + }); + positions.insert(positions.end(), { + quadPosition.topLeft(), + quadPosition.bottomLeft(), + quadPosition.topRight(), + quadPosition.bottomRight(), + }); + texcoords.insert(texcoords.end(), { + textureCoordinates.topLeft(), + textureCoordinates.bottomLeft(), + textureCoordinates.topRight(), + textureCoordinates.bottomRight() + }); + + /* Advance cursor position to next character */ + cursorPosition += advance; + } + + /* Destroy HarfBuzz buffer */ + hb_buffer_destroy(buffer); + + return std::make_tuple(std::move(positions), std::move(texcoords), std::move(indices)); +} + +template typename DimensionTraits::PointType point(const Vector2& vec); + +template<> inline Point2D point<2>(const Vector2& vec) { + return swizzle<'x', 'y', '1'>(vec); +} + +template<> inline Point3D point<3>(const Vector2& vec) { + return swizzle<'x', 'y', '0', '1'>(vec); +} + +} + +template std::tuple::PointType>, std::vector, std::vector> TextRenderer::render(Font& font, GLfloat size, const std::string& text) { + std::vector positions, textureCoordinates; + std::vector indices; + std::tie(positions, textureCoordinates, indices) = renderInternal(font, size, text); + + /* Create PointXD from Vector2 */ + std::vector::PointType> positionsXD; + positionsXD.reserve(positions.size()); + for(const Vector2& position: positions) + positionsXD.push_back(point(position)); + + return std::make_tuple(std::move(positionsXD), std::move(textureCoordinates), std::move(indices)); +} + +template IndexedMesh TextRenderer::render(Font& font, GLfloat size, const std::string& text, Buffer* vertexBuffer, Buffer* indexBuffer, Buffer::Usage usage) { + IndexedMesh mesh; + + std::vector::PointType> positions; + std::vector textureCoordinates; + std::vector indices; + std::tie(positions, textureCoordinates, indices) = render(font, size, text); + + MeshTools::interleave(&mesh, vertexBuffer, usage, positions, textureCoordinates); + MeshTools::compressIndices(&mesh, indexBuffer, usage, indices); + mesh.setPrimitive(Mesh::Primitive::Triangles) + ->addInterleavedVertexBuffer(vertexBuffer, 0, typename Shaders::AbstractTextShader::Position(), typename Shaders::AbstractTextShader::TextureCoordinates()); + + return mesh; +} + +template class TextRenderer<2>; +template class TextRenderer<3>; + +}} diff --git a/src/Text/TextRenderer.h b/src/Text/TextRenderer.h new file mode 100644 index 000000000..81016d362 --- /dev/null +++ b/src/Text/TextRenderer.h @@ -0,0 +1,84 @@ +#ifndef Magnum_Text_TextRenderer_h +#define Magnum_Text_TextRenderer_h +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +/** @file + * @brief Class Magnum::Text::TextRenderer + */ + +#include "Math/Vector2.h" +#include "Buffer.h" +#include "DimensionTraits.h" +#include "Text/Text.h" + +#include "magnumTextVisibility.h" + +#include +#include +#include + +namespace Magnum { namespace Text { + +/** +@brief %Text renderer + +Lays out the text into mesh using [HarfBuzz](http://www.freedesktop.org/wiki/Software/HarfBuzz) +library. Use of ligatures, kerning etc. depends on features supported by the +particular font. +@see TextRenderer2D, TextRenderer3D, Font, Shaders::AbstractTextShader +*/ +template class MAGNUM_TEXT_EXPORT TextRenderer { + public: + /** + * @brief Render text + * @param font %Font to use + * @param size %Font size + * @param text %Text to render + * @return Tuple with vertex positions, texture coordinates and indices + */ + static std::tuple::PointType>, std::vector, std::vector> render(Font& font, GLfloat size, const std::string& text); + + /** + * @brief Render text + * @param font %Font to use + * @param size %Font size + * @param text %Text to render + * @param vertexBuffer %Buffer where to store vertices + * @param indexBuffer %Buffer where to store indices + * @param usage Usage of vertex and index buffer + * @return Indexed mesh prepared for use with Shaders::AbstractTextShader + * subclasses + */ + static IndexedMesh render(Font& font, GLfloat size, const std::string& text, Buffer* vertexBuffer, Buffer* indexBuffer, Buffer::Usage usage); + + private: + #ifndef DOXYGEN_GENERATING_OUTPUT + struct Vertex { + typename DimensionTraits::PointType position; + Vector2 textureCoordinates; + }; + #endif +}; + +/** @brief Two-dimensional text renderer */ +typedef TextRenderer<2> TextRenderer2D; + +/** @brief Three-dimensional text renderer */ +typedef TextRenderer<3> TextRenderer3D; + +}} + +#endif diff --git a/src/Text/magnumTextVisibility.h b/src/Text/magnumTextVisibility.h new file mode 100644 index 000000000..4d70506c9 --- /dev/null +++ b/src/Text/magnumTextVisibility.h @@ -0,0 +1,30 @@ +#ifndef Magnum_Text_magnumTextVisibility_h +#define Magnum_Text_magnumTextVisibility_h +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +#ifdef _WIN32 + #ifdef MagnumText_EXPORTS + #define MAGNUM_TEXT_EXPORT __declspec(dllexport) + #else + #define MAGNUM_TEXT_EXPORT __declspec(dllimport) + #endif + #define MAGNUM_TEXT_LOCAL +#else + #define MAGNUM_TEXT_EXPORT __attribute__ ((visibility ("default"))) + #define MAGNUM_TEXT_LOCAL __attribute__ ((visibility ("hidden"))) +#endif + +#endif