Browse Source

Text: support for multi-line text rendering.

pull/34/head
Vladimír Vondruš 13 years ago
parent
commit
5f07906aba
  1. 2
      src/Text/AbstractFont.h
  2. 146
      src/Text/Renderer.cpp
  3. 106
      src/Text/Test/RendererGLTest.cpp

2
src/Text/AbstractFont.h

@ -199,6 +199,8 @@ class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin {
* @param size Font size * @param size Font size
* @param text %Text to layout * @param text %Text to layout
* *
* Note that the layouters support rendering of single-line text only.
* See @ref Renderer class for more advanced text layouting.
* @see @ref fillGlyphCache(), @ref createGlyphCache() * @see @ref fillGlyphCache(), @ref createGlyphCache()
*/ */
std::unique_ptr<AbstractLayouter> layout(const GlyphCache& cache, Float size, const std::string& text); std::unique_ptr<AbstractLayouter> layout(const GlyphCache& cache, Float size, const std::string& text);

146
src/Text/Renderer.cpp

@ -58,62 +58,114 @@ struct Vertex {
Vector2 position, textureCoordinates; Vector2 position, textureCoordinates;
}; };
std::tuple<std::vector<Vertex>, Rectangle> renderVerticesInternal(AbstractFont& font, const GlyphCache& cache, Float size, const std::string& text, Alignment alignment) { std::tuple<std::vector<Vertex>, Rectangle> renderVerticesInternal(AbstractFont& font, const GlyphCache& cache, Float size, const std::string& text, const Alignment alignment) {
const auto layouter = font.layout(cache, size, text); /* Output data, reserve memory as when the text would be ASCII-only. In
const UnsignedInt vertexCount = layouter->glyphCount()*4; reality the actual vertex count will be smaller, but allocating more at
once is better than reallocating many times later. */
/* Output data */
std::vector<Vertex> vertices; std::vector<Vertex> vertices;
vertices.reserve(vertexCount); vertices.reserve(text.size()*4);
/* Rendered rectangle */ /* Total rendered bounds, intial line position, last+1 vertex on previous line */
Rectangle rectangle; Rectangle rectangle;
Vector2 linePosition;
/* Render all glyphs */ std::size_t lastLineLastVertex = 0;
Vector2 cursorPosition;
for(UnsignedInt i = 0; i != layouter->glyphCount(); ++i) { /* Temp buffer so we don't allocate for each new line */
Rectangle quadPosition, textureCoordinates; /**
std::tie(quadPosition, textureCoordinates) = layouter->renderGlyph(i, cursorPosition, rectangle); * @todo C++1z: use std::string_view to avoid the one allocation and all
* the copying altogether
/* 0---2 */
| | std::string line;
| | line.reserve(text.size());
| |
1---3 */ /* Render each line separately and align it horizontally */
std::size_t pos, prevPos = 0;
vertices.insert(vertices.end(), { do {
{quadPosition.topLeft(), textureCoordinates.topLeft()}, /* Empty line, nothing to do (the rest is done below in while expression) */
{quadPosition.bottomLeft(), textureCoordinates.bottomLeft()}, if((pos = text.find('\n', prevPos)) == prevPos) continue;
{quadPosition.topRight(), textureCoordinates.topRight()},
{quadPosition.bottomRight(), textureCoordinates.bottomRight()} /* Copy the line into the temp buffer */
}); line.assign(text, prevPos, pos-prevPos);
}
/* Layout the line */
/* Align the rendered mesh */ const auto layouter = font.layout(cache, size, line);
const Vector2 renderedSize = rectangle.size(); const UnsignedInt vertexCount = layouter->glyphCount()*4;
Vector2 alignmentOffset;
/* Verify that we don't reallocate anything. The only problem might
/** @todo What about top-down text? */ arise when the layouter decides to compose one character from more
than one glyph (i.e. accents). Will remove the assert when this
/* Horizontal alignment */ issue arises. */
if((UnsignedByte(alignment) & Implementation::AlignmentHorizontal) == Implementation::AlignmentCenter) CORRADE_INTERNAL_ASSERT(vertices.size()+vertexCount <= vertices.capacity());
alignmentOffset -= Vector2::xAxis(renderedSize.x()*0.5f);
else if((UnsignedByte(alignment) & Implementation::AlignmentHorizontal) == Implementation::AlignmentRight) /* Bounds of rendered line */
alignmentOffset -= Vector2::xAxis(renderedSize.x()); Rectangle lineRectangle;
/* Vertical alignment */ /* Render all glyphs */
Vector2 cursorPosition(linePosition);
for(UnsignedInt i = 0; i != layouter->glyphCount(); ++i) {
Rectangle quadPosition, textureCoordinates;
std::tie(quadPosition, textureCoordinates) = layouter->renderGlyph(i, cursorPosition, lineRectangle);
/* 0---2
| |
| |
| |
1---3 */
vertices.insert(vertices.end(), {
{quadPosition.topLeft(), textureCoordinates.topLeft()},
{quadPosition.bottomLeft(), textureCoordinates.bottomLeft()},
{quadPosition.topRight(), textureCoordinates.topRight()},
{quadPosition.bottomRight(), textureCoordinates.bottomRight()}
});
}
/** @todo What about top-down text? */
/* Horizontally align the rendered line */
const Float renderedWidth = lineRectangle.width();
Float alignmentOffsetX = 0.0f;
if((UnsignedByte(alignment) & Implementation::AlignmentHorizontal) == Implementation::AlignmentCenter)
alignmentOffsetX = -renderedWidth*0.5f;
else if((UnsignedByte(alignment) & Implementation::AlignmentHorizontal) == Implementation::AlignmentRight)
alignmentOffsetX = -renderedWidth;
/* Integer alignment */
if(UnsignedByte(alignment) & Implementation::AlignmentIntegral)
alignmentOffsetX = Math::round(alignmentOffsetX);
/* Align positions and bounds on current line */
lineRectangle = lineRectangle.translated(Vector2::xAxis(alignmentOffsetX));
for(auto it = vertices.begin()+lastLineLastVertex; it != vertices.end(); ++it)
it->position.x() += alignmentOffsetX;
/* Add final line bounds to total bounds, similarly to AbstractFont::renderGlyph() */
if(!rectangle.size().isZero()) {
rectangle.bottomLeft() = Math::min(rectangle.bottomLeft(), lineRectangle.bottomLeft());
rectangle.topRight() = Math::max(rectangle.topRight(), lineRectangle.topRight());
} else rectangle = lineRectangle;
/* Move to next line */
} while(prevPos = pos+1,
linePosition -= Vector2::yAxis(font.lineHeight()),
lastLineLastVertex = vertices.size(),
pos != std::string::npos);
/* Vertically align the rendered text */
const Float renderedHeight = rectangle.height();
Float alignmentOffsetY = 0.0f;
if((UnsignedByte(alignment) & Implementation::AlignmentVertical) == Implementation::AlignmentMiddle) if((UnsignedByte(alignment) & Implementation::AlignmentVertical) == Implementation::AlignmentMiddle)
alignmentOffset -= Vector2::yAxis(renderedSize.y()*0.5f); alignmentOffsetY = -renderedHeight*0.5f;
else if((UnsignedByte(alignment) & Implementation::AlignmentVertical) == Implementation::AlignmentTop) else if((UnsignedByte(alignment) & Implementation::AlignmentVertical) == Implementation::AlignmentTop)
alignmentOffset -= Vector2::yAxis(renderedSize.y()); alignmentOffsetY = -renderedHeight;
/* Integer alignment */ /* Integer alignment */
if(UnsignedByte(alignment) & Implementation::AlignmentIntegral) if(UnsignedByte(alignment) & Implementation::AlignmentIntegral)
alignmentOffset = Math::round(alignmentOffset); alignmentOffsetY = Math::round(alignmentOffsetY);
/* Update positions and bounds */ /* Align positions and bounds */
rectangle = rectangle.translated(alignmentOffset); rectangle = rectangle.translated(Vector2::yAxis(alignmentOffsetY));
for(auto& v: vertices) v.position += alignmentOffset; for(auto& v: vertices) v.position.y() += alignmentOffsetY;
return std::make_tuple(std::move(vertices), rectangle); return std::make_tuple(std::move(vertices), rectangle);
} }

