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

Loading…
Cancel
Save