/* 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 "Mesh.h" #include "Swizzle.h" #include "Shaders/AbstractTextShader.h" #include "Text/Font.h" namespace Magnum { namespace Text { namespace { class TextLayouter { public: TextLayouter(Font& font, const GLfloat size, const std::string& text); ~TextLayouter(); inline std::uint32_t glyphCount() { return _glyphCount; } std::tuple renderGlyph(const Vector2& cursorPosition, const std::uint32_t i); private: const Font& font; const GLfloat size; hb_buffer_t* buffer; hb_glyph_info_t* glyphInfo; hb_glyph_position_t* glyphPositions; std::uint32_t _glyphCount; }; TextLayouter::TextLayouter(Font& font, const GLfloat size, const std::string& text): font(font), size(size) { /* Prepare HarfBuzz buffer */ 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); glyphInfo = hb_buffer_get_glyph_infos(buffer, &_glyphCount); glyphPositions = hb_buffer_get_glyph_positions(buffer, &_glyphCount); } TextLayouter::~TextLayouter() { /* Destroy HarfBuzz buffer */ hb_buffer_destroy(buffer); } std::tuple TextLayouter::renderGlyph(const Vector2& cursorPosition, const std::uint32_t 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); return std::make_tuple(quadPosition, textureCoordinates, advance); } template void createIndices(void* output, const std::uint32_t glyphCount) { T* const out = reinterpret_cast(output); for(std::uint32_t i = 0; i != glyphCount; ++i) { /* 0---2 2 | / /| | / / | |/ / | 1 1---3 */ const T vertex = i*4; const T pos = i*6; out[pos] = vertex; out[pos+1] = vertex+1; out[pos+2] = vertex+2; out[pos+3] = vertex+1; out[pos+4] = vertex+3; out[pos+5] = vertex+2; } } 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 struct Vertex { typename DimensionTraits::PointType position; Vector2 texcoords; }; } template std::tuple::PointType>, std::vector, std::vector, Rectangle> TextRenderer::render(Font& font, GLfloat size, const std::string& text) { TextLayouter layouter(font, size, text); const std::uint32_t vertexCount = layouter.glyphCount()*4; /* Output data */ std::vector::PointType> positions; std::vector texcoords; positions.reserve(vertexCount); texcoords.reserve(vertexCount); /* Render all glyphs */ Vector2 cursorPosition; for(std::uint32_t i = 0; i != layouter.glyphCount(); ++i) { /* Position of the texture in the resulting glyph, texture coordinates */ Rectangle quadPosition, textureCoordinates; Vector2 advance; std::tie(quadPosition, textureCoordinates, advance) = layouter.renderGlyph(cursorPosition, i); positions.insert(positions.end(), { point(quadPosition.topLeft()), point(quadPosition.bottomLeft()), point(quadPosition.topRight()), point(quadPosition.bottomRight()), }); texcoords.insert(texcoords.end(), { textureCoordinates.topLeft(), textureCoordinates.bottomLeft(), textureCoordinates.topRight(), textureCoordinates.bottomRight() }); /* Advance cursor position to next character */ cursorPosition += advance; } /* Create indices */ std::vector indices(layouter.glyphCount()*6); createIndices(indices.data(), layouter.glyphCount()); /* Rendered rectangle */ Rectangle rectangle; if(layouter.glyphCount()) rectangle = {positions[1].xy(), positions[positions.size()-2].xy()}; return std::make_tuple(std::move(positions), std::move(texcoords), std::move(indices), rectangle); } template std::tuple TextRenderer::render(Font& font, GLfloat size, const std::string& text, Buffer* vertexBuffer, Buffer* indexBuffer, Buffer::Usage usage) { TextLayouter layouter(font, size, text); const std::uint32_t vertexCount = layouter.glyphCount()*4; const std::uint32_t indexCount = layouter.glyphCount()*6; /* Vertex buffer */ std::vector> vertices; vertices.reserve(vertexCount); /* Render all glyphs */ Vector2 cursorPosition; for(std::uint32_t i = 0; i != layouter.glyphCount(); ++i) { /* Position of the texture in the resulting glyph, texture coordinates */ Rectangle quadPosition, textureCoordinates; Vector2 advance; std::tie(quadPosition, textureCoordinates, advance) = layouter.renderGlyph(cursorPosition, i); vertices.insert(vertices.end(), { {point(quadPosition.topLeft()), textureCoordinates.topLeft()}, {point(quadPosition.bottomLeft()), textureCoordinates.bottomLeft()}, {point(quadPosition.topRight()), textureCoordinates.topRight()}, {point(quadPosition.bottomRight()), textureCoordinates.bottomRight()} }); /* Advance cursor position to next character */ cursorPosition += advance; } vertexBuffer->setData(vertices, usage); /* Fill index buffer */ Mesh::IndexType indexType; std::size_t indicesSize; char* indices; if(vertexCount < 255) { indexType = Mesh::IndexType::UnsignedByte; indicesSize = indexCount*sizeof(GLushort); indices = new char[indicesSize]; createIndices(indices, layouter.glyphCount()); } else if(vertexCount < 65535) { indexType = Mesh::IndexType::UnsignedShort; indicesSize = indexCount*sizeof(GLushort); indices = new char[indicesSize]; createIndices(indices, layouter.glyphCount()); } else { indexType = Mesh::IndexType::UnsignedInt; indicesSize = indexCount*sizeof(GLuint); indices = new char[indicesSize]; createIndices(indices, layouter.glyphCount()); } indexBuffer->setData(indicesSize, indices, usage); delete indices; /* Rendered rectangle */ Rectangle rectangle; if(layouter.glyphCount()) rectangle = {vertices[1].position.xy(), vertices[vertices.size()-2].position.xy()}; /* Configure mesh */ Mesh mesh; mesh.setPrimitive(Mesh::Primitive::Triangles) ->setIndexCount(indexCount) ->addInterleavedVertexBuffer(vertexBuffer, 0, typename Shaders::AbstractTextShader::Position(), typename Shaders::AbstractTextShader::TextureCoordinates()) ->setIndexBuffer(indexBuffer, 0, indexType, 0, vertexCount); return std::make_tuple(std::move(mesh), rectangle); } template class TextRenderer<2>; template class TextRenderer<3>; }}