Browse Source

Text: support for aligning rendered text.

pull/34/head
Vladimír Vondruš 13 years ago
parent
commit
ccc31f8105
  1. 131
      src/Text/Alignment.h
  2. 1
      src/Text/CMakeLists.txt
  3. 55
      src/Text/Renderer.cpp
  4. 18
      src/Text/Renderer.h
  5. 87
      src/Text/Test/RendererGLTest.cpp
  6. 4
      src/Text/Text.h

131
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š <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 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

1
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

55
src/Text/Renderer.cpp

@ -57,13 +57,38 @@ template<class T> 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<Vector2>, std::vector<Vector2>, std::vector<UnsignedInt>, Rectangle> AbstractRenderer::render(AbstractFont& font, const GlyphCache& cache, Float size, const std::string& text) {
std::tuple<std::vector<Vector2>, std::vector<Vector2>, std::vector<UnsignedInt>, 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<Vector2>, std::vector<Vector2>, std::vector<UnsignedInt>,
cursorPosition += advance;
}
/* Respect the alignment */
const Vector2 offset = alignmentOffset(rectangle, alignment);
for(auto& p: positions) p += offset;
/* Create indices */
std::vector<UnsignedInt> indices(layouter->glyphCount()*6);
createIndices<UnsignedInt>(indices.data(), layouter->glyphCount());
@ -121,7 +150,7 @@ 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> AbstractRenderer::render(AbstractFont& font, const GlyphCache& cache, Float size, const std::string& text, Buffer& vertexBuffer, Buffer& indexBuffer, Buffer::Usage usage) {
std::tuple<Mesh, Rectangle> 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<Mesh, Rectangle> 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<Mesh, Rectangle> AbstractRenderer::render(AbstractFont& font, const G
return std::make_tuple(std::move(mesh), rectangle);
}
template<UnsignedInt dimensions> std::tuple<Mesh, Rectangle> Renderer<dimensions>::render(AbstractFont& font, const GlyphCache& cache, Float size, const std::string& text, Buffer& vertexBuffer, Buffer& indexBuffer, Buffer::Usage usage) {
template<UnsignedInt dimensions> std::tuple<Mesh, Rectangle> Renderer<dimensions>::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<dimensions>::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<UnsignedInt dimensions> Renderer<dimensions>::Renderer(AbstractFont& font, const GlyphCache& cache, const Float size): AbstractRenderer(font, cache, size) {
template<UnsignedInt dimensions> Renderer<dimensions>::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<dimensions>::Position(Shaders::AbstractVector<dimensions>::Position::Components::Two),
@ -338,8 +372,8 @@ void AbstractRenderer::render(const std::string& text) {
_rectangle = {};
/* Map buffer for rendering */
Vertex* const vertices = static_cast<Vertex*>(bufferMapImplementation(_vertexBuffer,
layouter->glyphCount()*4*sizeof(Vertex)));
Containers::ArrayReference<Vertex> vertices(static_cast<Vertex*>(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 */

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

87
src/Text/Test/RendererGLTest.cpp

@ -86,13 +86,19 @@ void RendererGLTest::renderData() {
std::vector<Vector2> textureCoordinates;
std::vector<UnsignedInt> indices;
Rectangle bounds;
std::tie(positions, textureCoordinates, indices, bounds) = Text::AbstractRenderer::render(font, *static_cast<GlyphCache*>(nullptr), 0.25f, "abc");
std::tie(positions, textureCoordinates, indices, bounds) = Text::AbstractRenderer::render(font, *static_cast<GlyphCache*>(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<Vector2>{
{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<GlyphCache*>(nullptr), 0.25f, "abc", vertexBuffer, indexBuffer, Buffer::Usage::StaticDraw);
std::tie(mesh, bounds) = Text::Renderer3D::render(font, *static_cast<GlyphCache*>(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<Float> vertices = vertexBuffer.data<Float>();
CORRADE_COMPARE(std::vector<Float>(vertices.begin(), vertices.end()), (std::vector<Float>{
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<UnsignedByte> indices = indexBuffer.data<UnsignedByte>();
@ -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<Float> vertices = renderer.vertexBuffer().subData<Float>(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}));
}
}}}

4
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<UnsignedInt> class Renderer;
typedef Renderer<2> Renderer2D;

Loading…
Cancel
Save