106
src/Text/Test/RendererGLTest.cpp

@ -35,12 +35,16 @@ class RendererGLTest: public Magnum::Test::AbstractOpenGLTester {
void renderData(); void renderData();
void renderMesh(); void renderMesh();
void mutableText(); void mutableText();
void multiline();
}; };
RendererGLTest::RendererGLTest() { RendererGLTest::RendererGLTest() {
addTests({&RendererGLTest::renderData, addTests({&RendererGLTest::renderData,
&RendererGLTest::renderMesh, &RendererGLTest::renderMesh,
&RendererGLTest::mutableText}); &RendererGLTest::mutableText,
&RendererGLTest::multiline});
} }
namespace { namespace {
@ -257,6 +261,106 @@ void RendererGLTest::mutableText() {
#endif #endif
} }
void RendererGLTest::multiline() {
class Layouter: public Text::AbstractLayouter {
public:
explicit Layouter(UnsignedInt glyphs): AbstractLayouter(glyphs) {}
private:
std::tuple<Rectangle, Rectangle, Vector2> doRenderGlyph(UnsignedInt) override {
return std::make_tuple(Rectangle({}, Vector2(1.0f)), Rectangle({}, Vector2(1.0f)), Vector2::xAxis(2.0f));
}
};
class Font: public Text::AbstractFont {
public:
explicit Font(): _opened(false) {}
private:
Features doFeatures() const override { return {}; }
bool doIsOpened() const override { return _opened; }
void doClose() override { _opened = false; }
std::pair<Float, Float> doOpenFile(const std::string&, Float) {
_opened = true;
return {0, 3.0f};
}
UnsignedInt doGlyphId(char32_t) override { return 0; }
Vector2 doGlyphAdvance(UnsignedInt) override { return {}; }
std::unique_ptr<AbstractLayouter> doLayout(const GlyphCache&, Float, const std::string& text) override {
return std::unique_ptr<AbstractLayouter>(new Layouter(text.size()));
}
bool _opened;
};
Font font;
font.openFile({}, 0.0f);
Rectangle rectangle;
std::vector<UnsignedInt> indices;
std::vector<Vector2> positions, textureCoordinates;
std::tie(positions, textureCoordinates, indices, rectangle) = Text::Renderer2D::render(font,
*static_cast<GlyphCache*>(nullptr), 0.0f, "abcd\nef\n\nghi", Alignment::MiddleCenter);
/* Bounds */
CORRADE_COMPARE(rectangle, Rectangle({-3.5f, -5.0f}, {3.5f, 5.0f}));
/* Vertices
[a] [b] [c] [d]
[e] [f]
[g] [h] [i] */
CORRADE_COMPARE(positions, (std::vector<Vector2>{
Vector2{-3.5f, 5.0f}, Vector2{-3.5f, 4.0f}, /* a */
Vector2{-2.5f, 5.0f}, Vector2{-2.5f, 4.0f},
Vector2{-1.5f, 5.0f}, Vector2{-1.5f, 4.0f}, /* b */
Vector2{-0.5f, 5.0f}, Vector2{-0.5f, 4.0f},
Vector2{ 0.5f, 5.0f}, Vector2{ 0.5f, 4.0f}, /* c */
Vector2{ 1.5f, 5.0f}, Vector2{ 1.5f, 4.0f},
Vector2{ 2.5f, 5.0f}, Vector2{ 2.5f, 4.0f}, /* d */
Vector2{ 3.5f, 5.0f}, Vector2{ 3.5f, 4.0f},
Vector2{-1.5f, 2.0f}, Vector2{-1.5f, 1.0f}, /* e */
Vector2{-0.5f, 2.0f}, Vector2{-0.5f, 1.0f},
Vector2{ 0.5f, 2.0f}, Vector2{ 0.5f, 1.0f}, /* f */
Vector2{ 1.5f, 2.0f}, Vector2{ 1.5f, 1.0f},
Vector2{-2.5f, -4.0f}, Vector2{-2.5f, -5.0f}, /* g */
Vector2{-1.5f, -4.0f}, Vector2{-1.5f, -5.0f},
Vector2{-0.5f, -4.0f}, Vector2{-0.5f, -5.0f}, /* h */
Vector2{ 0.5f, -4.0f}, Vector2{ 0.5f, -5.0f},
Vector2{ 1.5f, -4.0f}, Vector2{ 1.5f, -5.0f}, /* i */
Vector2{ 2.5f, -4.0f}, Vector2{ 2.5f, -5.0f},
}));
/* Indices
0---2 0---2 5
| | | / /|
| | | / / |
| | |/ / |
1---3 1 3---4 */
CORRADE_COMPARE(indices, (std::vector<UnsignedInt>{
0, 1, 2, 1, 3, 2,
4, 5, 6, 5, 7, 6,
8, 9, 10, 9, 11, 10,
12, 13, 14, 13, 15, 14,
16, 17, 18, 17, 19, 18,
20, 21, 22, 21, 23, 22,
24, 25, 26, 25, 27, 26,
28, 29, 30, 29, 31, 30,
32, 33, 34, 33, 35, 34
}));
}
}}} }}}
CORRADE_TEST_MAIN(Magnum::Text::Test::RendererGLTest) CORRADE_TEST_MAIN(Magnum::Text::Test::RendererGLTest)

Loading…
Cancel
Save