Browse Source

Text: introduce Alignment::*Start and *End.

Based on the actual text direction (either explicitly set or detected),
these resolve to either *Left or *Right. For the Text::Renderer it's
done automatically inside (and there's no way to actually set the
direction from outside due to the API being ancient and limited), for
the align*() utils the alignment has to be explicitly resolved using a
new alignmentForDirection() utility.
pull/641/head
Vladimír Vondruš 2 years ago
parent
commit
bc33945d3d
  1. 5
      doc/changelog.dox
  2. 44
      src/Magnum/Text/Alignment.cpp
  3. 286
      src/Magnum/Text/Alignment.h
  4. 2
      src/Magnum/Text/CMakeLists.txt
  5. 36
      src/Magnum/Text/Renderer.cpp
  6. 10
      src/Magnum/Text/Renderer.h
  7. 82
      src/Magnum/Text/Test/AlignmentTest.cpp
  8. 2
      src/Magnum/Text/Test/CMakeLists.txt
  9. 108
      src/Magnum/Text/Test/RendererTest.cpp

5
doc/changelog.dox

@ -834,6 +834,11 @@ See also:
missing before, and @ref Text::Alignment::TopCenterIntegral, which is also
needed in order to prevent aligning pixel-perfect fonts to fractional
positions.
- @ref Text::Alignment gained special `*Start` and `*End` values for
alignment based on text direction, and a new
@ref Text::alignmentForDirection() utility that resolves those to concrete
`*Left` and `*Right` values based @ref Text::LayoutDirection and
@ref Text::ShapeDirection
- Added a @ref Text::GlyphCache::GlyphCache(NoCreateT) and
@ref Text::DistanceFieldGlyphCache::DistanceFieldGlyphCache(NoCreateT)
constructor allowing to construct the object without a GL context present

44
src/Magnum/Text/Alignment.cpp

