Browse Source

Text: new, reusable and composable guts for the Renderer.

The awful original STL-heavy public API is kept the same for now, it's
just the internals being now implemented using brand new APIs that are
actually usable with multiple fonts, font sizes and runs with different
scripts/languages/directions. There's also preparation for configurable
vertical text layouting, although for now the functionality asserts that
horizontal text is used.

This also makes Renderer.h header available on non-GL builds, as the new
APIs don't rely on a class full of GL objects anymore. The class will
get eventually renamed and moved to a dedicated RendererGL.h header, but
for now this partial update has to suffice.
pull/168/head
Vladimír Vondruš 3 years ago
parent
commit
568a4205a6
  1. 8
      src/Magnum/Text/CMakeLists.txt
  2. 406
      src/Magnum/Text/Renderer.cpp
  3. 220
      src/Magnum/Text/Renderer.h
  4. 50
      src/Magnum/Text/Test/RendererGLTest.cpp
  5. 707
      src/Magnum/Text/Test/RendererTest.cpp

8
src/Magnum/Text/CMakeLists.txt

@ -40,6 +40,7 @@ set(MagnumText_GracefulAssert_SRCS
AbstractGlyphCache.cpp
AbstractShaper.cpp
Feature.cpp
Renderer.cpp
Script.cpp)
set(MagnumText_HEADERS
@ -50,6 +51,7 @@ set(MagnumText_HEADERS
Alignment.h
Direction.h
Feature.h
Renderer.h
Script.h
Text.h
@ -62,12 +64,10 @@ if(MAGNUM_TARGET_GL)
list(APPEND MagnumText_SRCS
GlyphCache.cpp)
list(APPEND MagnumText_GracefulAssert_SRCS
DistanceFieldGlyphCache.cpp
Renderer.cpp)
DistanceFieldGlyphCache.cpp)
list(APPEND MagnumText_HEADERS
DistanceFieldGlyphCache.h
GlyphCache.h
Renderer.h)
GlyphCache.h)
else()
# So MagnumTextObjects has at least something
list(APPEND MagnumText_SRCS ${PROJECT_SOURCE_DIR}/src/dummy.cpp)

406
src/Magnum/Text/Renderer.cpp

