diff --git a/src/Text/Alignment.h b/src/Text/Alignment.h new file mode 100644 index 000000000..0677e0a1e --- /dev/null +++ b/src/Text/Alignment.h @@ -0,0 +1,131 @@ +#ifndef Magnum_Text_Alignment_h +#define Magnum_Text_Alignment_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 Enum @ref Magnum::Text::Alignment + */ + +#include "Types.h" + +namespace Magnum { namespace Text { + +namespace Implementation { + enum: UnsignedByte { + AlignmentLeft = 1, + AlignmentCenter = 2, + AlignmentRight = 3, + + AlignmentLine = 1 << 3, + AlignmentMiddle = 2 << 3, + AlignmentTop = 3 << 3, + + AlignmentHorizontal = 3, + AlignmentVertical = 3 << 3, + AlignmentIntegral = 1 << 6 + }; +} + +/** +@brief Text rendering alignment + +@see @ref Renderer::render(), @ref Renderer::Renderer() +*/ +enum class Alignment: UnsignedByte { + /** Text start and line is at origin */ + LineLeft = Implementation::AlignmentLine|Implementation::AlignmentLeft, + + /** + * Text center and line is at origin + * + * @see @ref Alignment::LineCenterIntegral + */ + LineCenter = Implementation::AlignmentLine|Implementation::AlignmentCenter, + + /** Text end and line is at origin */ + LineRight = Implementation::AlignmentLine|Implementation::AlignmentRight, + + /** + * Text start and vertical middle is at origin + * + * @see @ref Alignment::MiddleLeftIntegral + */ + MiddleLeft = Implementation::AlignmentMiddle|Implementation::AlignmentLeft, + + /** + * Text center and vertical middle is at origin + * + * @see @ref Alignment::MiddleRightIntegral + */ + MiddleCenter = Implementation::AlignmentMiddle|Implementation::AlignmentCenter, + + /** + * Text end and vertical middle is at origin + * + * @see @ref Alignment::MiddleRightIntegral + */ + MiddleRight = Implementation::AlignmentMiddle|Implementation::AlignmentRight, + + /** Text start and top is at origin */ + TopLeft = Implementation::AlignmentTop|Implementation::AlignmentLeft, + + /** Text center and top is at origin */ + TopCenter = Implementation::AlignmentTop|Implementation::AlignmentCenter, + + /** Text end and top is at origin */ + TopRight = Implementation::AlignmentTop|Implementation::AlignmentRight, + + /** + * Text center and line is at origin and alignment offset is integral + * + * @see @ref Alignment::LineCenter + */ + LineCenterIntegral = Implementation::AlignmentLine|Implementation::AlignmentCenter|Implementation::AlignmentIntegral, + + /** + * Text start and vertical middle is at origin and alignment offset is integral + * + * @see @ref Alignment::MiddleLeft + */ + MiddleLeftIntegral = Implementation::AlignmentMiddle|Implementation::AlignmentLeft|Implementation::AlignmentIntegral, + + /** + * Text center and vertical middle is at origin and alignment offset is integral + * + * @see @ref Alignment::MiddleCenter + */ + MiddleCenterIntegral = Implementation::AlignmentMiddle|Implementation::AlignmentCenter|Implementation::AlignmentIntegral, + + /** + * Text end and vertical middle is at origin and alignment offset is integral + * + * @see @ref Alignment::MiddleRight + */ + MiddleRightIntegral = Implementation::AlignmentMiddle|Implementation::AlignmentRight|Implementation::AlignmentIntegral +}; + +}} + +#endif diff --git a/src/Text/CMakeLists.txt b/src/Text/CMakeLists.txt index 1c31f4bd2..8f61ce1fe 100644 --- a/src/Text/CMakeLists.txt +++ b/src/Text/CMakeLists.txt @@ -31,6 +31,7 @@ set(MagnumText_SRCS set(MagnumText_HEADERS AbstractFont.h AbstractFontConverter.h + Alignment.h DistanceFieldGlyphCache.h GlyphCache.h Renderer.h diff --git a/src/Text/Renderer.cpp b/src/Text/Renderer.cpp index f72c8c224..1ab54576e 100644 --- a/src/Text/Renderer.cpp +++ b/src/Text/Renderer.cpp @@ -57,13 +57,38 @@ template void createIndices(void* output, const UnsignedInt glyphCount) } } +Vector2 alignmentOffset(Rectangle& bounds, Alignment alignment) { + const Vector2 size = bounds.size(); + const auto value = UnsignedByte(alignment); + + /** @todo What about top-down text? */ + + Vector2 offset; + + /* Horizontal alignment */ + if((value & Implementation::AlignmentHorizontal) == Implementation::AlignmentCenter) offset -= Vector2::xAxis(size.x()*0.5f); + else if((value & Implementation::AlignmentHorizontal) == Implementation::AlignmentRight) offset -= Vector2::xAxis(size.x()); + + /* Vertical alignment */ + if((value & Implementation::AlignmentVertical) == Implementation::AlignmentMiddle) offset -= Vector2::yAxis(size.y()*0.5f); + else if((value & Implementation::AlignmentVertical) == Implementation::AlignmentTop) offset -= Vector2::yAxis(size.y()); + + /* Integer alignment */ + if(value & Implementation::AlignmentIntegral) offset = Math::round(offset); + + /* Update also the bounds */ + bounds.bottomLeft() += offset; + bounds.topRight() += offset; + return offset; +} + struct Vertex { Vector2 position, texcoords; }; } -std::tuple, std::vector, std::vector, Rectangle> AbstractRenderer::render(AbstractFont& font, const GlyphCache& cache, Float size, const std::string& text) { +std::tuple, std::vector, std::vector, Rectangle> AbstractRenderer::render(AbstractFont& font, const GlyphCache& cache, Float size, const std::string& text, Alignment alignment) { const auto layouter = font.layout(cache, size, text); const UnsignedInt vertexCount = layouter->glyphCount()*4; @@ -114,6 +139,10 @@ std::tuple, std::vector, std::vector, cursorPosition += advance; } + /* Respect the alignment */ + const Vector2 offset = alignmentOffset(rectangle, alignment); + for(auto& p: positions) p += offset; + /* Create indices */ std::vector indices(layouter->glyphCount()*6); createIndices(indices.data(), layouter->glyphCount()); @@ -121,7 +150,7 @@ std::tuple, std::vector, std::vector, return std::make_tuple(std::move(positions), std::move(texcoords), std::move(indices), rectangle); } -std::tuple AbstractRenderer::render(AbstractFont& font, const GlyphCache& cache, Float size, const std::string& text, Buffer& vertexBuffer, Buffer& indexBuffer, Buffer::Usage usage) { +std::tuple AbstractRenderer::render(AbstractFont& font, const GlyphCache& cache, Float size, const std::string& text, Buffer& vertexBuffer, Buffer& indexBuffer, Buffer::Usage usage, Alignment alignment) { const auto layouter = font.layout(cache, size, text); const UnsignedInt vertexCount = layouter->glyphCount()*4; @@ -160,6 +189,11 @@ std::tuple AbstractRenderer::render(AbstractFont& font, const G /* Advance cursor position to next character */ cursorPosition += advance; } + + /* Respect the alignment */ + const Vector2 offset = alignmentOffset(rectangle, alignment); + for(auto& v: vertices) v.position += offset; + vertexBuffer.setData(vertices, usage); /* Fill index buffer */ @@ -195,9 +229,9 @@ std::tuple AbstractRenderer::render(AbstractFont& font, const G return std::make_tuple(std::move(mesh), rectangle); } -template std::tuple Renderer::render(AbstractFont& font, const GlyphCache& cache, Float size, const std::string& text, Buffer& vertexBuffer, Buffer& indexBuffer, Buffer::Usage usage) { +template std::tuple Renderer::render(AbstractFont& font, const GlyphCache& cache, Float size, const std::string& text, Buffer& vertexBuffer, Buffer& indexBuffer, Buffer::Usage usage, Alignment alignment) { /* Finalize mesh configuration and return the result */ - auto r = AbstractRenderer::render(font, cache, size, text, vertexBuffer, indexBuffer, usage); + auto r = AbstractRenderer::render(font, cache, size, text, vertexBuffer, indexBuffer, usage, alignment); Mesh& mesh = std::get<0>(r); mesh.addVertexBuffer(vertexBuffer, 0, typename Shaders::AbstractVector::Position( @@ -250,7 +284,7 @@ void AbstractRenderer::bufferUnmapImplementationDefault(Buffer& buffer) #endif } -AbstractRenderer::AbstractRenderer(AbstractFont& font, const GlyphCache& cache, Float size): _vertexBuffer(Buffer::Target::Array), _indexBuffer(Buffer::Target::ElementArray), font(font), cache(cache), size(size), _capacity(0) { +AbstractRenderer::AbstractRenderer(AbstractFont& font, const GlyphCache& cache, const Float size, const Alignment alignment): _vertexBuffer(Buffer::Target::Array), _indexBuffer(Buffer::Target::ElementArray), font(font), cache(cache), size(size), _alignment(alignment), _capacity(0) { #ifndef MAGNUM_TARGET_GLES MAGNUM_ASSERT_EXTENSION_SUPPORTED(Extensions::GL::ARB::map_buffer_range); #elif defined(MAGNUM_TARGET_GLES2) && !defined(CORRADE_TARGET_EMSCRIPTEN) @@ -274,7 +308,7 @@ AbstractRenderer::AbstractRenderer(AbstractFont& font, const GlyphCache& cache, AbstractRenderer::~AbstractRenderer() {} -template Renderer::Renderer(AbstractFont& font, const GlyphCache& cache, const Float size): AbstractRenderer(font, cache, size) { +template Renderer::Renderer(AbstractFont& font, const GlyphCache& cache, const Float size, const Alignment alignment): AbstractRenderer(font, cache, size, alignment) { /* Finalize mesh configuration */ _mesh.addVertexBuffer(_vertexBuffer, 0, typename Shaders::AbstractVector::Position(Shaders::AbstractVector::Position::Components::Two), @@ -338,8 +372,8 @@ void AbstractRenderer::render(const std::string& text) { _rectangle = {}; /* Map buffer for rendering */ - Vertex* const vertices = static_cast(bufferMapImplementation(_vertexBuffer, - layouter->glyphCount()*4*sizeof(Vertex))); + Containers::ArrayReference vertices(static_cast(bufferMapImplementation(_vertexBuffer, + layouter->glyphCount()*4*sizeof(Vertex))), layouter->glyphCount()*4); CORRADE_INTERNAL_ASSERT_OUTPUT(vertices); /* Render all glyphs */ @@ -367,6 +401,11 @@ void AbstractRenderer::render(const std::string& text) { /* Advance cursor position to next character */ cursorPosition += advance; } + + /* Respect the alignment */ + const Vector2 offset = alignmentOffset(_rectangle, _alignment); + for(auto& v: vertices) v.position += offset; + bufferUnmapImplementation(_vertexBuffer); /* Update index count */ diff --git a/src/Text/Renderer.h b/src/Text/Renderer.h index b1c5b8a9c..439b42bd2 100644 --- a/src/Text/Renderer.h +++ b/src/Text/Renderer.h @@ -37,6 +37,7 @@ #include "DimensionTraits.h" #include "Mesh.h" #include "Text/Text.h" +#include "Text/Alignment.h" #include "magnumTextVisibility.h" @@ -56,11 +57,12 @@ class MAGNUM_TEXT_EXPORT AbstractRenderer { * @param cache Glyph cache * @param size Font size * @param text Text to render + * @param alignment Text alignment * * 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, const GlyphCache& cache, Float size, const std::string& text); + static std::tuple, std::vector, std::vector, Rectangle> render(AbstractFont& font, const GlyphCache& cache, Float size, const std::string& text, Alignment alignment = Alignment::LineLeft); /** * @brief Capacity for rendered glyphs @@ -114,9 +116,10 @@ class MAGNUM_TEXT_EXPORT AbstractRenderer { * @param font Font * @param cache Glyph cache * @param size Font size + * @param alignment Text alignment */ - explicit AbstractRenderer(AbstractFont& font, const GlyphCache& cache, Float size); - AbstractRenderer(AbstractFont&, GlyphCache&&, Float) = delete; /**< @overload */ + explicit AbstractRenderer(AbstractFont& font, const GlyphCache& cache, Float size, Alignment alignment = Alignment::LineLeft); + AbstractRenderer(AbstractFont&, GlyphCache&&, Float, Alignment alignment = Alignment::LineLeft) = delete; /**< @overload */ ~AbstractRenderer(); @@ -125,7 +128,7 @@ class MAGNUM_TEXT_EXPORT AbstractRenderer { #else private: #endif - static std::tuple MAGNUM_LOCAL render(AbstractFont& font, const GlyphCache& cache, Float size, const std::string& text, Buffer& vertexBuffer, Buffer& indexBuffer, Buffer::Usage usage); + static std::tuple MAGNUM_LOCAL render(AbstractFont& font, const GlyphCache& cache, Float size, const std::string& text, Buffer& vertexBuffer, Buffer& indexBuffer, Buffer::Usage usage, Alignment alignment); Mesh _mesh; Buffer _vertexBuffer, _indexBuffer; @@ -137,6 +140,7 @@ class MAGNUM_TEXT_EXPORT AbstractRenderer { AbstractFont& font; const GlyphCache& cache; Float size; + Alignment _alignment; UnsignedInt _capacity; Rectangle _rectangle; @@ -256,7 +260,7 @@ template class MAGNUM_TEXT_EXPORT Renderer: public Abstr * Returns mesh prepared for use with @ref Shaders::AbstractVector * subclasses and rectangle spanning the rendered text. */ - static std::tuple render(AbstractFont& font, const GlyphCache& cache, Float size, const std::string& text, Buffer& vertexBuffer, Buffer& indexBuffer, Buffer::Usage usage); + static std::tuple render(AbstractFont& font, const GlyphCache& cache, Float size, const std::string& text, Buffer& vertexBuffer, Buffer& indexBuffer, Buffer::Usage usage, Alignment alignment = Alignment::LineLeft); /** * @brief Constructor @@ -264,8 +268,8 @@ template class MAGNUM_TEXT_EXPORT Renderer: public Abstr * @param cache Glyph cache * @param size Font size */ - explicit Renderer(AbstractFont& font, const GlyphCache& cache, Float size); - Renderer(AbstractFont&, GlyphCache&&, Float) = delete; /**< @overload */ + explicit Renderer(AbstractFont& font, const GlyphCache& cache, Float size, Alignment alignment = Alignment::LineLeft); + Renderer(AbstractFont&, GlyphCache&&, Float, Alignment alignment = Alignment::LineLeft) = delete; /**< @overload */ using AbstractRenderer::render; }; diff --git a/src/Text/Test/RendererGLTest.cpp b/src/Text/Test/RendererGLTest.cpp index 467c691f5..ebc47d7d7 100644 --- a/src/Text/Test/RendererGLTest.cpp +++ b/src/Text/Test/RendererGLTest.cpp @@ -86,13 +86,19 @@ void RendererGLTest::renderData() { std::vector textureCoordinates; std::vector indices; Rectangle bounds; - std::tie(positions, textureCoordinates, indices, bounds) = Text::AbstractRenderer::render(font, *static_cast(nullptr), 0.25f, "abc"); + std::tie(positions, textureCoordinates, indices, bounds) = Text::AbstractRenderer::render(font, *static_cast(nullptr), 0.25f, "abc", Alignment::MiddleRightIntegral); /* Three glyphs, three quads -> 12 vertices, 18 indices */ CORRADE_COMPARE(positions.size(), 12); CORRADE_COMPARE(textureCoordinates.size(), 12); CORRADE_COMPARE(indices.size(), 18); + /* Alignment offset */ + const Vector2 offset{-5.0f, -1.0f}; + + /* Bounds */ + CORRADE_COMPARE(bounds, Rectangle(Vector2{0.0f, -0.5f} + offset, Vector2{5.0f, 1.0f} + offset)); + /* Vertex positions and texture coordinates 0---2 | | @@ -107,20 +113,20 @@ void RendererGLTest::renderData() { +-+ | | +---+ */ CORRADE_COMPARE(positions, (std::vector{ - {0.0f, 0.5f}, - {0.0f, 0.0f}, - {0.75f, 0.5f}, - {0.75f, 0.0f}, - - {1.0f, 0.75f}, - {1.0f, -0.25f}, - {2.5f, 0.75f}, - {2.5f, -0.25f}, - - {2.75f, 1.0f}, - {2.75f, -0.5f}, - {5.0f, 1.0f}, - {5.0f, -0.5f} + Vector2{0.0f, 0.5f} + offset, + Vector2{0.0f, 0.0f} + offset, + Vector2{0.75f, 0.5f} + offset, + Vector2{0.75f, 0.0f} + offset, + + Vector2{1.0f, 0.75f} + offset, + Vector2{1.0f, -0.25f} + offset, + Vector2{2.5f, 0.75f} + offset, + Vector2{2.5f, -0.25f} + offset, + + Vector2{2.75f, 1.0f} + offset, + Vector2{2.75f, -0.5f} + offset, + Vector2{5.0f, 1.0f} + offset, + Vector2{5.0f, -0.5f} + offset })); /* Texture coordinates @@ -155,9 +161,6 @@ void RendererGLTest::renderData() { 4, 5, 6, 5, 7, 6, 8, 9, 10, 9, 11, 10 })); - - /* Bounds */ - CORRADE_COMPARE(bounds, Rectangle({0.0f, -0.5f}, {5.0f, 1.0f})); } void RendererGLTest::renderMesh() { @@ -165,28 +168,34 @@ void RendererGLTest::renderMesh() { Mesh mesh; Buffer vertexBuffer, indexBuffer; Rectangle bounds; - std::tie(mesh, bounds) = Text::Renderer3D::render(font, *static_cast(nullptr), 0.25f, "abc", vertexBuffer, indexBuffer, Buffer::Usage::StaticDraw); + std::tie(mesh, bounds) = Text::Renderer3D::render(font, *static_cast(nullptr), 0.25f, "abc", vertexBuffer, indexBuffer, Buffer::Usage::StaticDraw, Alignment::TopCenter); MAGNUM_VERIFY_NO_ERROR(); + /* Alignment offset */ + const Vector2 offset{-2.5f, -1.5f}; + + /* Bounds */ + CORRADE_COMPARE(bounds, Rectangle(Vector2{0.0f, -0.5f} + offset, Vector2{5.0f, 1.0f} + offset)); + /** @todo How to verify this on ES? */ #ifndef MAGNUM_TARGET_GLES /* Vertex buffer contents */ Containers::Array vertices = vertexBuffer.data(); CORRADE_COMPARE(std::vector(vertices.begin(), vertices.end()), (std::vector{ - 0.0f, 0.5f, 0.0f, 10.0f, - 0.0f, 0.0f, 0.0f, 0.0f, - 0.75f, 0.5f, 6.0f, 10.0f, - 0.75f, 0.0f, 6.0f, 0.0f, - - 1.0f, 0.75f, 6.0f, 10.0f, - 1.0f, -0.25f, 6.0f, 0.0f, - 2.5f, 0.75f, 12.0f, 10.0f, - 2.5f, -0.25f, 12.0f, 0.0f, - - 2.75f, 1.0f, 12.0f, 10.0f, - 2.75f, -0.5f, 12.0f, 0.0f, - 5.0f, 1.0f, 18.0f, 10.0f, - 5.0f, -0.5f, 18.0f, 0.0f + 0.0f + offset.x(), 0.5f + offset.y(), 0.0f, 10.0f, + 0.0f + offset.x(), 0.0f + offset.y(), 0.0f, 0.0f, + 0.75f + offset.x(), 0.5f + offset.y(), 6.0f, 10.0f, + 0.75f + offset.x(), 0.0f + offset.y(), 6.0f, 0.0f, + + 1.0f + offset.x(), 0.75f + offset.y(), 6.0f, 10.0f, + 1.0f + offset.x(), -0.25f + offset.y(), 6.0f, 0.0f, + 2.5f + offset.x(), 0.75f + offset.y(), 12.0f, 10.0f, + 2.5f + offset.x(), -0.25f + offset.y(), 12.0f, 0.0f, + + 2.75f + offset.x(), 1.0f + offset.y(), 12.0f, 10.0f, + 2.75f + offset.x(), -0.5f + offset.y(), 12.0f, 0.0f, + 5.0f + offset.x(), 1.0f + offset.y(), 18.0f, 10.0f, + 5.0f + offset.x(), -0.5f + offset.y(), 18.0f, 0.0f })); Containers::Array indices = indexBuffer.data(); @@ -196,9 +205,6 @@ void RendererGLTest::renderMesh() { 8, 9, 10, 9, 11, 10 })); #endif - - /* Bounds */ - CORRADE_COMPARE(bounds, Rectangle({0.0f, -0.5f}, {5.0f, 1.0f})); } void RendererGLTest::mutableText() { @@ -226,6 +232,12 @@ void RendererGLTest::mutableText() { /* Render text */ renderer.render("abc"); MAGNUM_VERIFY_NO_ERROR(); + + /* Updated bounds */ + CORRADE_COMPARE(renderer.rectangle(), Rectangle({0.0f, -0.5f}, {5.0f, 1.0f})); + + /* Aligned to line/left, no offset needed */ + /** @todo How to verify this on ES? */ #ifndef MAGNUM_TARGET_GLES Containers::Array vertices = renderer.vertexBuffer().subData(0, 48); @@ -246,9 +258,6 @@ void RendererGLTest::mutableText() { 5.0f, -0.5f, 18.0f, 0.0f })); #endif - - /* Updated bounds */ - CORRADE_COMPARE(renderer.rectangle(), Rectangle({0.0f, -0.5f}, {5.0f, 1.0f})); } }}} diff --git a/src/Text/Text.h b/src/Text/Text.h index c14bee215..c1f65b53c 100644 --- a/src/Text/Text.h +++ b/src/Text/Text.h @@ -40,6 +40,10 @@ class AbstractLayouter; class DistanceFieldGlyphCache; class GlyphCache; +#ifndef MAGNUM_GCC46_COMPATIBILITY +enum class Alignment: UnsignedByte; +#endif + class AbstractRenderer; template class Renderer; typedef Renderer<2> Renderer2D;