@ -25,8 +25,11 @@
#include "Alignment.h"
#include <Corrade/Utility/Assert.h>
#include <Corrade/Utility/Debug.h>
#include "Magnum/Text/Direction.h"
namespace Magnum { namespace Text {
Debug& operator<<(Debug& debug, const Alignment value) {
@ -43,6 +46,10 @@ Debug& operator<<(Debug& debug, const Alignment value) {
_c(LineCenterGlyphBoundsIntegral)
_c(LineRight)
_c(LineRightGlyphBounds)
_c(LineStart)
_c(LineStartGlyphBounds)
_c(LineEnd)
_c(LineEndGlyphBounds)
_c(BottomLeft)
_c(BottomLeftGlyphBounds)
_c(BottomCenter)
@ -51,6 +58,10 @@ Debug& operator<<(Debug& debug, const Alignment value) {
_c(BottomCenterGlyphBoundsIntegral)
_c(BottomRight)
_c(BottomRightGlyphBounds)
_c(BottomStart)
_c(BottomStartGlyphBounds)
_c(BottomEnd)
_c(BottomEndGlyphBounds)
_c(MiddleLeft)
_c(MiddleLeftIntegral)
_c(MiddleLeftGlyphBounds)
@ -63,6 +74,14 @@ Debug& operator<<(Debug& debug, const Alignment value) {
_c(MiddleRightIntegral)
_c(MiddleRightGlyphBounds)
_c(MiddleRightGlyphBoundsIntegral)
_c(MiddleStart)
_c(MiddleStartIntegral)
_c(MiddleStartGlyphBounds)
_c(MiddleStartGlyphBoundsIntegral)
_c(MiddleEnd)
_c(MiddleEndIntegral)
_c(MiddleEndGlyphBounds)
_c(MiddleEndGlyphBoundsIntegral)
_c(TopLeft)
_c(TopLeftGlyphBounds)
_c(TopCenter)
@ -71,6 +90,10 @@ Debug& operator<<(Debug& debug, const Alignment value) {
_c(TopCenterGlyphBoundsIntegral)
_c(TopRight)
_c(TopRightGlyphBounds)
_c(TopStart)
_c(TopStartGlyphBounds)
_c(TopEnd)
_c(TopEndGlyphBounds)
#undef _c
/* LCOV_EXCL_STOP */
}
@ -78,4 +101,25 @@ Debug& operator<<(Debug& debug, const Alignment value) {
return debug << "(" << Debug::nospace << Debug::hex << UnsignedByte(value) << Debug::nospace << ")";
}
Alignment alignmentForDirection(const Alignment alignment, const LayoutDirection layoutDirection, const ShapeDirection shapeDirection) {
CORRADE_ASSERT(layoutDirection == LayoutDirection::HorizontalTopToBottom,
"Text::alignmentForDirection(): only" << LayoutDirection::HorizontalTopToBottom << "is supported right now, got" << layoutDirection, {});
CORRADE_ASSERT(shapeDirection != ShapeDirection::TopToBottom &&
shapeDirection != ShapeDirection::BottomToTop,
"Text::alignmentForDirection():" << shapeDirection << "is not supported yet, sorry", {});
const UnsignedByte horizontal = UnsignedByte(alignment) & Implementation::AlignmentHorizontal;
const UnsignedByte exceptHorizontal = UnsignedByte(alignment) & ~Implementation::AlignmentHorizontal;
if(horizontal == Implementation::AlignmentStart)
return Alignment((shapeDirection == ShapeDirection::RightToLeft ?
Implementation::AlignmentRight :
Implementation::AlignmentLeft)|exceptHorizontal);
if(horizontal == Implementation::AlignmentEnd)
return Alignment((shapeDirection == ShapeDirection::RightToLeft ?
Implementation::AlignmentLeft :
Implementation::AlignmentRight)|exceptHorizontal);
return alignment;
}
}}

286
src/Magnum/Text/Alignment.h

@ -26,10 +26,11 @@
*/
/** @file
* @brief Enum @ref Magnum::Text::Alignment
* @brief Enum @ref Magnum::Text::Alignment, function @ref Magnum::Text::alignmentForDirection()
*/
#include "Magnum/Magnum.h"
#include "Magnum/Text/Text.h"
#include "Magnum/Text/visibility.h"
namespace Magnum { namespace Text {
@ -42,16 +43,20 @@ namespace Implementation {
AlignmentLeft = 0,
AlignmentCenter = 1 << 0,
AlignmentRight = 2 << 0,
AlignmentHorizontal = AlignmentLeft|AlignmentCenter|AlignmentRight,
/* Start and End is Left or Right based on ShapeDirection, and possibly
also Top / Bottom eventually for vertical text */
AlignmentStart = 3 << 0,
AlignmentEnd = 4 << 0,
AlignmentHorizontal = AlignmentLeft|AlignmentCenter|AlignmentRight|AlignmentStart|AlignmentEnd,
AlignmentLine = 0,
AlignmentBottom = 1 << 2,
AlignmentMiddle = 2 << 2,
AlignmentTop = 3 << 2,
AlignmentBottom = 1 << 4,
AlignmentMiddle = 2 << 4,
AlignmentTop = 3 << 4,
AlignmentVertical = AlignmentLine|AlignmentBottom|AlignmentMiddle|AlignmentTop,
AlignmentIntegral = 1 << 4,
AlignmentGlyphBounds = 1 << 5
AlignmentIntegral = 1 << 6,
AlignmentGlyphBounds = 1 << 7
};
}
@ -71,19 +76,31 @@ positioned on whole pixels as well in order to avoid making them blurry. These
are only needed for `*Middle` and `*Center` alignments as they may result in
the bounding rectangle to have odd dimensions, which would then result in
half-pixel shifts when divided by 2.
@see @ref Renderer::render(), @ref Renderer::Renderer()
The `*Start` and `*End` values behave the same as `*Left` and `*Right`,
respectively, if @ref ShapeDirection::LeftToRight is passed to
@ref AbstractShaper::setDirection(), or if it's
@ref ShapeDirection::Unspecified and the particular font plugin doesn't detect
@ref ShapeDirection::RightToLeft when shaping. If
@ref ShapeDirection::RightToLeft is set (or detected for
@ref ShapeDirection::Unspecified), they're swapped, i.e. `*Start` becomes
`*Right` and `*End` becomes `*Left`.
@see @ref Renderer::render(), @ref Renderer::Renderer(),
@see @ref alignmentForDirection()
*/
enum class Alignment: UnsignedByte {
/**
* Leftmost cursor position and vertical line position is at origin.
* @see @ref Alignment::LineLeftGlyphBounds
* @see @ref Alignment::LineLeftGlyphBounds, @ref Alignment::LineStart,
* @ref Alignment::LineEnd
*/
LineLeft = Implementation::AlignmentLine|Implementation::AlignmentLeft,
/**
* Left side of the glyph bounding rectangle and vertical line position is
* at origin.
* @see @ref Alignment::LineLeft
* @see @ref Alignment::LineLeft, @ref Alignment::LineStartGlyphBounds,
* @ref Alignment::LineEndGlyphBounds
* @m_since_latest
*/
LineLeftGlyphBounds = LineLeft|Implementation::AlignmentGlyphBounds,
@ -126,28 +143,68 @@ enum class Alignment: UnsignedByte {
/**
* Rightmost cursor position and vertical line position is at origin.
* @see @ref Alignment::LineRightGlyphBounds
* @see @ref Alignment::LineRightGlyphBounds, @ref Alignment::LineEnd,
* @ref Alignment::LineStart
* @see @ref alignmentForDirection()
*/
LineRight = Implementation::AlignmentLine|Implementation::AlignmentRight,
/**
* Right side of the glyph bounding rectangle and vertical line position is
* at origin.
* @see @ref Alignment::LineRight
* @see @ref Alignment::LineRight, @ref Alignment::LineEndGlyphBounds,
* @ref Alignment::LineStartGlyphBounds
* @see @ref alignmentForDirection()
* @m_since_latest
*/
LineRightGlyphBounds = LineRight|Implementation::AlignmentGlyphBounds,
/**
* @ref Alignment::LineRight for @ref ShapeDirection::RightToLeft,
* @ref Alignment::LineLeft otherwise.
* @see @ref alignmentForDirection()
* @m_since_latest
*/
LineStart = Implementation::AlignmentLine|Implementation::AlignmentStart,
/**
* @ref Alignment::LineRightGlyphBounds for
* @ref ShapeDirection::RightToLeft, @ref Alignment::LineLeftGlyphBounds
* otherwise.
* @see @ref alignmentForDirection()
* @m_since_latest
*/
LineStartGlyphBounds = LineStart|Implementation::AlignmentGlyphBounds,
/**
* @ref Alignment::LineLeft for @ref ShapeDirection::RightToLeft,
* @ref Alignment::LineRight otherwise.
* @see @ref alignmentForDirection()
* @m_since_latest
*/
LineEnd = Implementation::AlignmentLine|Implementation::AlignmentEnd,
/**
* @ref Alignment::LineLeftGlyphBounds for
* @ref ShapeDirection::RightToLeft, @ref Alignment::LineRightGlyphBounds
* otherwise.
* @see @ref alignmentForDirection()
* @m_since_latest
*/
LineEndGlyphBounds = LineEnd|Implementation::AlignmentGlyphBounds,
/**
* Leftmost cursor position and bottommost line descent is at origin.
* @see @ref Alignment::BottomLeftGlyphBounds
* @see @ref Alignment::BottomLeftGlyphBounds, @ref Alignment::BottomStart,
* @ref Alignment::BottomEnd
* @m_since_latest
*/
BottomLeft = Implementation::AlignmentBottom|Implementation::AlignmentLeft,
/**
* Bottom left corner of the glyph bounding rectangle is at origin.
* @see @ref Alignment::BottomLeft
* @see @ref Alignment::BottomLeft, @ref Alignment::BottomStartGlyphBounds,
* @ref Alignment::BottomEndGlyphBounds
* @m_since_latest
*/
BottomLeftGlyphBounds = BottomLeft|Implementation::AlignmentGlyphBounds,
@ -191,23 +248,60 @@ enum class Alignment: UnsignedByte {
/**
* Rightmost cursor position and bottommost line descent is at origin.
* @see @ref Alignment::BottomRightGlyphBounds
* @see @ref Alignment::BottomRightGlyphBounds, @ref Alignment::BottomEnd,
* @ref Alignment::BottomStart
* @m_since_latest
*/
BottomRight = Implementation::AlignmentBottom|Implementation::AlignmentRight,
/**
* Bottom right corner of the glyph bounding rectangle is at origin.
* @see @ref Alignment::BottomRight
* @see @ref Alignment::BottomRight, @ref Alignment::BottomEndGlyphBounds,
* @ref Alignment::BottomStartGlyphBounds
* @m_since_latest
*/
BottomRightGlyphBounds = BottomRight|Implementation::AlignmentGlyphBounds,
/**
* @ref Alignment::BottomRight for @ref ShapeDirection::RightToLeft,
* @ref Alignment::BottomLeft otherwise.
* @see @ref alignmentForDirection()
* @m_since_latest
*/
BottomStart = Implementation::AlignmentBottom|Implementation::AlignmentStart,
/**
* @ref Alignment::BottomRightGlyphBounds for
* @ref ShapeDirection::RightToLeft, @ref Alignment::BottomLeftGlyphBounds
* otherwise.
* @see @ref alignmentForDirection()
* @m_since_latest
*/
BottomStartGlyphBounds = BottomStart|Implementation::AlignmentGlyphBounds,
/**
* @ref Alignment::BottomLeft for @ref ShapeDirection::RightToLeft,
* @ref Alignment::BottomRight otherwise.
* @see @ref alignmentForDirection()
* @m_since_latest
*/
BottomEnd = Implementation::AlignmentBottom|Implementation::AlignmentEnd,
/**
* @ref Alignment::BottomLeftGlyphBounds for
* @ref ShapeDirection::RightToLeft, @ref Alignment::BottomRightGlyphBounds
* otherwise.
* @see @ref alignmentForDirection()
* @m_since_latest
*/
BottomEndGlyphBounds = BottomEnd|Implementation::AlignmentGlyphBounds,
/**
* Leftmost cursor position and a midpoint between topmost line ascent and
* bottommost line descent is at origin.
* @see @ref Alignment::MiddleLeftGlyphBounds,
* @ref Alignment::MiddleLeftIntegral
* @ref Alignment::MiddleLeftIntegral, @ref Alignment::MiddleStart,
* @ref Alignment::MiddleEnd
*/
MiddleLeft = Implementation::AlignmentMiddle|Implementation::AlignmentLeft,
@ -216,7 +310,9 @@ enum class Alignment: UnsignedByte {
* bottommost line descent is at origin, with the vertical offset rounded
* to whole units.
* @see @ref Alignment::MiddleLeft,
* @ref Alignment::MiddleLeftGlyphBoundsIntegral
* @ref Alignment::MiddleLeftGlyphBoundsIntegral,
* @ref Alignment::MiddleStartIntegral,
* @ref Alignment::MiddleEndIntegral
*/
MiddleLeftIntegral = MiddleLeft|Implementation::AlignmentIntegral,
@ -224,7 +320,9 @@ enum class Alignment: UnsignedByte {
* Left side and vertical center of the glyph bounding rectangle is at
* origin.
* @see @ref Alignment::MiddleLeft,
* @ref Alignment::MiddleLeftGlyphBoundsIntegral
* @ref Alignment::MiddleLeftGlyphBoundsIntegral,
* @ref Alignment::MiddleStartGlyphBounds,
* @ref Alignment::MiddleEndGlyphBounds
* @m_since_latest
*/
MiddleLeftGlyphBounds = MiddleLeft|Implementation::AlignmentGlyphBounds,
@ -233,7 +331,9 @@ enum class Alignment: UnsignedByte {
* Left side and vertical center of the glyph bounding rectangle is at
* origin, with the vertical offset rounded to whole units.
* @see @ref Alignment::MiddleLeftGlyphBounds,
* @ref Alignment::MiddleLeftIntegral
* @ref Alignment::MiddleLeftIntegral,
* @ref Alignment::MiddleStartGlyphBoundsIntegral,
* @ref Alignment::MiddleEndGlyphBoundsIntegral
* @m_since_latest
*/
MiddleLeftGlyphBoundsIntegral = MiddleLeftGlyphBounds|Implementation::AlignmentIntegral,
@ -278,7 +378,8 @@ enum class Alignment: UnsignedByte {
* Rightmost cursor position and a midpoint between topmost line ascent and
* bottommost line descent is at origin.
* @see @ref Alignment::MiddleRightGlyphBounds,
* @ref Alignment::MiddleRightIntegral
* @ref Alignment::MiddleRightIntegral, @ref Alignment::MiddleEnd,
* @ref Alignment::MiddleStart
*/
MiddleRight = Implementation::AlignmentMiddle|Implementation::AlignmentRight,
@ -287,7 +388,9 @@ enum class Alignment: UnsignedByte {
* bottommost line descent is at origin, with the vertical offset rounded
* to whole units.
* @see @ref Alignment::MiddleRight,
* @ref Alignment::MiddleRightGlyphBoundsIntegral
* @ref Alignment::MiddleRightGlyphBoundsIntegral,
* @ref Alignment::MiddleEndIntegral,
* @ref Alignment::MiddleStartIntegral
*/
MiddleRightIntegral = MiddleRight|Implementation::AlignmentIntegral,
@ -295,7 +398,9 @@ enum class Alignment: UnsignedByte {
* Right side and vertical center of the glyph bounding rectangle is at
* origin.
* @see @ref Alignment::MiddleRight,
* @ref Alignment::MiddleRightGlyphBoundsIntegral
* @ref Alignment::MiddleRightGlyphBoundsIntegral,
* @ref Alignment::MiddleEndGlyphBounds,
* @ref Alignment::MiddleStartGlyphBounds
* @m_since_latest
*/
MiddleRightGlyphBounds = MiddleRight|Implementation::AlignmentGlyphBounds,
@ -304,20 +409,94 @@ enum class Alignment: UnsignedByte {
* Right side and vertical center of the glyph bounding rectangle is at
* origin, with the vertical offset rounded to whole units.
* @see @ref Alignment::MiddleRightGlyphBounds,
* @ref Alignment::MiddleRightIntegral
* @ref Alignment::MiddleRightIntegral,
* @ref Alignment::MiddleEndGlyphBoundsIntegral,
* @ref Alignment::MiddleStartGlyphBoundsIntegral
* @m_since_latest
*/
MiddleRightGlyphBoundsIntegral = MiddleRightGlyphBounds|Implementation::AlignmentIntegral,
/**
* @ref Alignment::MiddleRight for @ref ShapeDirection::RightToLeft,
* @ref Alignment::MiddleLeft otherwise.
* @see @ref alignmentForDirection()
* @m_since_latest
*/
MiddleStart = Implementation::AlignmentMiddle|Implementation::AlignmentStart,
/**
* @ref Alignment::MiddleRightIntegral for
* @ref ShapeDirection::RightToLeft, @ref Alignment::MiddleLeftIntegral
* otherwise.
* @see @ref alignmentForDirection()
* @m_since_latest
*/
MiddleStartIntegral = MiddleStart|Implementation::AlignmentIntegral,
/**
* @ref Alignment::MiddleRightGlyphBounds for
* @ref ShapeDirection::RightToLeft, @ref Alignment::MiddleLeftGlyphBounds
* otherwise.
* @see @ref alignmentForDirection()
* @m_since_latest
*/
MiddleStartGlyphBounds = MiddleStart|Implementation::AlignmentGlyphBounds,
/**
* @ref Alignment::MiddleRightGlyphBoundsIntegral for
* @ref ShapeDirection::RightToLeft,
* @ref Alignment::MiddleLeftGlyphBoundsIntegral otherwise.
* @see @ref alignmentForDirection()
* @m_since_latest
*/
MiddleStartGlyphBoundsIntegral = MiddleStartGlyphBounds|Implementation::AlignmentIntegral,
/**
* @ref Alignment::MiddleLeft for @ref ShapeDirection::RightToLeft,
* @ref Alignment::MiddleRight otherwise.
* @see @ref alignmentForDirection()
* @m_since_latest
*/
MiddleEnd = Implementation::AlignmentMiddle|Implementation::AlignmentEnd,
/**
* @ref Alignment::MiddleLeftIntegral for
* @ref ShapeDirection::RightToLeft, @ref Alignment::MiddleRightIntegral
* otherwise.
* @see @ref alignmentForDirection()
* @m_since_latest
*/
MiddleEndIntegral = MiddleEnd|Implementation::AlignmentIntegral,
/**
* @ref Alignment::MiddleLeftGlyphBounds for
* @ref ShapeDirection::RightToLeft, @ref Alignment::MiddleRightGlyphBounds
* otherwise.
* @see @ref alignmentForDirection()
* @m_since_latest
*/
MiddleEndGlyphBounds = MiddleEnd|Implementation::AlignmentGlyphBounds,
/**
* @ref Alignment::MiddleLeftGlyphBoundsIntegral for
* @ref ShapeDirection::RightToLeft,
* @ref Alignment::MiddleRightGlyphBoundsIntegral otherwise.
* @see @ref alignmentForDirection()
* @m_since_latest
*/
MiddleEndGlyphBoundsIntegral = MiddleEndGlyphBounds|Implementation::AlignmentIntegral,
/**
* Leftmost cursor position and topmost line ascent is at origin.
* @see @ref Alignment::TopLeftGlyphBounds
* @see @ref Alignment::TopLeftGlyphBounds, @ref Alignment::TopStart,
* @ref Alignment::TopEnd
*/
TopLeft = Implementation::AlignmentTop|Implementation::AlignmentLeft,
/**
* Top left corner of the glyph bounding rectangle is at origin.
* @see @ref Alignment::TopLeft
* @see @ref Alignment::TopLeft, @ref Alignment::TopStartGlyphBounds,
* @ref Alignment::TopEndGlyphBounds
* @m_since_latest
*/
TopLeftGlyphBounds = TopLeft|Implementation::AlignmentGlyphBounds,
@ -359,21 +538,72 @@ enum class Alignment: UnsignedByte {
/**
* Rightmost cursor position and topmost line ascent is at origin.
* @see @ref Alignment::TopRightGlyphBounds
* @see @ref Alignment::TopRightGlyphBounds, @ref Alignment::TopEnd,
* @ref Alignment::TopStart
*/
TopRight = Implementation::AlignmentTop|Implementation::AlignmentRight,
/**
* Top right corner of the glyph bounding rectangle is at origin.
* @see @ref Alignment::TopRight
* @see @ref Alignment::TopRight, @ref Alignment::TopEndGlyphBounds,
* @ref Alignment::TopStartGlyphBounds
* @m_since_latest
*/
TopRightGlyphBounds = TopRight|Implementation::AlignmentGlyphBounds,
/**
* @ref Alignment::TopRight for @ref ShapeDirection::RightToLeft,
* @ref Alignment::TopLeft otherwise.
* @see @ref alignmentForDirection()
* @m_since_latest
*/
TopStart = Implementation::AlignmentTop|Implementation::AlignmentStart,
/**
* @ref Alignment::TopRightGlyphBounds for
* @ref ShapeDirection::RightToLeft, @ref Alignment::TopLeftGlyphBounds
* otherwise.
* @see @ref alignmentForDirection()
* @m_since_latest
*/
TopStartGlyphBounds = TopStart|Implementation::AlignmentGlyphBounds,
/**
* @ref Alignment::TopLeft for @ref ShapeDirection::RightToLeft,
* @ref Alignment::TopRight otherwise.
* @see @ref alignmentForDirection()
* @m_since_latest
*/
TopEnd = Implementation::AlignmentTop|Implementation::AlignmentEnd,
/**
* @ref Alignment::TopLeftGlyphBounds for
* @ref ShapeDirection::RightToLeft, @ref Alignment::TopRightGlyphBounds
* otherwise.
* @see @ref alignmentForDirection()
* @m_since_latest
*/
TopEndGlyphBounds = TopEnd|Implementation::AlignmentGlyphBounds,
};
/** @debugoperatorenum{Alignment} */
MAGNUM_TEXT_EXPORT Debug& operator<<(Debug& debug, Alignment value);
/**
@brief Alignment for layout and shape direction
@m_since_latest
The @p layoutDirection is currently expected to always be
@ref LayoutDirection::HorizontalTopToBottom and @p shapeDirection never
@ref ShapeDirection::TopToBottom or @ref ShapeDirection::BottomToTop. Then, if
@p alignment is `*Start` or `*End`, it's converted to `*Left` or `*Right`,
respectively, if @p shapeDirection is @ref ShapeDirection::LeftToRight or
@ref ShapeDirection::Unspecified, and `*Right` or `*Left`, respectively, if
@p shapeDirection is @ref ShapeDirection::RightToLeft.
*/
MAGNUM_TEXT_EXPORT Alignment alignmentForDirection(Alignment alignment, LayoutDirection layoutDirection, ShapeDirection shapeDirection);
}}
#endif

2
src/Magnum/Text/CMakeLists.txt

@ -31,7 +31,6 @@ find_package(Corrade REQUIRED PluginManager)
# Files shared between main library and unit test library
set(MagnumText_SRCS
Alignment.cpp
Direction.cpp)
# Files compiled with different flags for main library and unit test library
@ -40,6 +39,7 @@ set(MagnumText_GracefulAssert_SRCS
AbstractFontConverter.cpp
AbstractGlyphCache.cpp
AbstractShaper.cpp
Alignment.cpp
Feature.cpp
Renderer.cpp
Script.cpp)

36
src/Magnum/Text/Renderer.cpp

@ -188,6 +188,10 @@ Range2D renderGlyphQuadsInto(const AbstractGlyphCache& cache, const Float scale,
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, {});
CORRADE_ASSERT(
(UnsignedByte(alignment) & Implementation::AlignmentHorizontal) != Implementation::AlignmentStart &&
(UnsignedByte(alignment) & Implementation::AlignmentHorizontal) != Implementation::AlignmentEnd,
"Text::alignRenderedLine():" << alignment << "has to be resolved to *Left / *Right before being passed to this function", {});
#ifdef CORRADE_NO_ASSERT
static_cast<void>(direction); /** @todo drop once implemented */
#endif
@ -218,6 +222,10 @@ Range2D alignRenderedLine(const Range2D& lineRectangle, const LayoutDirection di
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, {});
CORRADE_ASSERT(
(UnsignedByte(alignment) & Implementation::AlignmentHorizontal) != Implementation::AlignmentStart &&
(UnsignedByte(alignment) & Implementation::AlignmentHorizontal) != Implementation::AlignmentEnd,
"Text::alignRenderedBlock():" << alignment << "has to be resolved to *Left / *Right before being passed to this function", {});
#ifdef CORRADE_NO_ASSERT
static_cast<void>(direction); /** @todo drop once implemented */
#endif
@ -335,6 +343,12 @@ std::tuple<std::vector<Vertex>, Range2D> renderVerticesInternal(AbstractFont& fo
/** @todo even with reusing a shaper this is all horrific, rework!! */
Containers::Pointer<AbstractShaper> shaper = font.createShaper();
/* Start/End alignment resolved based on what the shaper detects for the
first line. Not great, but can't do much better with this old limited
API. */
/** @todo rework all this, again */
Containers::Optional<Alignment> resolvedAlignment;
/* Render each line separately and align it horizontally */
std::size_t pos, prevPos = 0;
do {
@ -404,14 +418,30 @@ std::tuple<std::vector<Vertex>, Range2D> renderVerticesInternal(AbstractFont& fo
lineVertices.slice(&Vertex::position),
lineVertices.slice(&Vertex::textureCoordinates));
/* Resolve the alignment based on what the shaper detected (if
anything). Assume there are no font plugins that would produce
vertical shape direction by default. */
/** @todo drop all this once the shaper instance is configurable from
outside */
if(!resolvedAlignment) {
const ShapeDirection shapeDirection = shaper->direction();
CORRADE_INTERNAL_ASSERT(
shapeDirection != ShapeDirection::TopToBottom &&
shapeDirection != ShapeDirection::BottomToTop);
resolvedAlignment = alignmentForDirection(alignment,
/** @todo direction hardcoded here */
LayoutDirection::HorizontalTopToBottom,
shapeDirection);
}
/* Horizontally align the line, using either of the rectangles based on
which alignment is desired */
const Range2D alignedLineRectangle = alignRenderedLine(
UnsignedByte(alignment) & Implementation::AlignmentGlyphBounds ?
UnsignedByte(*resolvedAlignment) & Implementation::AlignmentGlyphBounds ?
lineQuadRectangle : lineRectangle,
/** @todo direction hardcoded here */
LayoutDirection::HorizontalTopToBottom,
alignment,
*resolvedAlignment,
lineVertices.slice(&Vertex::position));
/* Extend the rectangle with final line bounds */
@ -426,7 +456,7 @@ std::tuple<std::vector<Vertex>, Range2D> renderVerticesInternal(AbstractFont& fo
rectangle,
/** @todo direction hardcoded here */
LayoutDirection::HorizontalTopToBottom,
alignment,
*resolvedAlignment,
Containers::stridedArrayView(vertices).slice(&Vertex::position));
return std::make_tuple(Utility::move(vertices), alignedRectangle);

10
src/Magnum/Text/Renderer.h

@ -212,6 +212,11 @@ MAGNUM_TEXT_EXPORT Range2D renderGlyphQuadsInto(const AbstractGlyphCache& cache,
the alignment.
@m_since_latest
Expects that @p alignment isn't `*Start` or `*End`, those values have to be
resolved to `*Left` or `*Right` based on desired or detected
@ref ShapeDirection using @ref alignmentForDirection() before being passed to
this function.
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
@ -244,6 +249,11 @@ MAGNUM_TEXT_EXPORT Range2D alignRenderedLine(const Range2D& lineRectangle, Layou
advance based on the alignment.
@m_since_latest
Expects that @p alignment isn't `*Start` or `*End`, those values have to be
resolved to `*Left` or `*Right` based on desired or detected
@ref ShapeDirection using @ref alignmentForDirection() before being passed to
this function.
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().

82
src/Magnum/Text/Test/AlignmentTest.cpp

@ -24,10 +24,13 @@
*/
#include <sstream>
#include <Corrade/Containers/StringStl.h> /** @todo remove once Debug is stream-free */
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/TestSuite/Compare/String.h>
#include <Corrade/Utility/DebugStl.h> /** @todo remove once Debug is stream-free */
#include "Magnum/Text/Alignment.h"
#include "Magnum/Text/Direction.h"
namespace Magnum { namespace Text { namespace Test { namespace {
@ -35,10 +38,16 @@ struct AlignmentTest: TestSuite::Tester {
explicit AlignmentTest();
void debug();
void forDirection();
void forDirectionInvalid();
};
AlignmentTest::AlignmentTest() {
addTests({&AlignmentTest::debug});
addTests({&AlignmentTest::debug,
&AlignmentTest::forDirection,
&AlignmentTest::forDirectionInvalid});
}
void AlignmentTest::debug() {
@ -47,6 +56,77 @@ void AlignmentTest::debug() {
CORRADE_COMPARE(out.str(), "Text::Alignment::MiddleRightGlyphBounds Text::Alignment(0xab)\n");
}
void AlignmentTest::forDirection() {
/* For alignment that's neither Start nor End it's just a passthrough */
CORRADE_COMPARE(alignmentForDirection(
Alignment::BottomRightGlyphBounds,
LayoutDirection::HorizontalTopToBottom,
ShapeDirection::RightToLeft
), Alignment::BottomRightGlyphBounds);
CORRADE_COMPARE(alignmentForDirection(
Alignment::MiddleLeftIntegral,
LayoutDirection::HorizontalTopToBottom,
ShapeDirection::RightToLeft
), Alignment::MiddleLeftIntegral);
CORRADE_COMPARE(alignmentForDirection(
Alignment::TopCenter,
LayoutDirection::HorizontalTopToBottom,
ShapeDirection::LeftToRight
), Alignment::TopCenter);
/* For Start / End it resolves based on ShapeDirection, keeping all extra
bits as well */
CORRADE_COMPARE(alignmentForDirection(
Alignment::TopStart,
LayoutDirection::HorizontalTopToBottom,
ShapeDirection::LeftToRight
), Alignment::TopLeft);
CORRADE_COMPARE(alignmentForDirection(
Alignment::MiddleEndIntegral,
LayoutDirection::HorizontalTopToBottom,
ShapeDirection::LeftToRight
), Alignment::MiddleRightIntegral);
CORRADE_COMPARE(alignmentForDirection(
Alignment::MiddleStartGlyphBoundsIntegral,
LayoutDirection::HorizontalTopToBottom,
ShapeDirection::RightToLeft
), Alignment::MiddleRightGlyphBoundsIntegral);
CORRADE_COMPARE(alignmentForDirection(
Alignment::LineEndGlyphBounds,
LayoutDirection::HorizontalTopToBottom,
ShapeDirection::RightToLeft
), Alignment::LineLeftGlyphBounds);
/* Unspecified ShapeDirection behaves same as LeftToRight */
CORRADE_COMPARE(alignmentForDirection(
Alignment::BottomStart,
LayoutDirection::HorizontalTopToBottom,
ShapeDirection::Unspecified
), Alignment::BottomLeft);
CORRADE_COMPARE(alignmentForDirection(
Alignment::TopEndGlyphBounds,
LayoutDirection::HorizontalTopToBottom,
ShapeDirection::Unspecified
), Alignment::TopRightGlyphBounds);
}
void AlignmentTest::forDirectionInvalid() {
CORRADE_SKIP_IF_NO_ASSERT();
std::ostringstream out;
Error redirectError{&out};
/* It should blow up also for alignments that don't use Start or End, for
consistency */
alignmentForDirection(Alignment::BottomCenter, LayoutDirection::VerticalRightToLeft, ShapeDirection::Unspecified);
alignmentForDirection(Alignment::MiddleCenterIntegral, LayoutDirection::HorizontalTopToBottom, ShapeDirection::TopToBottom);
alignmentForDirection(Alignment::MiddleCenterIntegral, LayoutDirection::HorizontalTopToBottom, ShapeDirection::BottomToTop);
CORRADE_COMPARE_AS(out.str(),
"Text::alignmentForDirection(): only Text::LayoutDirection::HorizontalTopToBottom is supported right now, got Text::LayoutDirection::VerticalRightToLeft\n"
"Text::alignmentForDirection(): Text::ShapeDirection::TopToBottom is not supported yet, sorry\n"
"Text::alignmentForDirection(): Text::ShapeDirection::BottomToTop is not supported yet, sorry\n",
TestSuite::Compare::String);
}
}}}}
CORRADE_TEST_MAIN(Magnum::Text::Test::AlignmentTest)

2
src/Magnum/Text/Test/CMakeLists.txt

@ -77,7 +77,7 @@ endif()
corrade_add_test(TextAbstractShaperTest AbstractShaperTest.cpp
LIBRARIES MagnumTextTestLib)
corrade_add_test(TextAlignmentTest AlignmentTest.cpp LIBRARIES MagnumText)
corrade_add_test(TextAlignmentTest AlignmentTest.cpp LIBRARIES MagnumTextTestLib)
corrade_add_test(TextDirectionTest DirectionTest.cpp LIBRARIES MagnumText)
corrade_add_test(TextFeatureTest FeatureTest.cpp LIBRARIES MagnumTextTestLib)
corrade_add_test(TextRendererTest RendererTest.cpp LIBRARIES MagnumTextTestLib)

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

@ -122,62 +122,106 @@ const struct {
const struct {
TestSuite::TestCaseDescriptionSourceLocation name;
Alignment alignment;
ShapeDirection shapeDirection;
Vector2 offset;
} RenderDataData[]{
/* Not testing all combinations, just making sure that each horizontal,
vertical, glyph bounds and integer variant is covered */
{"line left", Alignment::LineLeft,
{"line left",
Alignment::LineLeft, ShapeDirection::Unspecified,
/* This is the default (0) value, thus should result in no shift */
{}},
{"line left, glyph bounds", Alignment::LineLeftGlyphBounds,
{"line left, glyph bounds",
Alignment::LineLeftGlyphBounds, ShapeDirection::Unspecified,
/* The first glyph has X offset of 2.5, which is subtracted */
{-2.5f, 0.0f}},
{"top left", Alignment::TopLeft,
{"top left",
Alignment::TopLeft, ShapeDirection::Unspecified,
/* Ascent is 4.5, scaled by 0.5 */
{0.0f, -2.25f}},
{"top left, glyph bounds", Alignment::TopLeftGlyphBounds,
{"top left, glyph bounds",
Alignment::TopLeftGlyphBounds, ShapeDirection::Unspecified,
/* Largest Y value is 10.5f */
{-2.5f, -10.5f}},
{"top right", Alignment::TopRight,
{"top right",
Alignment::TopRight, ShapeDirection::Unspecified,
/* Advances were 1, 2, 3, so 6 in total, ascent is 4.5; scaled by
0.5 */
{-3.0f, -2.25f}},
{"top right, glyph bounds", Alignment::TopRightGlyphBounds,
{"top right, glyph bounds",
Alignment::TopRightGlyphBounds, ShapeDirection::Unspecified,
/* Basically subtracting the largest vertex value */
{-12.5f, -10.5f}},
{"top center", Alignment::TopCenter,
{"top center",
Alignment::TopCenter, ShapeDirection::Unspecified,
/* Advances were 1, 2, 3, so 6 in total, center is 3, scaled by 0.5 */
{-1.5f, -2.25f}},
{"top center, integral", Alignment::TopCenterIntegral,
{"top center, integral",
Alignment::TopCenterIntegral, ShapeDirection::Unspecified,
/* The Y shift isn't whole units but only X is rounded here */
{-2.0f, -2.25f}},
{"top center, glyph bounds", Alignment::TopCenterGlyphBounds,
{"top center, glyph bounds",
Alignment::TopCenterGlyphBounds, ShapeDirection::Unspecified,
{-7.5f, -10.5f}},
{"top center, glyph bounds, integral", Alignment::TopCenterGlyphBoundsIntegral,
{"top center, glyph bounds, integral",
Alignment::TopCenterGlyphBoundsIntegral, ShapeDirection::Unspecified,
/* The Y shift isn't whole units but only X is rounded here */
{-8.0f, -10.5f}},
{"middle left, glyph bounds", Alignment::MiddleLeftGlyphBounds,
{"middle left, glyph bounds",
Alignment::MiddleLeftGlyphBounds, ShapeDirection::Unspecified,
{-2.5f, -7.125f}},
{"middle left, glyph bounds, integral", Alignment::MiddleLeftGlyphBoundsIntegral,
{"middle left, glyph bounds, integral",
Alignment::MiddleLeftGlyphBoundsIntegral, ShapeDirection::Unspecified,
/* The X shift isn't whole units but only Y is rounded here */
{-2.5f, -7.0f}},
{"middle center", Alignment::MiddleCenter,
{"middle center",
Alignment::MiddleCenter, ShapeDirection::Unspecified,
{-1.5f, -0.5f}},
{"middle center, integral", Alignment::MiddleCenterIntegral,
{"middle center, integral",
Alignment::MiddleCenterIntegral, ShapeDirection::Unspecified,
/* Rounding happens on both X and Y in this case */
{-2.0f, -1.0f}},
{"middle center, glyph bounds", Alignment::MiddleCenterGlyphBounds,
{"middle center, glyph bounds",
Alignment::MiddleCenterGlyphBounds, ShapeDirection::Unspecified,
/* Half size of the bounds quad */
{-7.5f, -7.125f}},
{"middle center, glyph bounds, integral", Alignment::MiddleCenterGlyphBoundsIntegral,
{"middle center, glyph bounds, integral",
Alignment::MiddleCenterGlyphBoundsIntegral, ShapeDirection::Unspecified,
{-8.0f, -7.0f}},
{"bottom left", Alignment::BottomLeft,
{"bottom left",
Alignment::BottomLeft, ShapeDirection::Unspecified,
/* Descent is -2.5; scaled by 0.5 */
{0.0f, 1.25f}},
{"bottom right", Alignment::BottomRight,
{"bottom right",
Alignment::BottomRight, ShapeDirection::Unspecified,
{-3.0f, 1.25f}},
{"bottom right, glyph bounds", Alignment::BottomRightGlyphBounds,
{"bottom right, glyph bounds",
Alignment::BottomRightGlyphBounds, ShapeDirection::Unspecified,
{-12.5f, -3.75f}},
{"line start, direction unspecified",
Alignment::LineStart, ShapeDirection::Unspecified,
{}}, /* Same as line left, thus no shift */
{"bottom start, LTR",
Alignment::BottomStart, ShapeDirection::LeftToRight,
{0.0f, 1.25f}}, /* Same as bottom left */
{"line end, RTL",
Alignment::LineEnd, ShapeDirection::RightToLeft,
{}}, /* Again same as line left, thus no shift */
{"line end, direction unspecified",
Alignment::LineEnd, ShapeDirection::Unspecified,
{-3.0f, 0.0f}}, /* Same as line right */
{"top end, LTR",
Alignment::TopEnd, ShapeDirection::LeftToRight,
{-3.0f, -2.25f}}, /* Same as top right */
{"line start, RTL",
Alignment::LineStart, ShapeDirection::RightToLeft,
{-3.0f, 0.0f}}, /* Same as line right */
{"line left, RTL",
Alignment::LineLeft, ShapeDirection::RightToLeft,
{}}, /* Line left with no change */
{"middle center, RTL",
Alignment::MiddleCenter, ShapeDirection::RightToLeft,
{-1.5f, -0.5f}}, /* Middle center with no change */
};
const struct {
@ -303,12 +347,16 @@ RendererTest::RendererTest() {
}
struct TestShaper: AbstractShaper {
using AbstractShaper::AbstractShaper;
explicit TestShaper(AbstractFont& font, ShapeDirection direction): AbstractShaper{font}, _direction{direction} {}
UnsignedInt doShape(Containers::StringView text, UnsignedInt, UnsignedInt, Containers::ArrayView<const FeatureRange>) override {
return text.size();
}
ShapeDirection doDirection() const override {
return _direction;
}
void doGlyphIdsInto(const Containers::StridedArrayView1D<UnsignedInt>& ids) const override {
for(UnsignedInt i = 0; i != ids.size(); ++i) {
/* It just rotates between the three glyphs */
@ -325,9 +373,12 @@ struct TestShaper: AbstractShaper {
/* Offset Y and advance X is getting larger with every glyph,
advance Y is flipping its sign with every glyph */
offsets[i] = Vector2::yAxis(i + 1);
/* This is always to the right, independent of ShapeDirection */
advances[i] = {Float(i + 1), i % 2 ? -0.5f : +0.5f};
}
}
ShapeDirection _direction;
};
struct TestFont: AbstractFont {
@ -350,9 +401,11 @@ struct TestFont: AbstractFont {
Vector2 doGlyphAdvance(UnsignedInt) override { return {}; }
Containers::Pointer<AbstractShaper> doCreateShaper() override {
return Containers::pointer<TestShaper>(*this);
return Containers::pointer<TestShaper>(*this, direction);
}
ShapeDirection direction = ShapeDirection::Unspecified;
bool _opened = false;
};
@ -817,7 +870,11 @@ void RendererTest::alignLineInvalidDirection() {
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");
alignRenderedLine({}, LayoutDirection::HorizontalTopToBottom, Alignment::BottomEnd, nullptr);
CORRADE_COMPARE_AS(out.str(),
"Text::alignRenderedLine(): only Text::LayoutDirection::HorizontalTopToBottom is supported right now, got Text::LayoutDirection::VerticalRightToLeft\n"
"Text::alignRenderedLine(): Text::Alignment::BottomEnd has to be resolved to *Left / *Right before being passed to this function\n",
TestSuite::Compare::String);
}
void RendererTest::alignBlock() {
@ -847,7 +904,11 @@ void RendererTest::alignBlockInvalidDirection() {
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");
alignRenderedBlock({}, LayoutDirection::HorizontalTopToBottom, Alignment::LineStartGlyphBounds, nullptr);
CORRADE_COMPARE_AS(out.str(),
"Text::alignRenderedBlock(): only Text::LayoutDirection::HorizontalTopToBottom is supported right now, got Text::LayoutDirection::VerticalRightToLeft\n"
"Text::alignRenderedBlock(): Text::Alignment::LineStartGlyphBounds has to be resolved to *Left / *Right before being passed to this function\n",
TestSuite::Compare::String);
}
template<class T> void RendererTest::glyphQuadIndices() {
@ -909,6 +970,7 @@ void RendererTest::renderData() {
setTestCaseDescription(data.name);
TestFont font;
font.direction = data.shapeDirection;
font.openFile({}, 0.5f);
DummyGlyphCache cache = testGlyphCache(font);

Loading…
Cancel
Save