@ -25,46 +25,237 @@
#include "Renderer.h"
#include <Corrade/Containers/StridedArrayView.h>
#include <Corrade/Containers/Triple.h>
#include "Magnum/Math/Functions.h"
#include "Magnum/Text/AbstractFont.h"
#include "Magnum/Text/AbstractGlyphCache.h"
#include "Magnum/Text/Direction.h"
#ifdef MAGNUM_TARGET_GL
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/ArrayViewStl.h> /** @todo remove once Renderer is STL-free */
#include <Corrade/Containers/StridedArrayView.h>
#include <Corrade/Containers/StringStl.h> /** @todo remove once Renderer is STL-free */
#include <Corrade/Containers/Triple.h>
#include "Magnum/Mesh.h"
#include "Magnum/GL/Context.h"
#include "Magnum/GL/Extensions.h"
#include "Magnum/GL/Mesh.h"
#include "Magnum/Math/Functions.h"
#include "Magnum/Shaders/GenericGL.h"
#include "Magnum/Text/AbstractFont.h"
#include "Magnum/Text/AbstractGlyphCache.h"
#include "Magnum/Text/AbstractShaper.h"
#endif
namespace Magnum { namespace Text {
Range2D renderLineGlyphPositionsInto(const AbstractFont& font, const Float size, const LayoutDirection direction, const Containers::StridedArrayView1D<const Vector2>& glyphOffsets, const Containers::StridedArrayView1D<const Vector2>& glyphAdvances, Vector2& cursor, const Containers::StridedArrayView1D<Vector2>& glyphPositions) {
CORRADE_ASSERT(glyphAdvances.size() == glyphOffsets.size() &&
glyphPositions.size() == glyphOffsets.size(),
"Text::renderLineGlyphPositionsInto(): expected glyphOffsets, glyphAdvances and output views to have the same size, got" << glyphOffsets.size() << Debug::nospace << "," << glyphAdvances.size() << "and" << glyphPositions.size(), {});
CORRADE_ASSERT(direction == LayoutDirection::HorizontalTopToBottom,
"Text::renderLineGlyphPositionsInto(): only" << LayoutDirection::HorizontalTopToBottom << "is supported right now, got" << direction, {});
#ifdef CORRADE_NO_ASSERT
static_cast<void>(direction); /** @todo drop once implemented */
#endif
CORRADE_ASSERT(font.isOpened(),
"Text::renderLineGlyphPositionsInto(): no font opened", {});
const Float scale = size/font.size();
/* Combine the offsets and cursor advances and calculate the line rectangle
along the way. Initially the cursor is at origin and rectangle is empty,
with just the Y bounds from font metrics. */
Range2D rectangle{cursor + Vector2::yAxis(font.descent()*scale),
cursor + Vector2::yAxis(font.ascent()*scale)};
for(UnsignedInt i = 0; i != glyphOffsets.size(); ++i) {
/* The glyphOffsets and output are allowed to be aliased, so make sure
the value isn't stomped on when writing the output */
glyphPositions[i] = cursor + glyphOffsets[i]*scale;
cursor += glyphAdvances[i]*scale;
/* Extend the line rectangle with the cursor range */
/** @todo this assumes left-to-right direction, update when vertical
and LTR text is possible & testable */
rectangle.max() = Math::max(rectangle.max(), cursor);
}
return rectangle;
}
namespace {
template<class T> void createIndices(void* output, const UnsignedInt glyphCount) {
T* const out = reinterpret_cast<T*>(output);
Range2D renderGlyphQuadsInto(const AbstractFont& font, const Float size, const AbstractGlyphCache& cache, const Containers::StridedArrayView1D<const Vector2>& glyphPositions, const Containers::StridedArrayView1D<const UnsignedInt>& glyphIds, const Containers::StridedArrayView1D<Vector2>& vertexPositions, const Containers::StridedArrayView1D<Vector2>& vertexTextureCoordinates, const Containers::StridedArrayView1D<Float>& vertexTextureLayers) {
CORRADE_ASSERT(glyphIds.size() == glyphPositions.size(),
"Text::renderGlyphQuadsInto(): expected glyphIds and glyphPositions views to have the same size, got" << glyphIds.size() << "and" << glyphPositions.size(), {});
CORRADE_ASSERT(vertexPositions.size() == glyphPositions.size()*4 &&
vertexTextureCoordinates.size() == glyphPositions.size()*4,
"Text::renderGlyphQuadsInto(): expected vertexPositions and vertexTextureCoordinates views to have" << glyphPositions.size()*4 << "elements, got" << vertexPositions.size() << "and" << vertexTextureCoordinates.size(), {});
/* Should be ensured by the callers below */
CORRADE_INTERNAL_ASSERT(!vertexTextureLayers || vertexTextureLayers.size() == vertexTextureCoordinates.size());
CORRADE_ASSERT(font.isOpened(),
"Text::renderGlyphQuadsInto(): no font opened", {});
const Float scale = size/font.size();
const Vector2 inverseCacheSize = 1.0f/Vector2{cache.size().xy()};
const Containers::Optional<UnsignedInt> fontId = cache.findFont(&font);
CORRADE_ASSERT(fontId,
"Text::renderGlyphQuadsInto(): font not found among" << cache.fontCount() << "fonts in passed glyph cache", {});
/* Get all glyphs from the glyph cache, create quads for each and calculate
the glyph bound rectangle along the way. */
Range2D rectangle;
for(std::size_t i = 0; i != glyphIds.size(); ++i) {
/* Offset of the glyph rectangle relative to the cursor, layer,
texture coordinates. We checked that the glyph cache is 2D above
so the layer can be ignored. */
const Containers::Triple<Vector2i, Int, Range2Di> cacheGlyph = cache.glyph(*fontId, glyphIds[i]);
/* 2---3
| |
| |
| |
0---1 */
const Range2D quad = Range2D::fromSize(
glyphPositions[i] + Vector2{cacheGlyph.first()}*scale,
Vector2{cacheGlyph.third().size()}*scale);
const Range2D texture = Range2D{cacheGlyph.third()}
.scaled(inverseCacheSize);
const std::size_t i4 = i*4;
for(UnsignedByte j = 0; j != 4; ++j) {
/* ✨ */
vertexPositions[i4 + j] = Math::lerp(quad.min(), quad.max(), BitVector2{j});
vertexTextureCoordinates[i4 + j] = Math::lerp(texture.min(), texture.max(), BitVector2{j});
}
/* Fill also a texture layer if desirable. For 2D output the caller
already checked that the cache is 2D. */
if(vertexTextureLayers) for(std::size_t j = 0; j != 4; ++j)
vertexTextureLayers[i4 + j] = cacheGlyph.second();
/* Extend the rectangle with current glyph bounds */
rectangle = Math::join(rectangle, quad);
}
return rectangle;
}
}
Range2D renderGlyphQuadsInto(const AbstractFont& font, const Float size, const AbstractGlyphCache& cache, const Containers::StridedArrayView1D<const Vector2>& glyphPositions, const Containers::StridedArrayView1D<const UnsignedInt>& glyphIds, const Containers::StridedArrayView1D<Vector2>& vertexPositions, const Containers::StridedArrayView1D<Vector3>& vertexTextureCoordinates) {
return renderGlyphQuadsInto(font, size, cache, glyphPositions, glyphIds, vertexPositions, vertexTextureCoordinates.slice(&Vector3::xy), vertexTextureCoordinates.slice(&Vector3::z));
}
Range2D renderGlyphQuadsInto(const AbstractFont& font, const Float size, const AbstractGlyphCache& cache, const Containers::StridedArrayView1D<const Vector2>& glyphPositions, const Containers::StridedArrayView1D<const UnsignedInt>& glyphIds, const Containers::StridedArrayView1D<Vector2>& vertexPositions, const Containers::StridedArrayView1D<Vector2>& vertexTextureCoordinates) {
CORRADE_ASSERT(cache.size().z() == 1,
"Text::renderGlyphQuadsInto(): can't use this overload with an array glyph cache", {});
return renderGlyphQuadsInto(font, size, cache, glyphPositions, glyphIds, vertexPositions, vertexTextureCoordinates, nullptr);
}
Range2D alignRenderedLine(const Range2D& lineRectangle, const LayoutDirection direction, const Alignment alignment, const Containers::StridedArrayView1D<Vector2>& positions) {
CORRADE_ASSERT(direction == LayoutDirection::HorizontalTopToBottom,
"Text::alignRenderedLine(): only" << LayoutDirection::HorizontalTopToBottom << "is supported right now, got" << direction, {});
#ifdef CORRADE_NO_ASSERT
static_cast<void>(direction); /** @todo drop once implemented */
#endif
/** @todo this again assumes horizontal direction, needs to be updated once
vertical (and possibly mixed horizontal/vertical) text is possible */
Float alignmentOffsetX = 0.0f;
if((UnsignedByte(alignment) & Implementation::AlignmentHorizontal) == Implementation::AlignmentLeft)
alignmentOffsetX = -lineRectangle.left();
else if((UnsignedByte(alignment) & Implementation::AlignmentHorizontal) == Implementation::AlignmentCenter) {
alignmentOffsetX = -lineRectangle.centerX();
/* Integer alignment */
if(UnsignedByte(alignment) & Implementation::AlignmentIntegral)
alignmentOffsetX = Math::round(alignmentOffsetX);
}
else if((UnsignedByte(alignment) & Implementation::AlignmentHorizontal) == Implementation::AlignmentRight)
alignmentOffsetX = -lineRectangle.right();
/* Shift all positions */
for(Vector2& i: positions)
i.x() += alignmentOffsetX;
return lineRectangle.translated(Vector2::xAxis(alignmentOffsetX));
}
Range2D alignRenderedBlock(const Range2D& blockRectangle, const LayoutDirection direction, const Alignment alignment, const Containers::StridedArrayView1D<Vector2>& positions) {
CORRADE_ASSERT(direction == LayoutDirection::HorizontalTopToBottom,
"Text::alignRenderedBlock(): only" << LayoutDirection::HorizontalTopToBottom << "is supported right now, got" << direction, {});
#ifdef CORRADE_NO_ASSERT
static_cast<void>(direction); /** @todo drop once implemented */
#endif
/** @todo this assumes vertical layout advance, needs to be updated once
other directions are possible */
Float alignmentOffsetY = 0.0f;
if((UnsignedByte(alignment) & Implementation::AlignmentVertical) == Implementation::AlignmentBottom)
alignmentOffsetY = -blockRectangle.bottom();
else if((UnsignedByte(alignment) & Implementation::AlignmentVertical) == Implementation::AlignmentMiddle) {
alignmentOffsetY = -blockRectangle.centerY();
/* Integer alignment */
if(UnsignedByte(alignment) & Implementation::AlignmentIntegral)
alignmentOffsetY = Math::round(alignmentOffsetY);
}
else if((UnsignedByte(alignment) & Implementation::AlignmentVertical) == Implementation::AlignmentTop)
alignmentOffsetY = -blockRectangle.top();
/* Shift all positions */
for(Vector2& i: positions)
i.y() += alignmentOffsetY;
return blockRectangle.translated(Vector2::yAxis(alignmentOffsetY));
}
namespace {
template<class T> void renderGlyphQuadIndicesIntoInternal(const UnsignedInt glyphOffset, const Containers::StridedArrayView1D<T>& indices) {
CORRADE_ASSERT(indices.size() % 6 == 0,
"Text::renderGlyphQuadIndicesInto(): expected the indices view size to be divisible by 6, got" << indices.size(), );
const UnsignedInt glyphCount = indices.size()/6;
#ifndef CORRADE_NO_ASSERT
const UnsignedLong maxValue = UnsignedLong(glyphOffset)*4 + UnsignedLong(glyphCount)*4;
#endif
CORRADE_ASSERT(maxValue <= (1ull << 8*sizeof(T)),
"Text::renderGlyphQuadIndicesInto(): max index value of" << maxValue - 1 << "cannot fit into a" << 8*sizeof(T) << Debug::nospace << "-bit type", );
for(UnsignedInt i = 0; i != glyphCount; ++i) {
/* 0---2 0---2 5
| | | / /|
| | | / / |
| | |/ / |
1---3 1 3---4 */
const T vertex = T(i)*4;
const UnsignedInt pos = T(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;
/* 2---3 2 3---5
| | |\ \ |
| | | \ \ |
| | | \ \|
0---1 0---1 4 */
const UnsignedInt i4 = (glyphOffset + i)*4;
const UnsignedInt i6 = i*6;
indices[i6 + 0] = i4 + 0;
indices[i6 + 1] = i4 + 1;
indices[i6 + 2] = i4 + 2;
indices[i6 + 3] = i4 + 2;
indices[i6 + 4] = i4 + 1;
indices[i6 + 5] = i4 + 3;
}
}
}
void renderGlyphQuadIndicesInto(UnsignedInt glyphOffset, const Containers::StridedArrayView1D<UnsignedInt>& indices) {
renderGlyphQuadIndicesIntoInternal(glyphOffset, indices);
}
void renderGlyphQuadIndicesInto(UnsignedInt glyphOffset, const Containers::StridedArrayView1D<UnsignedShort>& indices) {
renderGlyphQuadIndicesIntoInternal(glyphOffset, indices);
}
void renderGlyphQuadIndicesInto(UnsignedInt glyphOffset, const Containers::StridedArrayView1D<UnsignedByte>& indices) {
renderGlyphQuadIndicesIntoInternal(glyphOffset, indices);
}
#ifdef MAGNUM_TARGET_GL
namespace {
struct Vertex {
Vector2 position, textureCoordinates;
};
@ -77,10 +268,9 @@ std::tuple<std::vector<Vertex>, Range2D> renderVerticesInternal(AbstractFont& fo
CORRADE_ASSERT(cache.size().z() == 1,
"Text::Renderer: array glyph caches are not supported", {});
/* Find this font in the cache. This is an assert again, as not having a
font in the cache is a user error. */
Containers::Optional<UnsignedInt> fontId = cache.findFont(&font);
CORRADE_ASSERT(fontId,
/* Find this font in the cache and assert in the high-level API already to
avoid confusion */
CORRADE_ASSERT(cache.findFont(&font),
"Text::Renderer: font not found among" << cache.fontCount() << "fonts in passed glyph cache", {});
/* Output data, reserve memory as when the text would be ASCII-only. In
@ -95,7 +285,6 @@ std::tuple<std::vector<Vertex>, Range2D> renderVerticesInternal(AbstractFont& fo
const Vector2 lineAdvance = Vector2::yAxis(font.lineHeight()*scale);
Range2D rectangle;
Vector2 linePosition;
std::size_t lastLineLastVertex = 0;
/* Temp buffer so we don't allocate for each new line */
/**
@ -137,118 +326,58 @@ std::tuple<std::vector<Vertex>, Range2D> renderVerticesInternal(AbstractFont& fo
issue arises. */
CORRADE_INTERNAL_ASSERT(vertices.size() + shaper->glyphCount()*4 <= vertices.capacity());
/* Bounds of rendered line. If `Alignment::*GlyphBounds` is used, it's
filled with actual bounds of each glyph, otherwise with
ascent/descent and actual cursor range. */
Range2D lineRectangle;
/** @todo this assumes horizontal direction, update when vertical text
is possible & testable */
if(!(UnsignedByte(alignment) & Implementation::AlignmentGlyphBounds))
lineRectangle = {linePosition + Vector2::yAxis(font.descent()*scale),
linePosition + Vector2::yAxis(font.ascent()*scale)};
/* Create quads for all glyphs */
Vector2 cursorPosition(linePosition);
for(UnsignedInt i = 0; i != lineGlyphs.size(); ++i) {
/* Offset of the glyph rectangle relative to the cursor, layer,
texture coordinates. We checked that the glyph cache is 2D above
so the layer can be ignored. */
const Containers::Triple<Vector2i, Int, Range2Di> cacheGlyph = cache.glyph(*fontId, glyphs[i].id);
CORRADE_INTERNAL_ASSERT(cacheGlyph.second() == 0);
/* Quad rectangle, created from cache and shaper offset and the
texture rectangle, scaled to requested text size and translated
to current cursor */
const Range2D quadPosition = Range2D::fromSize(
Vector2{cacheGlyph.first()} + glyphs[i].offset,
Vector2{cacheGlyph.third().size()})
.scaled(Vector2{scale})
.translated(cursorPosition);
/* Normalized texture coordinates */
const Range2D quadTextureCoordinates = Range2D{cacheGlyph.third()}
.scaled(1.0f/Vector2{cache.size().xy()});
/* 0---2
| |
| |
| |
1---3 */
vertices.insert(vertices.end(), {
{quadPosition.topLeft(), quadTextureCoordinates.topLeft()},
{quadPosition.bottomLeft(), quadTextureCoordinates.bottomLeft()},
{quadPosition.topRight(), quadTextureCoordinates.topRight()},
{quadPosition.bottomRight(), quadTextureCoordinates.bottomRight()}
});
/* Advance cursor position to next character, again scaled */
cursorPosition += glyphs[i].advance*scale;
/* Extend the line rectangle with current glyph bounds if
`Alignment::*GlyphBounds` is used, otherwise just expand with
the cursor range. */
if(UnsignedByte(alignment) & Implementation::AlignmentGlyphBounds) {
/* If the original is zero size, it gets replaced */
lineRectangle = Math::join(lineRectangle, quadPosition);
} else {
/** @todo this assumes left-to-right direction, update when
when vertical text is possible & testable */
lineRectangle.max() = Math::max(lineRectangle.max(), cursorPosition);
}
}
/** @todo What about top-down text? */
/* Horizontally align the rendered line. As we have the `lineRectangle`
already appropriate based on presence of `Alignment::*GlyphBounds`,
we don't need to special-case it here in any way. */
Float alignmentOffsetX = 0.0f;
if((UnsignedByte(alignment) & Implementation::AlignmentHorizontal) == Implementation::AlignmentLeft)
alignmentOffsetX = -lineRectangle.left();
else if((UnsignedByte(alignment) & Implementation::AlignmentHorizontal) == Implementation::AlignmentCenter) {
alignmentOffsetX = -lineRectangle.centerX();
/* Integer alignment */
if(UnsignedByte(alignment) & Implementation::AlignmentIntegral)
alignmentOffsetX = Math::round(alignmentOffsetX);
}
else if((UnsignedByte(alignment) & Implementation::AlignmentHorizontal) == Implementation::AlignmentRight)
alignmentOffsetX = -lineRectangle.right();
/* 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;
/* Extend the rectangle with final line bounds. This is again the same
code path for both with and without `Alignment::*GlyphBounds`. */
rectangle = Math::join(rectangle, lineRectangle);
Vector2 cursor = linePosition;
/* Render line glyph positions into the first vertex of each quad in
the output */
vertices.resize(vertices.size() + shaper->glyphCount()*4);
const Containers::StridedArrayView1D<Vertex> lineVertices = Containers::stridedArrayView(vertices).exceptPrefix(vertices.size() - shaper->glyphCount()*4);
const Range2D lineRectangle = renderLineGlyphPositionsInto(
font,
size,
/** @todo direction hardcoded here */
LayoutDirection::HorizontalTopToBottom,
lineGlyphs.slice(&Glyph::offset),
lineGlyphs.slice(&Glyph::advance),
cursor,
lineVertices.slice(&Vertex::position).every(4));
/* Create quads from the positions */
const Range2D lineQuadRectangle = renderGlyphQuadsInto(
font,
size,
cache,
lineVertices.slice(&Vertex::position).every(4),
lineGlyphs.slice(&Glyph::id),
lineVertices.slice(&Vertex::position),
lineVertices.slice(&Vertex::textureCoordinates));
/* Horizontally align the line, using either of the rectangles based on
which alignment is desired */
const Range2D alignedLineRectangle = alignRenderedLine(
UnsignedByte(alignment) & Implementation::AlignmentGlyphBounds ?
lineQuadRectangle : lineRectangle,
/** @todo direction hardcoded here */
LayoutDirection::HorizontalTopToBottom,
alignment,
lineVertices.slice(&Vertex::position));
/* Extend the rectangle with final line bounds */
rectangle = Math::join(rectangle, alignedLineRectangle);
/* Move to next line */
} while(prevPos = pos+1,
linePosition -= lineAdvance,
lastLineLastVertex = vertices.size(),
pos != std::string::npos);
/* Vertically align the rendered text. Again, as we had the input rects
already appropriate based on presence of `Alignment::*GlyphBounds`, we
don't need to special-case it here in any way either. */
Float alignmentOffsetY = 0.0f;
if((UnsignedByte(alignment) & Implementation::AlignmentVertical) == Implementation::AlignmentBottom)
alignmentOffsetY = -rectangle.bottom();
else if((UnsignedByte(alignment) & Implementation::AlignmentVertical) == Implementation::AlignmentMiddle) {
alignmentOffsetY = -rectangle.centerY();
/* Integer alignment */
if(UnsignedByte(alignment) & Implementation::AlignmentIntegral)
alignmentOffsetY = Math::round(alignmentOffsetY);
}
else if((UnsignedByte(alignment) & Implementation::AlignmentVertical) == Implementation::AlignmentTop)
alignmentOffsetY = -rectangle.top();
const Range2D alignedRectangle = alignRenderedBlock(
rectangle,
/** @todo direction hardcoded here */
LayoutDirection::HorizontalTopToBottom,
alignment,
Containers::stridedArrayView(vertices).slice(&Vertex::position));
/* Align positions and bounds */
rectangle = rectangle.translated(Vector2::yAxis(alignmentOffsetY));
for(auto& v: vertices) v.position.y() += alignmentOffsetY;
return std::make_tuple(Utility::move(vertices), rectangle);
return std::make_tuple(Utility::move(vertices), alignedRectangle);
}
std::pair<Containers::Array<char>, MeshIndexType> renderIndicesInternal(const UnsignedInt glyphCount) {
@ -259,16 +388,16 @@ std::pair<Containers::Array<char>, MeshIndexType> renderIndicesInternal(const Un
MeshIndexType indexType;
if(vertexCount <= 256) {
indexType = MeshIndexType::UnsignedByte;
indices = Containers::Array<char>(indexCount*sizeof(UnsignedByte));
createIndices<UnsignedByte>(indices, glyphCount);
indices = Containers::Array<char>{NoInit, indexCount*sizeof(UnsignedByte)};
renderGlyphQuadIndicesInto(0, Containers::arrayCast<UnsignedByte>(indices));
} else if(vertexCount <= 65536) {
indexType = MeshIndexType::UnsignedShort;
indices = Containers::Array<char>(indexCount*sizeof(UnsignedShort));
createIndices<UnsignedShort>(indices, glyphCount);
indices = Containers::Array<char>{NoInit, indexCount*sizeof(UnsignedShort)};
renderGlyphQuadIndicesInto(0, Containers::arrayCast<UnsignedShort>(indices));
} else {
indexType = MeshIndexType::UnsignedInt;
indices = Containers::Array<char>(indexCount*sizeof(UnsignedInt));
createIndices<UnsignedInt>(indices, glyphCount);
indices = Containers::Array<char>{NoInit, indexCount*sizeof(UnsignedInt)};
renderGlyphQuadIndicesInto(0, Containers::arrayCast<UnsignedInt>(indices));
}
return {Utility::move(indices), indexType};
@ -320,7 +449,7 @@ std::tuple<std::vector<Vector2>, std::vector<Vector2>, std::vector<UnsignedInt>,
/* Render indices */
const UnsignedInt glyphCount = vertices.size()/4;
std::vector<UnsignedInt> indices(glyphCount*6);
createIndices<UnsignedInt>(indices.data(), glyphCount);
renderGlyphQuadIndicesInto(0, indices);
return std::make_tuple(Utility::move(positions), Utility::move(textureCoordinates), Utility::move(indices), rectangle);
}
@ -461,5 +590,6 @@ void AbstractRenderer::render(const std::string& text) {
template class MAGNUM_TEXT_EXPORT Renderer<2>;
template class MAGNUM_TEXT_EXPORT Renderer<3>;
#endif
#endif
}}

220
src/Magnum/Text/Renderer.h

@ -26,10 +26,11 @@
*/
/** @file Text/Renderer.h
* @brief Class @ref Magnum::Text::AbstractRenderer, @ref Magnum::Text::Renderer, typedef @ref Magnum::Text::Renderer2D, @ref Magnum::Text::Renderer3D
* @brief Class @ref Magnum::Text::AbstractRenderer, @ref Magnum::Text::Renderer, typedef @ref Magnum::Text::Renderer2D, @ref Magnum::Text::Renderer3D, function @ref Magnum::Text::renderLineGlyphPositionsInto(), @ref Magnum::Text::renderGlyphQuadsInto() @ref Magnum::Text::alignRenderedLine(), @ref Magnum::Text::alignRenderedBlock()
*/
#include "Magnum/configure.h"
#include "Magnum/Text/Text.h"
#include "Magnum/Text/visibility.h"
#ifdef MAGNUM_TARGET_GL
#include <string>
@ -40,16 +41,223 @@
#include "Magnum/Math/Range.h"
#include "Magnum/GL/Buffer.h"
#include "Magnum/GL/Mesh.h"
#include "Magnum/Text/Text.h"
#include "Magnum/Text/Alignment.h"
#include "Magnum/Text/visibility.h"
#ifdef CORRADE_TARGET_EMSCRIPTEN
#include <Corrade/Containers/Array.h>
#endif
#endif
namespace Magnum { namespace Text {
/**
@brief Render glyph positions for a (part of a) single line
@param[in] font Font to query metrics from
@param[in] size Size to render the glyphs at
@param[in] direction Layout direction. Currently expected to always be
@ref LayoutDirection::HorizontalTopToBottom.
@param[in] glyphOffsets Glyph offsets coming from @ref AbstractShaper
instance(s) associated with @p font
@param[in] glyphAdvances Glyph advances coming from @ref AbstractShaper
instance(s) associated with @p font
@param[in,out] cursor Initial cursor position. Is updated to a final
cursor position after all glyphs are rendered.
@param[out] glyphPositions Where to put output absolute glyph positions
@return Rectangle spanning the rendered cursor range in one direction and font
descent to ascent in the other
@m_since_latest
The output of this function are just glyph positions alone, which is useful for
example when the actual glyph quad expansion is done by a shader or when the
glyphs get subsequently rasterized some other way than applying a glyph texture
to a sequence of quads. Use @ref renderGlyphQuadsInto() on the resulting
@p glyphPositions array to form actual glyph quads together with texture
coordinates.
The @p glyphOffsets, @p glyphAdvances and @p glyphPositions views are all
expected to have the same size. It's possible to use the same view for
@p glyphOffsets and @p glyphPositions, which will turn the input relative glyph
offsets into absolute positions.
Calls to this function don't strictly need to match calls to
@ref AbstractShaper::shape(). For example if multiple text runs on a single
line differ just by script, language or direction but not by a font or
rendering size, they can be shaped into consecutive portions of a larger
@p glyphOffsets and @p glyphAdvances array and this function can be then called
just once for all runs together. If the font or rendering size changes between
text runs however, you have to call this function for each such run separately
and each time use the updated @p cursor value as an input for the next
@ref renderLineGlyphPositionsInto() call.
@m_class{m-note m-warning}
@par
This function only works on a single line of text. When rendering a
multi-line text, you have to split it by lines and then shape, render and
align each individually, and adjust @p cursor for each new line as
appropriate.
Once the whole line is rendered, @ref Math::join() the rectangles returned from
all calls to this function and pass them together with positions for the whole
line to @ref alignRenderedLine(). Finally, to align a multi-line block, join
rectangles returned from all @ref alignRenderedLine() calls and pass them
together with positions for the whole text to @ref alignRenderedBlock().
*/
MAGNUM_TEXT_EXPORT Range2D renderLineGlyphPositionsInto(const AbstractFont& font, Float size, LayoutDirection direction, const Containers::StridedArrayView1D<const Vector2>& glyphOffsets, const Containers::StridedArrayView1D<const Vector2>& glyphAdvances, Vector2& cursor, const Containers::StridedArrayView1D<Vector2>& glyphPositions);
/**
@brief Render glyph quads for a (part of a) single line
@param[in] font Font to query metrics from
@param[in] size Size to render the glyphs at
@param[in] cache Glyph cache to query for glyph rectangles
@param[in] glyphPositions Glyph positions coming from an earlier call to
@ref renderLineGlyphPositionsInto()
@param[in] glyphIds Matching glyph IDs coming from
@ref AbstractShaper instance(s) associated with @p font
@param[out] vertexPositions Where to put output vertex positions
@param[out] vertexTextureCoordinates Where to put output texture coordinates
@return Rectangle spanning the rendered glyph quads
@m_since_latest
Produces a sequence of quad corner positions and texture coordinates in order
as shown below. The @p glyphPositions and @p glyphIds views are expected to
have the same size, the @p vertexPositions and @p vertexTextureCoordinates
views are then expected to be four times larger than @p glyphPositions and
@p glyphIds, in order to ultimately contain four corner vertices for each
glyph. To optimize memory use, it's possible to alias @p glyphPositions and
@p glyphIds with @cpp vertexPositions.every(4) @ce and
@cpp vertexTextureCoordinates.every(4) @ce --- the rendering is performed in a
way that first reads the position and ID for each glyph and only then fills in
the vertex data.
@verbatim
2---3
| |
| |
| |
0---1
@endverbatim
If the text doesn't need to be aligned based on the actual glyph bounds (i.e.,
the desired @ref Alignment isn't `*GlyphBounds`), it's possible to call this
function even on a multi-line text run provided that @ref alignRenderedLine()
was called on the @p glyphPositions before to align lines relatively to each
other. Otherwise this function should be called on each line individually and
then the @p vertexPositions passed further to @ref alignRenderedLine().
Expects that @p font is contained in @p cache. Glyph IDs not found in the cache
are replaced with the cache-global invalid glyph. If the @p cache is only 2D,
you can use the @ref renderGlyphQuadsInto(const AbstractFont&, Float, const AbstractGlyphCache&, const Containers::StridedArrayView1D<const Vector2>&, const Containers::StridedArrayView1D<const UnsignedInt>&, const Containers::StridedArrayView1D<Vector2>&, const Containers::StridedArrayView1D<Vector2>&)
overload to get just 2D texture coordinates out. Use
@ref renderGlyphQuadIndicesInto() to populate the corresponding index array.
*/
MAGNUM_TEXT_EXPORT Range2D renderGlyphQuadsInto(const AbstractFont& font, Float size, const AbstractGlyphCache& cache, const Containers::StridedArrayView1D<const Vector2>& glyphPositions, const Containers::StridedArrayView1D<const UnsignedInt>& glyphIds, const Containers::StridedArrayView1D<Vector2>& vertexPositions, const Containers::StridedArrayView1D<Vector3>& vertexTextureCoordinates);
/**
@brief Render glyph quads for a (part of a) single line and a 2D glyph cache
@m_since_latest
Compared to @ref renderGlyphQuadsInto(const AbstractFont&, Float, const AbstractGlyphCache&, const Containers::StridedArrayView1D<const Vector2>&, const Containers::StridedArrayView1D<const UnsignedInt>&, const Containers::StridedArrayView1D<Vector2>&, const Containers::StridedArrayView1D<Vector3>&)
outputs just 2D texture coordinates. Expects that @ref AbstractGlyphCache::size()
depth is @cpp 1 @ce.
*/
MAGNUM_TEXT_EXPORT Range2D renderGlyphQuadsInto(const AbstractFont& font, Float size, const AbstractGlyphCache& cache, const Containers::StridedArrayView1D<const Vector2>& glyphPositions, const Containers::StridedArrayView1D<const UnsignedInt>& glyphIds, const Containers::StridedArrayView1D<Vector2>& vertexPositions, const Containers::StridedArrayView1D<Vector2>& vertexTextureCoordinates);
/**
@brief Align a rendered line
@param[in] lineRectangle Rectangle spanning the whole line
@param[in] direction Layout direction. Currently expected to always be
@ref LayoutDirection::HorizontalTopToBottom.
@param[in] alignment Desired alignment. Only the part in direction of
the line is used.
@param[in,out] positions Positions of glyphs or glyph quad vertices on the
whole line to be aligned
@return The @p lineRectangle, translated in the direction of the line based on
the alignment.
@m_since_latest
If @p alignment isn't `*GlyphBounds`, this function should get glyph
@p positions for the whole line coming from @ref renderLineGlyphPositionsInto()
and @p lineRectangle being all rectangles returned by that function combined
together with @ref Math::join().
If @p alignment is `*GlyphBounds`, this function should get vertex @p positions
for a whole line coming from @ref renderGlyphQuadsInto() and @p lineRectangle
being all rectangles returned by that function combined together with
@ref Math::join().
The @p positions are translated in one axis based on the @p inputRectangle and
the part of @p alignment matching line direction in @p direction. Values of the
@p positions themselves aren't considered when calculating the alignment. To
align a multi-line block, join rectangles returned from all calls to this
function and pass them together with positions for the whole block to
@ref alignRenderedBlock().
*/
MAGNUM_TEXT_EXPORT Range2D alignRenderedLine(const Range2D& lineRectangle, LayoutDirection direction, Alignment alignment, const Containers::StridedArrayView1D<Vector2>& positions);
/**
@brief Align a rendered block
@param[in] blockRectangle Rectangle spanning all lines in the block
@param[in] direction Layout direction. Currently expected to always be
@ref LayoutDirection::HorizontalTopToBottom.
@param[in] alignment Desired alignment. Only the part in direction of
the line is used.
@param[in,out] positions Positions of glyphs or glyph quad vertices on the
whole line to be aligned
@return The @p blockRectangle, translated in the direction of the layout
advance based on the alignment.
@m_since_latest
This function should get glyph or vertex @p positions for all lines as aligned
by calls to @ref alignRenderedLine(), and @p blockRectangle being all line
rectangles returned by that function combined together with @ref Math::join().
The @p positions are translated in one axis based on the @p inputRectangle and
the part of @p alignment matching layout advance in @p direction. Values of the
@p positions themselves aren't considered when calculating the translation.
*/
MAGNUM_TEXT_EXPORT Range2D alignRenderedBlock(const Range2D& blockRectangle, LayoutDirection direction, Alignment alignment, const Containers::StridedArrayView1D<Vector2>& positions);
/**
@brief Render 32-bit glyph quad indices
@param[in] glyphOffset Offset of the first glyph to generate indices for
@param[out] indices Where to put the generated indices
@m_since_latest
Produces a sequence of quad indices in order as shown below, with the index
values being shifted by @cpp glyphOffset*4 @ce. Expects that the @p indices
view size is divisible by @cpp 6 @ce and the value range fits into the output
type.
@verbatim
2---3 2 3---5
| | |\ \ |
| | | \ \ |
| | | \ \|
0---1 0---1 4
@endverbatim
*/
MAGNUM_TEXT_EXPORT void renderGlyphQuadIndicesInto(UnsignedInt glyphOffset, const Containers::StridedArrayView1D<UnsignedInt>& indices);
/**
@brief Render 16-bit glyph quad indices
@m_since_latest
See @ref renderGlyphQuadIndicesInto(UnsignedInt, const Containers::StridedArrayView1D<UnsignedInt>&)
for more information.
*/
MAGNUM_TEXT_EXPORT void renderGlyphQuadIndicesInto(UnsignedInt glyphOffset, const Containers::StridedArrayView1D<UnsignedShort>& indices);
/**
@brief Render 8-bit glyph quad indices
@m_since_latest
See @ref renderGlyphQuadIndicesInto(UnsignedInt, const Containers::StridedArrayView1D<UnsignedInt>&)
for more information.
*/
MAGNUM_TEXT_EXPORT void renderGlyphQuadIndicesInto(UnsignedInt glyphOffset, const Containers::StridedArrayView1D<UnsignedByte>& indices);
#ifdef MAGNUM_TARGET_GL
/**
@brief Base for text renderers
@ -340,10 +548,8 @@ typedef Renderer<2> Renderer2D;
for more information.
*/
typedef Renderer<3> Renderer3D;
#endif
}}
#else
#error this header is available only in the OpenGL build
#endif
#endif

50
src/Magnum/Text/Test/RendererGLTest.cpp

@ -146,28 +146,28 @@ void RendererGLTest::renderMesh() {
/* Vertex buffer contents */
Containers::Array<char> vertices = vertexBuffer.data();
CORRADE_COMPARE_AS(Containers::arrayCast<const Vector2>(vertices), Containers::arrayView<Vector2>({
Vector2{ 2.5f, 10.5f} + offset, {0.0f, 0.5f},
Vector2{ 2.5f, 5.5f} + offset, {0.0f, 0.0f},
Vector2{12.5f, 10.5f} + offset, {1.0f, 0.5f},
Vector2{12.5f, 5.5f} + offset, {1.0f, 0.0f},
Vector2{ 2.5f, 10.5f} + offset, {0.0f, 0.5f},
Vector2{12.5f, 10.5f} + offset, {1.0f, 0.5f},
Vector2{ 5.5f, 8.75f} + offset, {0.0f, 1.0f},
Vector2{ 5.5f, 3.75f} + offset, {0.0f, 0.5f},
Vector2{10.5f, 8.75f} + offset, {0.5f, 1.0f},
Vector2{10.5f, 3.75f} + offset, {0.5f, 0.5f},
Vector2{ 5.5f, 8.75f} + offset, {0.0f, 1.0f},
Vector2{10.5f, 8.75f} + offset, {0.5f, 1.0f},
Vector2{ 4.0f, 9.0f} + offset, {0.5f, 1.0f},
Vector2{ 4.0f, 4.0f} + offset, {0.5f, 0.5f},
Vector2{ 9.0f, 9.0f} + offset, {1.0f, 1.0f},
Vector2{ 9.0f, 4.0f} + offset, {1.0f, 0.5f},
Vector2{ 4.0f, 9.0f} + offset, {0.5f, 1.0f},
Vector2{ 9.0f, 9.0f} + offset, {1.0f, 1.0f},
}), TestSuite::Compare::Container);
Containers::Array<char> indices = indexBuffer.data();
CORRADE_COMPARE_AS(Containers::arrayCast<const UnsignedByte>(indices),
Containers::arrayView<UnsignedByte>({
0, 1, 2, 1, 3, 2,
4, 5, 6, 5, 7, 6,
8, 9, 10, 9, 11, 10
0, 1, 2, 2, 1, 3,
4, 5, 6, 6, 5, 7,
8, 9, 10, 10, 9, 11,
}), TestSuite::Compare::Container);
#endif
}
@ -196,9 +196,9 @@ void RendererGLTest::renderMeshIndexType() {
CORRADE_COMPARE(indicesByte.size(), 64*6);
CORRADE_COMPARE_AS(Containers::arrayCast<const UnsignedByte>(indicesByte).prefix(18),
Containers::arrayView<UnsignedByte>({
0, 1, 2, 1, 3, 2,
4, 5, 6, 5, 7, 6,
8, 9, 10, 9, 11, 10
0, 1, 2, 2, 1, 3,
4, 5, 6, 6, 5, 7,
8, 9, 10, 10, 9, 11,
}), TestSuite::Compare::Container);
/* 16-bit indices (260 vertices) */
@ -210,9 +210,9 @@ void RendererGLTest::renderMeshIndexType() {
CORRADE_COMPARE(indicesShort.size(), 65*6*2);
CORRADE_COMPARE_AS(Containers::arrayCast<const UnsignedShort>(indicesShort).prefix(18),
Containers::arrayView<UnsignedShort>({
0, 1, 2, 1, 3, 2,
4, 5, 6, 5, 7, 6,
8, 9, 10, 9, 11, 10
0, 1, 2, 2, 1, 3,
4, 5, 6, 6, 5, 7,
8, 9, 10, 10, 9, 11,
}), TestSuite::Compare::Container);
#else
CORRADE_SKIP("Can't verify buffer contents on OpenGL ES.");
@ -250,10 +250,10 @@ void RendererGLTest::mutableText() {
Containers::Array<char> indices = renderer.indexBuffer().data();
CORRADE_COMPARE_AS(Containers::arrayCast<const UnsignedByte>(indices).prefix(24),
Containers::arrayView<UnsignedByte>({
0, 1, 2, 1, 3, 2,
4, 5, 6, 5, 7, 6,
8, 9, 10, 9, 11, 10,
12, 13, 14, 13, 15, 14
0, 1, 2, 2, 1, 3,
4, 5, 6, 6, 5, 7,
8, 9, 10, 10, 9, 11,
12, 13, 14, 14, 13, 15,
}), TestSuite::Compare::Container);
#endif
@ -272,20 +272,20 @@ void RendererGLTest::mutableText() {
#ifndef MAGNUM_TARGET_GLES
Containers::Array<char> vertices = renderer.vertexBuffer().data();
CORRADE_COMPARE_AS(Containers::arrayCast<const Vector2>(vertices).prefix(2*4*3), Containers::arrayView<Vector2>({
Vector2{ 2.5f, 10.5f} + offset, {0.0f, 0.5f},
Vector2{ 2.5f, 5.5f} + offset, {0.0f, 0.0f},
Vector2{12.5f, 10.5f} + offset, {1.0f, 0.5f},
Vector2{12.5f, 5.5f} + offset, {1.0f, 0.0f},
Vector2{ 2.5f, 10.5f} + offset, {0.0f, 0.5f},
Vector2{12.5f, 10.5f} + offset, {1.0f, 0.5f},
Vector2{ 5.5f, 8.75f} + offset, {0.0f, 1.0f},
Vector2{ 5.5f, 3.75f} + offset, {0.0f, 0.5f},
Vector2{10.5f, 8.75f} + offset, {0.5f, 1.0f},
Vector2{10.5f, 3.75f} + offset, {0.5f, 0.5f},
Vector2{ 5.5f, 8.75f} + offset, {0.0f, 1.0f},
Vector2{10.5f, 8.75f} + offset, {0.5f, 1.0f},
Vector2{ 4.0f, 9.0f} + offset, {0.5f, 1.0f},
Vector2{ 4.0f, 4.0f} + offset, {0.5f, 0.5f},
Vector2{ 9.0f, 9.0f} + offset, {1.0f, 1.0f},
Vector2{ 9.0f, 4.0f} + offset, {1.0f, 0.5f},
Vector2{ 4.0f, 9.0f} + offset, {0.5f, 1.0f},
Vector2{ 9.0f, 9.0f} + offset, {1.0f, 1.0f},
}), TestSuite::Compare::Container);
#endif
}

707
src/Magnum/Text/Test/RendererTest.cpp

@ -27,14 +27,18 @@
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/StridedArrayView.h>
#include <Corrade/Containers/StringView.h>
#include <Corrade/Containers/StringStl.h> /** @todo drop once Debug is stream-free */
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/TestSuite/Compare/Container.h>
#include <Corrade/TestSuite/Compare/String.h>
#include <Corrade/Utility/Algorithms.h>
#include <Corrade/Utility/DebugStl.h> /** @todo drop once Debug is stream-free */
#include "Magnum/PixelFormat.h"
#include "Magnum/Text/AbstractFont.h"
#include "Magnum/Text/AbstractGlyphCache.h"
#include "Magnum/Text/AbstractShaper.h"
#include "Magnum/Text/Direction.h"
#include "Magnum/Text/Renderer.h"
namespace Magnum { namespace Text { namespace Test { namespace {
@ -42,6 +46,30 @@ namespace Magnum { namespace Text { namespace Test { namespace {
struct RendererTest: TestSuite::Tester {
explicit RendererTest();
void lineGlyphPositions();
void lineGlyphPositionsAliasedViews();
void lineGlyphPositionsInvalidViewSizes();
void lineGlyphPositionsInvalidDirection();
void lineGlyphPositionsNoFontOpened();
void lineGlyphQuads();
void lineGlyphQuadsAliasedViews();
void lineGlyphQuadsInvalidViewSizes();
void lineGlyphQuadsNoFontOpened();
void lineGlyphQuadsFontNotFoundInCache();
void lineGlyphQuads2D();
void lineGlyphQuads2DArrayGlyphCache();
void alignLine();
void alignLineInvalidDirection();
void alignBlock();
void alignBlockInvalidDirection();
template<class T> void glyphQuadIndices();
void glyphQuadIndicesTypeTooSmall();
void renderData();
void multiline();
@ -52,6 +80,37 @@ struct RendererTest: TestSuite::Tester {
#endif
};
const struct {
const char* name;
Alignment alignment;
Float offset;
} AlignLineData[]{
/* The vertical alignment and GlyphBounds has no effect here */
/* Left is the default (0) value, thus should result in no shift */
{"left", Alignment::BottomLeft, -10.0f},
{"right", Alignment::LineRightGlyphBounds, -13.5f},
/* Integral should be handled only for Center */
{"right, integral", Alignment::MiddleRightGlyphBoundsIntegral, -13.5f},
{"center", Alignment::TopCenter, -11.75f},
{"center, integral", Alignment::TopCenterIntegral, -12.0f},
};
const struct {
const char* name;
Alignment alignment;
Float offset;
} AlignBlockData[]{
/* The horizontal alignment and GlyphBounds has no effect here */
/* Line is the default (0) value, thus should result in no shift */
{"line", Alignment::LineCenterGlyphBounds, 0.0f},
{"bottom", Alignment::BottomRight, -9.5f},
{"top", Alignment::TopLeftGlyphBounds, -19.5f},
/* Integral should be handled only for Middle */
{"top, integral", Alignment::TopCenterGlyphBoundsIntegral, -19.5f},
{"middle", Alignment::MiddleLeft, -14.5f},
{"middle, integral", Alignment::MiddleLeftIntegral, -15.0f}
};
const struct {
TestSuite::TestCaseDescriptionSourceLocation name;
Alignment alignment;
@ -189,6 +248,36 @@ const struct {
};
RendererTest::RendererTest() {
addTests({&RendererTest::lineGlyphPositions,
&RendererTest::lineGlyphPositionsAliasedViews,
&RendererTest::lineGlyphPositionsInvalidViewSizes,
&RendererTest::lineGlyphPositionsInvalidDirection,
&RendererTest::lineGlyphPositionsNoFontOpened,
&RendererTest::lineGlyphQuads,
&RendererTest::lineGlyphQuadsAliasedViews,
&RendererTest::lineGlyphQuadsInvalidViewSizes,
&RendererTest::lineGlyphQuadsNoFontOpened,
&RendererTest::lineGlyphQuadsFontNotFoundInCache,
&RendererTest::lineGlyphQuads2D,
&RendererTest::lineGlyphQuads2DArrayGlyphCache});
addInstancedTests({&RendererTest::alignLine},
Containers::arraySize(AlignLineData));
addTests({&RendererTest::alignLineInvalidDirection});
addInstancedTests({&RendererTest::alignBlock},
Containers::arraySize(AlignBlockData));
addTests({&RendererTest::alignBlockInvalidDirection,
&RendererTest::glyphQuadIndices<UnsignedInt>,
&RendererTest::glyphQuadIndices<UnsignedShort>,
&RendererTest::glyphQuadIndices<UnsignedByte>,
&RendererTest::glyphQuadIndicesTypeTooSmall});
addInstancedTests({&RendererTest::renderData},
Containers::arraySize(RenderDataData));
@ -271,6 +360,494 @@ DummyGlyphCache testGlyphCache(AbstractFont& font) {
return cache;
}
DummyGlyphCache testGlyphCacheArray(AbstractFont& font) {
DummyGlyphCache cache{PixelFormat::R8Unorm, {20, 20, 3}};
/* Add one more font to verify the right one gets picked */
cache.addFont(96);
UnsignedInt fontId = cache.addFont(font.glyphCount(), &font);
/* Three glyphs, covering bottom, top left and top right of the cache */
cache.addGlyph(fontId, 3, {5, 10}, 2, {{}, {20, 10}});
cache.addGlyph(fontId, 7, {10, 5}, 0, {{0, 10}, {10, 20}});
cache.addGlyph(fontId, 9, {5, 5}, 1, {{10, 10}, {20, 20}});
return cache;
}
void RendererTest::lineGlyphPositions() {
TestFont font;
font.openFile({}, 2.5f);
Vector2 glyphOffsets[]{
{0.2f, -0.4f},
{0.4f, 0.8f},
{-0.2f, 0.4f},
};
Vector2 glyphAdvances[]{
{1.0f, 0.0f},
{2.0f, 0.2f},
{3.0f, -0.2f}
};
Vector2 cursor{100.0f, 200.0f};
/* The font is opened at 2.5, rendering at 1.25, so everything will be
scaled by 0.5 */
Vector2 glyphPositions[3];
Range2D rectangle = renderLineGlyphPositionsInto(font, 1.25f, LayoutDirection::HorizontalTopToBottom, glyphOffsets, glyphAdvances, cursor, glyphPositions);
/* The rectangle contains the cursor range and descent to ascent */
CORRADE_COMPARE(rectangle, (Range2D{{100.0f, 198.75f}, {103.0f, 202.25}}));
CORRADE_COMPARE(cursor, (Vector2{103.0f, 200.0f}));
CORRADE_COMPARE_AS(Containers::arrayView(glyphPositions), Containers::arrayView<Vector2>({
{100.1f, 199.8f},
{100.7f, 200.4f},
{101.4f, 200.3f}
}), TestSuite::Compare::Container);
}
void RendererTest::lineGlyphPositionsAliasedViews() {
/* Like lineGlyphPositions(), but with the input data stored in the output
array. The internals should be written in a way that doesn't overwrite
the input before it's read. */
TestFont font;
font.openFile({}, 2.5f);
Vector2 glyphOffsetsPositions[]{
{0.2f, -0.4f},
{0.4f, 0.8f},
{-0.2f, 0.4f},
};
Vector2 glyphAdvances[]{
{1.0f, 0.0f},
{2.0f, 0.2f},
{3.0f, -0.2f}
};
Vector2 cursor{100.0f, 200.0f};
Range2D rectangle = renderLineGlyphPositionsInto(font, 1.25f, LayoutDirection::HorizontalTopToBottom, glyphOffsetsPositions, glyphAdvances, cursor, glyphOffsetsPositions);
CORRADE_COMPARE(rectangle, (Range2D{{100.0f, 198.75f}, {103.0f, 202.25}}));
CORRADE_COMPARE(cursor, (Vector2{103.0f, 200.0f}));
CORRADE_COMPARE_AS(Containers::arrayView(glyphOffsetsPositions), Containers::arrayView<Vector2>({
{100.1f, 199.8f},
{100.7f, 200.4f},
{101.4f, 200.3f}
}), TestSuite::Compare::Container);
}
void RendererTest::lineGlyphPositionsInvalidViewSizes() {
CORRADE_SKIP_IF_NO_ASSERT();
TestFont font;
Vector2 data[5];
Vector2 dataInvalid[4];
Vector2 cursor;
std::ostringstream out;
Error redirectError{&out};
renderLineGlyphPositionsInto(font, 10.0f, LayoutDirection::HorizontalTopToBottom, data, data, cursor, dataInvalid);
renderLineGlyphPositionsInto(font, 10.0f, LayoutDirection::HorizontalTopToBottom, data, dataInvalid, cursor, data);
renderLineGlyphPositionsInto(font, 10.0f, LayoutDirection::HorizontalTopToBottom, dataInvalid, data, cursor, data);
CORRADE_COMPARE(out.str(),
"Text::renderLineGlyphPositionsInto(): expected glyphOffsets, glyphAdvances and output views to have the same size, got 5, 5 and 4\n"
"Text::renderLineGlyphPositionsInto(): expected glyphOffsets, glyphAdvances and output views to have the same size, got 5, 4 and 5\n"
"Text::renderLineGlyphPositionsInto(): expected glyphOffsets, glyphAdvances and output views to have the same size, got 4, 5 and 5\n");
}
void RendererTest::lineGlyphPositionsInvalidDirection() {
CORRADE_SKIP_IF_NO_ASSERT();
TestFont font;
Vector2 cursor;
std::ostringstream out;
Error redirectError{&out};
renderLineGlyphPositionsInto(font, 10.0f, LayoutDirection::VerticalLeftToRight, {}, {}, cursor, {});
CORRADE_COMPARE(out.str(), "Text::renderLineGlyphPositionsInto(): only Text::LayoutDirection::HorizontalTopToBottom is supported right now, got Text::LayoutDirection::VerticalLeftToRight\n");
}
void RendererTest::lineGlyphPositionsNoFontOpened() {
CORRADE_SKIP_IF_NO_ASSERT();
TestFont font;
Vector2 cursor;
std::ostringstream out;
Error redirectError{&out};
renderLineGlyphPositionsInto(font, 10.0f, LayoutDirection::HorizontalTopToBottom, {}, {}, cursor, {});
CORRADE_COMPARE(out.str(), "Text::renderLineGlyphPositionsInto(): no font opened\n");
}
void RendererTest::lineGlyphQuads() {
TestFont font;
font.openFile({}, 2.5f);
DummyGlyphCache cache = testGlyphCacheArray(font);
Vector2 glyphPositions[]{
{100.0f, 200.0f},
{103.0f, 202.0f},
{107.0f, 196.0f}
};
UnsignedInt glyphIds[]{
3, 7, 9
};
Vector2 positions[3*4];
Vector3 textureCoordinates[3*4];
/* The font is opened at 2.5, rendering at 1.25, so everything will be
scaled by 0.5 */
Range2D rectangle = renderGlyphQuadsInto(font, 1.25f, cache, glyphPositions, glyphIds, positions, textureCoordinates);
CORRADE_COMPARE(rectangle, (Range2D{{102.5f, 198.5f}, {114.5f, 210.0f}}));
/* 2---3
| |
0---1 */
CORRADE_COMPARE_AS(Containers::arrayView(positions), Containers::arrayView<Vector2>({
{102.5f, 205.0f}, /* Offset {5, 10}, size {20, 10}, scaled by 0.5 */
{112.5f, 205.0f},
{102.5f, 210.0f},
{112.5f, 210.0f},
{108.0f, 204.5f}, /* Offset {10, 5}, size {10, 10}, scaled by 0.5 */
{113.0f, 204.5f},
{108.0f, 209.5f},
{113.0f, 209.5f},
{109.5f, 198.5f}, /* Offset {5, 5}, size {10, 10}, scaled by 0.5 */
{114.5f, 198.5f},
{109.5f, 203.5f},
{114.5f, 203.5f},
}), TestSuite::Compare::Container);
/* First glyph is bottom, second top left, third top right; layer is
different for each.
+-+-+
|b|c|
2---3
| a |
0---1 */
CORRADE_COMPARE_AS(Containers::arrayView(textureCoordinates), Containers::arrayView<Vector3>({
{0.0f, 0.0f, 2.0f},
{1.0f, 0.0f, 2.0f},
{0.0f, 0.5f, 2.0f},
{1.0f, 0.5f, 2.0f},
{0.0f, 0.5f, 0.0f},
{0.5f, 0.5f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.5f, 1.0f, 0.0f},
{0.5f, 0.5f, 1.0f},
{1.0f, 0.5f, 1.0f},
{0.5f, 1.0f, 1.0f},
{1.0f, 1.0f, 1.0f},
}), TestSuite::Compare::Container);
}
void RendererTest::lineGlyphQuadsAliasedViews() {
/* Like lineGlyphPositions(), but with the input data stored in the output
array. The internals should be written in a way that doesn't overwrite
the input before it's read. */
TestFont font;
font.openFile({}, 2.5f);
DummyGlyphCache cache = testGlyphCacheArray(font);
Vector2 positions[3*4];
Vector3 textureCoordinates[3*4];
Containers::StridedArrayView1D<Vector2> glyphPositions = Containers::stridedArrayView(positions).every(4);
Utility::copy({
{100.0f, 200.0f},
{103.0f, 202.0f},
{107.0f, 196.0f}
}, glyphPositions);
Containers::StridedArrayView1D<UnsignedInt> glyphIds = Containers::arrayCast<UnsignedInt>(Containers::stridedArrayView(textureCoordinates).every(4));
Utility::copy({
3, 7, 9
}, glyphIds);
Range2D rectangle = renderGlyphQuadsInto(font, 1.25f, cache, glyphPositions, glyphIds, positions, textureCoordinates);
CORRADE_COMPARE(rectangle, (Range2D{{102.5f, 198.5f}, {114.5f, 210.0f}}));
CORRADE_COMPARE_AS(Containers::arrayView(positions), Containers::arrayView<Vector2>({
{102.5f, 205.0f},
{112.5f, 205.0f},
{102.5f, 210.0f},
{112.5f, 210.0f},
{108.0f, 204.5f},
{113.0f, 204.5f},
{108.0f, 209.5f},
{113.0f, 209.5f},
{109.5f, 198.5f},
{114.5f, 198.5f},
{109.5f, 203.5f},
{114.5f, 203.5f},
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(Containers::arrayView(textureCoordinates), Containers::arrayView<Vector3>({
{0.0f, 0.0f, 2.0f},
{1.0f, 0.0f, 2.0f},
{0.0f, 0.5f, 2.0f},
{1.0f, 0.5f, 2.0f},
{0.0f, 0.5f, 0.0f},
{0.5f, 0.5f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.5f, 1.0f, 0.0f},
{0.5f, 0.5f, 1.0f},
{1.0f, 0.5f, 1.0f},
{0.5f, 1.0f, 1.0f},
{1.0f, 1.0f, 1.0f},
}), TestSuite::Compare::Container);
}
void RendererTest::lineGlyphQuadsInvalidViewSizes() {
CORRADE_SKIP_IF_NO_ASSERT();
TestFont font;
DummyGlyphCache cache{PixelFormat::R8Unorm, {20, 20}};
Vector2 glyphPositions[4];
Vector2 glyphPositionsInvalid[5];
UnsignedInt glyphIds[4];
UnsignedInt glyphIdsInvalid[3];
Vector2 positions[16];
Vector2 positionsInvalid[15];
Vector3 textureCoordinates[16];
Vector3 textureCoordinatesInvalid[17];
std::ostringstream out;
Error redirectError{&out};
renderGlyphQuadsInto(font, 10.0f, cache, glyphPositions, glyphIdsInvalid, positions, textureCoordinates);
renderGlyphQuadsInto(font, 10.0f, cache, glyphPositionsInvalid, glyphIds, positions, textureCoordinates);
renderGlyphQuadsInto(font, 10.0f, cache, glyphPositions, glyphIds, positions, textureCoordinatesInvalid);
renderGlyphQuadsInto(font, 10.0f, cache, glyphPositions, glyphIds, positionsInvalid, textureCoordinates);
CORRADE_COMPARE_AS(out.str(),
"Text::renderGlyphQuadsInto(): expected glyphIds and glyphPositions views to have the same size, got 3 and 4\n"
"Text::renderGlyphQuadsInto(): expected glyphIds and glyphPositions views to have the same size, got 4 and 5\n"
"Text::renderGlyphQuadsInto(): expected vertexPositions and vertexTextureCoordinates views to have 16 elements, got 16 and 17\n"
"Text::renderGlyphQuadsInto(): expected vertexPositions and vertexTextureCoordinates views to have 16 elements, got 15 and 16\n",
TestSuite::Compare::String);
}
void RendererTest::lineGlyphQuadsNoFontOpened() {
CORRADE_SKIP_IF_NO_ASSERT();
TestFont font;
DummyGlyphCache cache{PixelFormat::R8Unorm, {20, 20}};
std::ostringstream out;
Error redirectError{&out};
renderGlyphQuadsInto(font, 10.0f, cache, nullptr, nullptr, nullptr, Containers::StridedArrayView1D<Vector3>{});
CORRADE_COMPARE(out.str(), "Text::renderGlyphQuadsInto(): no font opened\n");
}
void RendererTest::lineGlyphQuadsFontNotFoundInCache() {
CORRADE_SKIP_IF_NO_ASSERT();
TestFont font;
font.openFile({}, 0.5f);
DummyGlyphCache cache{PixelFormat::R8Unorm, {20, 20}};
cache.addFont(56);
cache.addFont(13);
std::ostringstream out;
Error redirectError{&out};
renderGlyphQuadsInto(font, 10.0f, cache, nullptr, nullptr, nullptr, Containers::StridedArrayView1D<Vector3>{});
CORRADE_COMPARE(out.str(), "Text::renderGlyphQuadsInto(): font not found among 2 fonts in passed glyph cache\n");
}
void RendererTest::lineGlyphQuads2D() {
/* Like lineGlyphPositions(), but with just a 2D glyph cache and using the
three-component overload. */
TestFont font;
font.openFile({}, 2.5f);
DummyGlyphCache cache = testGlyphCache(font);
Vector2 glyphPositions[]{
{100.0f, 200.0f},
{103.0f, 202.0f},
{107.0f, 196.0f}
};
UnsignedInt glyphIds[]{
3, 7, 9
};
Vector2 positions[3*4];
Vector2 textureCoordinates[3*4];
Range2D rectangle = renderGlyphQuadsInto(font, 1.25f, cache, glyphPositions, glyphIds, positions, textureCoordinates);
CORRADE_COMPARE(rectangle, (Range2D{{102.5f, 198.5f}, {114.5f, 210.0f}}));
CORRADE_COMPARE_AS(Containers::arrayView(positions), Containers::arrayView<Vector2>({
{102.5f, 205.0f},
{112.5f, 205.0f},
{102.5f, 210.0f},
{112.5f, 210.0f},
{108.0f, 204.5f},
{113.0f, 204.5f},
{108.0f, 209.5f},
{113.0f, 209.5f},
{109.5f, 198.5f},
{114.5f, 198.5f},
{109.5f, 203.5f},
{114.5f, 203.5f},
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(Containers::arrayView(textureCoordinates), Containers::arrayView<Vector2>({
{0.0f, 0.0f},
{1.0f, 0.0f},
{0.0f, 0.5f},
{1.0f, 0.5f},
{0.0f, 0.5f},
{0.5f, 0.5f},
{0.0f, 1.0f},
{0.5f, 1.0f},
{0.5f, 0.5f},
{1.0f, 0.5f},
{0.5f, 1.0f},
{1.0f, 1.0f},
}), TestSuite::Compare::Container);
}
void RendererTest::lineGlyphQuads2DArrayGlyphCache() {
CORRADE_SKIP_IF_NO_ASSERT();
TestFont font;
struct: AbstractGlyphCache {
using AbstractGlyphCache::AbstractGlyphCache;
GlyphCacheFeatures doFeatures() const override { return {}; }
} cache{PixelFormat::R8Unorm, {20, 20, 2}};
std::ostringstream out;
Error redirectError{&out};
renderGlyphQuadsInto(font, 10.0f, cache, nullptr, nullptr, nullptr, Containers::StridedArrayView1D<Vector2>{});
CORRADE_COMPARE(out.str(), "Text::renderGlyphQuadsInto(): can't use this overload with an array glyph cache\n");
}
void RendererTest::alignLine() {
auto&& data = AlignLineData[testCaseInstanceId()];
setTestCaseDescription(data.name);
Range2D rectangle{{10.0f, 200.0f}, {13.5f, -960.0f}};
/* The positions aren't taken into account, so they can be arbitrary */
Vector2 positions[]{
{100.0f, 200.0f},
{300.0f, -60.0f},
{-10.0f, 100.0f},
};
Range2D alignedRectangle = alignRenderedLine(rectangle, LayoutDirection::HorizontalTopToBottom, data.alignment, positions);
CORRADE_COMPARE(alignedRectangle, rectangle.translated({data.offset, 0.0f}));
CORRADE_COMPARE_AS(Containers::arrayView(positions), Containers::arrayView<Vector2>({
{100.0f + data.offset, 200.0f},
{300.0f + data.offset, -60.0f},
{-10.0f + data.offset, 100.0f}
}), TestSuite::Compare::Container);
}
void RendererTest::alignLineInvalidDirection() {
CORRADE_SKIP_IF_NO_ASSERT();
std::ostringstream out;
Error redirectError{&out};
alignRenderedLine({}, LayoutDirection::VerticalRightToLeft, Alignment::LineLeft, nullptr);
CORRADE_COMPARE(out.str(), "Text::alignRenderedLine(): only Text::LayoutDirection::HorizontalTopToBottom is supported right now, got Text::LayoutDirection::VerticalRightToLeft\n");
}
void RendererTest::alignBlock() {
auto&& data = AlignBlockData[testCaseInstanceId()];
setTestCaseDescription(data.name);
Range2D rectangle{{100.0f, 9.5f}, {-70.0f, 19.5f}};
/* The positions aren't taken into account, so they can be arbitrary */
Vector2 positions[]{
{100.0f, 200.0f},
{-10.0f, 100.0f},
{300.0f, -60.0f},
};
Range2D alignedRectangle = alignRenderedBlock(rectangle, LayoutDirection::HorizontalTopToBottom, data.alignment, positions);
CORRADE_COMPARE(alignedRectangle, rectangle.translated({0.0f, data.offset}));
CORRADE_COMPARE_AS(Containers::arrayView(positions), Containers::arrayView<Vector2>({
{100.0f, 200.0f + data.offset},
{-10.0f, 100.0f + data.offset},
{300.0f, -60.0f + data.offset},
}), TestSuite::Compare::Container);
}
void RendererTest::alignBlockInvalidDirection() {
CORRADE_SKIP_IF_NO_ASSERT();
std::ostringstream out;
Error redirectError{&out};
alignRenderedBlock({}, LayoutDirection::VerticalRightToLeft, Alignment::LineLeft, nullptr);
CORRADE_COMPARE(out.str(), "Text::alignRenderedBlock(): only Text::LayoutDirection::HorizontalTopToBottom is supported right now, got Text::LayoutDirection::VerticalRightToLeft\n");
}
template<class T> void RendererTest::glyphQuadIndices() {
setTestCaseTemplateName(Math::TypeTraits<T>::name());
/* 2---3 2 3---5
| | |\ \ |
| | | \ \ |
| | | \ \|
0---1 0---1 4 */
T indices[3*6];
renderGlyphQuadIndicesInto(60, indices);
CORRADE_COMPARE_AS(Containers::arrayView(indices), Containers::arrayView<T>({
240, 241, 242, 242, 241, 243,
244, 245, 246, 246, 245, 247,
248, 249, 250, 250, 249, 251
}), TestSuite::Compare::Container);
}
void RendererTest::glyphQuadIndicesTypeTooSmall() {
CORRADE_SKIP_IF_NO_ASSERT();
/* This should be fine */
UnsignedByte indices8[18];
UnsignedShort indices16[18];
UnsignedInt indices32[18];
renderGlyphQuadIndicesInto(256/4 - 3, indices8);
renderGlyphQuadIndicesInto(65536/4 - 3, indices16);
renderGlyphQuadIndicesInto(4294967296u/4 - 3, indices32);
CORRADE_COMPARE(indices8[17], 255);
CORRADE_COMPARE(indices16[17], 65535);
CORRADE_COMPARE(indices32[17], 4294967295);
/* Empty view also */
renderGlyphQuadIndicesInto(256/4, Containers::ArrayView<UnsignedByte>{});
renderGlyphQuadIndicesInto(65536/4, Containers::ArrayView<UnsignedShort>{});
renderGlyphQuadIndicesInto(4294967296u/4, Containers::ArrayView<UnsignedInt>{});
std::ostringstream out;
Error redirectError{&out};
renderGlyphQuadIndicesInto(256/4 - 3 + 1, indices8);
renderGlyphQuadIndicesInto(65536/4 - 3 + 1, indices16);
renderGlyphQuadIndicesInto(4294967296u/4 - 3 + 1, indices32);
/* Should assert even if there's actually no indices to write */
renderGlyphQuadIndicesInto(256/4 + 1, Containers::ArrayView<UnsignedByte>{});
renderGlyphQuadIndicesInto(65536/4 + 1, Containers::ArrayView<UnsignedShort>{});
renderGlyphQuadIndicesInto(4294967296u/4 + 1, Containers::ArrayView<UnsignedInt>{});
CORRADE_COMPARE(out.str(),
"Text::renderGlyphQuadIndicesInto(): max index value of 259 cannot fit into a 8-bit type\n"
"Text::renderGlyphQuadIndicesInto(): max index value of 65539 cannot fit into a 16-bit type\n"
"Text::renderGlyphQuadIndicesInto(): max index value of 4294967299 cannot fit into a 32-bit type\n"
"Text::renderGlyphQuadIndicesInto(): max index value of 259 cannot fit into a 8-bit type\n"
"Text::renderGlyphQuadIndicesInto(): max index value of 65539 cannot fit into a 16-bit type\n"
"Text::renderGlyphQuadIndicesInto(): max index value of 4294967299 cannot fit into a 32-bit type\n");
}
void RendererTest::renderData() {
auto&& data = RenderDataData[testCaseInstanceId()];
setTestCaseDescription(data.name);
@ -301,32 +878,32 @@ void RendererTest::renderData() {
+-+
+-+ |c|
0---2 |b| +-+
2---3 |b| +-+
| a | +-+
1---3 */
0---1 */
CORRADE_COMPARE_AS(positions, (std::vector<Vector2>{
/* Cursor is {0, 0}. Offset from the cache is {5, 10}, offset from the
renderer is {0, 1}, size is {20, 10}; all scaled by 0.5 */
Vector2{ 2.5f, 10.5f} + data.offset,
Vector2{ 2.5f, 5.5f} + data.offset,
Vector2{12.5f, 10.5f} + data.offset,
Vector2{12.5f, 5.5f} + data.offset,
Vector2{ 2.5f, 10.5f} + data.offset,
Vector2{12.5f, 10.5f} + data.offset,
/* Advance was {1, 0.5}, cursor is {1, 0.5}. Offset from the cache is
{10, 5}, offset from the renderer is {0, 2}, size is {10, 10}; all
scaled by 0.5 */
Vector2{ 5.5f, 8.75f} + data.offset,
Vector2{ 5.5f, 3.75f} + data.offset,
Vector2{10.5f, 8.75f} + data.offset,
Vector2{10.5f, 3.75f} + data.offset,
Vector2{ 5.5f, 8.75f} + data.offset,
Vector2{10.5f, 8.75f} + data.offset,
/* Advance was {2, -0.5}, cursor is {3, 0}. Offset from the cache is
{5, 5}, offset from the renderer is {0, 3}, size is {10, 10}; all
scaled by 0.5 */
Vector2{ 4.0f, 9.0f} + data.offset,
Vector2{ 4.0f, 4.0f} + data.offset,
Vector2{ 9.0f, 4.0f} + data.offset,
Vector2{ 4.0f, 9.0f} + data.offset,
Vector2{ 9.0f, 9.0f} + data.offset,
Vector2{ 9.0f, 4.0f} + data.offset
}), TestSuite::Compare::Container);
/* Bounds. Different depending on whether or not GlyphBounds alignment is
@ -340,36 +917,36 @@ void RendererTest::renderData() {
right.
+-+-+
|b|c|
0---2
2---3
| a |
1---3 */
0---1 */
CORRADE_COMPARE_AS(textureCoordinates, (std::vector<Vector2>{
{0.0f, 0.5f},
{0.0f, 0.0f},
{1.0f, 0.5f},
{1.0f, 0.0f},
{0.0f, 0.5f},
{1.0f, 0.5f},
{0.0f, 1.0f},
{0.0f, 0.5f},
{0.5f, 1.0f},
{0.5f, 0.5f},
{0.0f, 1.0f},
{0.5f, 1.0f},
{0.5f, 0.5f},
{1.0f, 0.5f},
{0.5f, 1.0f},
{1.0f, 1.0f},
{1.0f, 0.5f}
}), TestSuite::Compare::Container);
/* Indices
0---2 0---2 5
| | | / /|
| | | / / |
| | |/ / |
1---3 1 3---4 */
2---3 2 3---5
| | |\ \ |
| | | \ \ |
| | | \ \|
0---1 0---1 4 */
CORRADE_COMPARE_AS(indices, (std::vector<UnsignedInt>{
0, 1, 2, 1, 3, 2,
4, 5, 6, 5, 7, 6,
8, 9, 10, 9, 11, 10
0, 1, 2, 2, 1, 3,
4, 5, 6, 6, 5, 7,
8, 9, 10, 10, 9, 11,
}), TestSuite::Compare::Container);
}
@ -449,70 +1026,70 @@ void RendererTest::multiline() {
[g] [h] [i] */
CORRADE_COMPARE_AS(positions, (std::vector<Vector2>{
Vector2{0.0f, 1.0f} + data.offset0, /* a */
Vector2{0.0f, 0.0f} + data.offset0,
Vector2{1.0f, 1.0f} + data.offset0,
Vector2{0.0f, 0.0f} + data.offset0, /* a */
Vector2{1.0f, 0.0f} + data.offset0,
Vector2{0.0f, 1.0f} + data.offset0,
Vector2{1.0f, 1.0f} + data.offset0,
Vector2{2.0f, 1.0f} + data.offset0, /* b */
Vector2{2.0f, 0.0f} + data.offset0,
Vector2{3.0f, 1.0f} + data.offset0,
Vector2{2.0f, 0.0f} + data.offset0, /* b */
Vector2{3.0f, 0.0f} + data.offset0,
Vector2{2.0f, 1.0f} + data.offset0,
Vector2{3.0f, 1.0f} + data.offset0,
Vector2{4.0f, 1.0f} + data.offset0, /* c */
Vector2{4.0f, 0.0f} + data.offset0,
Vector2{5.0f, 1.0f} + data.offset0,
Vector2{4.0f, 0.0f} + data.offset0, /* c */
Vector2{5.0f, 0.0f} + data.offset0,
Vector2{4.0f, 1.0f} + data.offset0,
Vector2{5.0f, 1.0f} + data.offset0,
Vector2{6.0f, 1.0f} + data.offset0, /* d */
Vector2{6.0f, 0.0f} + data.offset0,
Vector2{7.0f, 1.0f} + data.offset0,
Vector2{6.0f, 0.0f} + data.offset0, /* d */
Vector2{7.0f, 0.0f} + data.offset0,
Vector2{6.0f, 1.0f} + data.offset0,
Vector2{7.0f, 1.0f} + data.offset0,
Vector2{0.0f, 1.0f} + data.offset1, /* e */
Vector2{0.0f, 0.0f} + data.offset1,
Vector2{1.0f, 1.0f} + data.offset1,
Vector2{0.0f, 0.0f} + data.offset1, /* e */
Vector2{1.0f, 0.0f} + data.offset1,
Vector2{0.0f, 1.0f} + data.offset1,
Vector2{1.0f, 1.0f} + data.offset1,
Vector2{2.0f, 1.0f} + data.offset1, /* f */
Vector2{2.0f, 0.0f} + data.offset1,
Vector2{3.0f, 1.0f} + data.offset1,
Vector2{2.0f, 0.0f} + data.offset1, /* f */
Vector2{3.0f, 0.0f} + data.offset1,
Vector2{2.0f, 1.0f} + data.offset1,
Vector2{3.0f, 1.0f} + data.offset1,
/* Two linebreaks here */
Vector2{0.0f, 1.0f} + data.offset2, /* g */
Vector2{0.0f, 0.0f} + data.offset2,
Vector2{1.0f, 1.0f} + data.offset2,
Vector2{0.0f, 0.0f} + data.offset2, /* g */
Vector2{1.0f, 0.0f} + data.offset2,
Vector2{0.0f, 1.0f} + data.offset2,
Vector2{1.0f, 1.0f} + data.offset2,
Vector2{2.0f, 1.0f} + data.offset2, /* h */
Vector2{2.0f, 0.0f} + data.offset2,
Vector2{3.0f, 1.0f} + data.offset2,
Vector2{2.0f, 0.0f} + data.offset2, /* h */
Vector2{3.0f, 0.0f} + data.offset2,
Vector2{2.0f, 1.0f} + data.offset2,
Vector2{3.0f, 1.0f} + data.offset2,
Vector2{4.0f, 1.0f} + data.offset2, /* i */
Vector2{4.0f, 0.0f} + data.offset2,
Vector2{5.0f, 1.0f} + data.offset2,
Vector2{4.0f, 0.0f} + data.offset2, /* i */
Vector2{5.0f, 0.0f} + data.offset2,
Vector2{4.0f, 1.0f} + data.offset2,
Vector2{5.0f, 1.0f} + data.offset2,
}), TestSuite::Compare::Container);
/* Indices
0---2 0---2 5
| | | / /|
| | | / / |
| | |/ / |
1---3 1 3---4 */
2---3 2 3---5
| | |\ \ |
| | | \ \ |
| | | \ \|
0---1 0---1 4 */
CORRADE_COMPARE_AS(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
0, 1, 2, 2, 1, 3,
4, 5, 6, 6, 5, 7,
8, 9, 10, 10, 9, 11,
12, 13, 14, 14, 13, 15,
16, 17, 18, 18, 17, 19,
20, 21, 22, 22, 21, 23,
24, 25, 26, 26, 25, 27,
28, 29, 30, 30, 29, 31,
32, 33, 34, 34, 33, 35,
}), TestSuite::Compare::Container);
}

Loading…
Cancel
Save