From 818442a391dfac6d0b163d340cbcc1c3ef92bccf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 17 Oct 2023 23:23:08 +0200 Subject: [PATCH] Text: align based just on font metric and cursor by default. So it's possible to shape the text even before having all glyphs ready. That's one reason, second reason is that this is a more common behavior -- it usually doesn't make sense to make the text jump based on whether it's "zaxaca", "KEKEKE" or "yqpyq". The original alignment based on glyph bounds is now moved into dedicated `*GlyphBounds` variants. Additionally the `*LeftGlyphBounds` were changed to subtract the initial glyph offset as well, `*Integer` now rounds only in the direction where it's needed because a division by 2 happened, and there's a set of `*Bottom*` values that somehow weren't there before. --- doc/changelog.dox | 17 +- src/Magnum/Text/Alignment.h | 324 ++++++++++++++++++++---- src/Magnum/Text/Renderer.cpp | 61 +++-- src/Magnum/Text/Test/RendererGLTest.cpp | 133 +++++++--- 4 files changed, 434 insertions(+), 101 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index cedd92d11..d15e6957f 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -785,8 +785,13 @@ See also: - Added @ref Text::AbstractFont::glyphCount() and @relativeref{Text::AbstractFont,glyphSize()} - Added @ref Text::Renderer::fontSize() -- Added @ref Text::Alignment::TopCenterIntegral, which is also needed in - order to prevent aligning pixel-perfect fonts to fractional positions +- @ref Text::Alignment now has separate values for alignment based on font + metrics and cursor position alone, which is the more commonly expected + behavior, and `*GlyphBounds` values that align based on actual glyph + bounds. There's a set of new `Bottom*` values that were for some reason + missing before, and @ref Text::Alignment::TopCenterIntegral, which is also + needed in order to prevent aligning pixel-perfect fonts to fractional + positions. @subsubsection changelog-latest-changes-trade Trade library @@ -1660,6 +1665,14 @@ See also: - The @ref Text::AbstractGlyphCache::image() query now returns a @ref MutableImageView3D instead of an @ref Image2D in order to support incremental population and texture arrays +- The @ref Text::Alignment left, right, top and middle values now work with + the font metrics and cursor position instead of glyph bounding rectangles, + as that's the more commonly expected behavior. The original behavior with alignment based on glyph bounds is now in the `*GlyphBounds` variants + except for `*LeftGlyphBounds`, where the left glyph offset is also removed + by the alignment compared to `*Left` which performs no horizontal + translation like in the original case. Additionally, the `*Integer` + variants now only round the axis where division by 2 happened for the + middle / center alignment, with the other axis kept unchanged. - @ref TextureTools::DistanceField now asserts that the output texture has a framebuffer-drawable format. Before it only printed an error message and silently did nothing, causing a hard-to-track error. diff --git a/src/Magnum/Text/Alignment.h b/src/Magnum/Text/Alignment.h index 13977a417..73f5b89d2 100644 --- a/src/Magnum/Text/Alignment.h +++ b/src/Magnum/Text/Alignment.h @@ -35,23 +35,35 @@ namespace Magnum { namespace Text { namespace Implementation { enum: UnsignedByte { - AlignmentLeft = 1, - AlignmentCenter = 2, - AlignmentRight = 3, + /* Line/Left, which causes no shift of the shaped text whatsoever, + is deliberately 0 to signify a default */ + + AlignmentLeft = 0, + AlignmentCenter = 1 << 0, + AlignmentRight = 2 << 0, AlignmentHorizontal = AlignmentLeft|AlignmentCenter|AlignmentRight, - AlignmentLine = 1 << 3, - AlignmentMiddle = 2 << 3, - AlignmentTop = 3 << 3, - AlignmentVertical = AlignmentLine|AlignmentMiddle|AlignmentTop, + AlignmentLine = 0, + AlignmentBottom = 1 << 2, + AlignmentMiddle = 2 << 2, + AlignmentTop = 3 << 2, + AlignmentVertical = AlignmentLine|AlignmentBottom|AlignmentMiddle|AlignmentTop, - AlignmentIntegral = 1 << 6, + AlignmentIntegral = 1 << 4, + AlignmentGlyphBounds = 1 << 5 }; } /** @brief Text rendering alignment +By default, the alignment is performed based on cursor position and font metric +alone, without taking actual glyph offsets and rectangles into account. This +allows the alignment to be performed even before actual glyph bounds are known +and avoids the position changing based on what concrete glyphs are present. +Aligning to actual glyph rectangle bounds can be done with the `*GlyphBounds` +variants. + The `*Integer` values are meant to be used for pixel-perfect fonts that always have glyph sizes, advances and other metrics whole pixels, and need to be positioned on whole pixels as well in order to avoid making them blurry. These @@ -61,89 +73,301 @@ half-pixel shifts when divided by 2. @see @ref Renderer::render(), @ref Renderer::Renderer() */ enum class Alignment: UnsignedByte { - /** Text start and line is at origin */ + /** + * Leftmost cursor position and vertical line position is at origin. + * @see @ref Alignment::LineLeftGlyphBounds + */ LineLeft = Implementation::AlignmentLine|Implementation::AlignmentLeft, /** - * Text center and line is at origin - * - * @see @ref Alignment::LineCenterIntegral + * Left side of the glyph bounding rectangle and vertical line position is + * at origin. + * @see @ref Alignment::LineLeft + * @m_since_latest + */ + LineLeftGlyphBounds = LineLeft|Implementation::AlignmentGlyphBounds, + + /** + * Midpoint between leftmost and rightmost cursor position and vertical + * line position is at origin. + * @see @ref Alignment::LineCenterGlyphBounds, + * @ref Alignment::LineCenterIntegral */ LineCenter = Implementation::AlignmentLine|Implementation::AlignmentCenter, - /** Text end and line is at origin */ + /** + * Midpoint between leftmost and rightmost cursor position and vertical + * line position is at origin, with the horizontal offset rounded to whole + * units. + * @see @ref Alignment::LineCenter, + * @ref Alignment::LineCenterGlyphBoundsIntegral + */ + LineCenterIntegral = LineCenter|Implementation::AlignmentIntegral, + + /** + * Horizontal center of the glyph bounding rectangle and vertical line + * position is at origin. + * @see @ref Alignment::LineCenter, + * @ref Alignment::LineCenterGlyphBoundsIntegral + * @m_since_latest + */ + LineCenterGlyphBounds = LineCenter|Implementation::AlignmentGlyphBounds, + + /** + * Horizontal center of the glyph bounding rectangle and vertical line + * position is at origin, with the horizontal offset rounded to whole + * units. + * @see @ref Alignment::LineCenterGlyphBounds, + * @ref Alignment::LineCenterIntegral + * @m_since_latest + */ + LineCenterGlyphBoundsIntegral = LineCenterGlyphBounds|Implementation::AlignmentIntegral, + + /** + * Rightmost cursor position and vertical line position is at origin. + * @see @ref Alignment::LineRightGlyphBounds + */ LineRight = Implementation::AlignmentLine|Implementation::AlignmentRight, /** - * Text start and vertical middle is at origin - * - * @see @ref Alignment::MiddleLeftIntegral + * Right side of the glyph bounding rectangle and vertical line position is + * at origin. + * @see @ref Alignment::LineRight + * @m_since_latest + */ + LineRightGlyphBounds = LineRight|Implementation::AlignmentGlyphBounds, + + /** + * Leftmost cursor position and bottommost line descent is at origin. + * @see @ref Alignment::BottomLeftGlyphBounds + * @m_since_latest + */ + BottomLeft = Implementation::AlignmentBottom|Implementation::AlignmentLeft, + + /** + * Bottom left corner of the glyph bounding rectangle is at origin. + * @see @ref Alignment::BottomLeft + * @m_since_latest + */ + BottomLeftGlyphBounds = BottomLeft|Implementation::AlignmentGlyphBounds, + + /** + * Midpoint between leftmost and rightmost cursor position and bottommost + * line decent is at origin. + * @see @ref Alignment::BottomCenterGlyphBounds, + * @ref Alignment::BottomCenterIntegral + * @m_since_latest + */ + BottomCenter = Implementation::AlignmentBottom|Implementation::AlignmentCenter, + + /** + * Midpoint between leftmost and rightmost cursor position and bottommost + * line descent is at origin, with the horizontal offset rounded to whole + * units. + * @see @ref Alignment::BottomCenter, + * @ref Alignment::BottomCenterGlyphBoundsIntegral + * @m_since_latest + */ + BottomCenterIntegral = BottomCenter|Implementation::AlignmentIntegral, + + /** + * Horizontal center and bottom side of the glyph bounding rectangle is at + * origin. + * @see @ref Alignment::BottomCenter, + * @ref Alignment::BottomCenterGlyphBoundsIntegral + * @m_since_latest + */ + BottomCenterGlyphBounds = BottomCenter|Implementation::AlignmentGlyphBounds, + + /** + * Horizontal center and bottom side of the glyph bounding rectangle is at + * origin, with the horizontal offset rounded to whole units. + * @see @ref Alignment::BottomCenterGlyphBounds, + * @ref Alignment::BottomCenterIntegral + * @m_since_latest + */ + BottomCenterGlyphBoundsIntegral = BottomCenterGlyphBounds|Implementation::AlignmentIntegral, + + /** + * Rightmost cursor position and bottommost line descent is at origin. + * @see @ref Alignment::BottomRightGlyphBounds + * @m_since_latest + */ + BottomRight = Implementation::AlignmentBottom|Implementation::AlignmentRight, + + /** + * Bottom right corner of the glyph bounding rectangle is at origin. + * @see @ref Alignment::BottomRight + * @m_since_latest + */ + BottomRightGlyphBounds = BottomRight|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 */ MiddleLeft = Implementation::AlignmentMiddle|Implementation::AlignmentLeft, /** - * Text center and vertical middle is at origin - * - * @see @ref Alignment::MiddleRightIntegral + * Leftmost cursor position and a midpoint between topmost line ascent and + * bottommost line descent is at origin, with the vertical offset rounded + * to whole units. + * @see @ref Alignment::MiddleLeft, + * @ref Alignment::MiddleLeftGlyphBoundsIntegral + */ + MiddleLeftIntegral = MiddleLeft|Implementation::AlignmentIntegral, + + /** + * Left side and vertical center of the glyph bounding rectangle is at + * origin. + * @see @ref Alignment::MiddleLeft, + * @ref Alignment::MiddleLeftGlyphBoundsIntegral + * @m_since_latest + */ + MiddleLeftGlyphBounds = MiddleLeft|Implementation::AlignmentGlyphBounds, + + /** + * 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 + * @m_since_latest + */ + MiddleLeftGlyphBoundsIntegral = MiddleLeftGlyphBounds|Implementation::AlignmentIntegral, + + /** + * Midpoint between leftmost and rightmost cursor position and a midpoint + * between topmost line ascent and bottommost line descent is at origin. + * @see @ref Alignment::MiddleCenterGlyphBounds, + * @ref Alignment::MiddleRightIntegral */ MiddleCenter = Implementation::AlignmentMiddle|Implementation::AlignmentCenter, /** - * Text end and vertical middle is at origin - * - * @see @ref Alignment::MiddleRightIntegral + * Midpoint between leftmost and rightmost cursor position and a midpoint + * between topmost line ascent and bottommost line descent is at origin, + * with both the horizontal and vertical offset rounded to whole units. + * @see @ref Alignment::MiddleCenter, + * @ref Alignment::MiddleCenterGlyphBoundsIntegral + */ + MiddleCenterIntegral = MiddleCenter|Implementation::AlignmentIntegral, + + /** + * Horizontal and vertical center of the glyph bounding rectangle is at + * origin. + * @see @ref Alignment::MiddleCenter, + * @ref Alignment::MiddleCenterGlyphBoundsIntegral + * @m_since_latest + */ + MiddleCenterGlyphBounds = MiddleCenter|Implementation::AlignmentGlyphBounds, + + /** + * Horizontal and vertical center of the glyph bounding rectangle is at + * origin, with both the horizontal and vertical offset rounded to whole + * units. + * @see @ref Alignment::MiddleCenterGlyphBounds, + * @ref Alignment::MiddleCenterIntegral + * @m_since_latest + */ + MiddleCenterGlyphBoundsIntegral = MiddleCenterGlyphBounds|Implementation::AlignmentIntegral, + + /** + * Rightmost cursor position and a midpoint between topmost line ascent and + * bottommost line descent is at origin. + * @see @ref Alignment::MiddleRightGlyphBounds, + * @ref Alignment::MiddleRightIntegral */ MiddleRight = Implementation::AlignmentMiddle|Implementation::AlignmentRight, - /** Text start and top is at origin */ - TopLeft = Implementation::AlignmentTop|Implementation::AlignmentLeft, + /** + * Rightmost cursor position and a midpoint between topmost line ascent and + * bottommost line descent is at origin, with the vertical offset rounded + * to whole units. + * @see @ref Alignment::MiddleRight, + * @ref Alignment::MiddleRightGlyphBoundsIntegral + */ + MiddleRightIntegral = MiddleRight|Implementation::AlignmentIntegral, /** - * Text center and top is at origin - * - * @see @ref Alignment::TopCenterIntegral + * Right side and vertical center of the glyph bounding rectangle is at + * origin. + * @see @ref Alignment::MiddleRight, + * @ref Alignment::MiddleRightGlyphBoundsIntegral + * @m_since_latest */ - TopCenter = Implementation::AlignmentTop|Implementation::AlignmentCenter, + MiddleRightGlyphBounds = MiddleRight|Implementation::AlignmentGlyphBounds, - /** Text end and top is at origin */ - TopRight = Implementation::AlignmentTop|Implementation::AlignmentRight, + /** + * 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 + * @m_since_latest + */ + MiddleRightGlyphBoundsIntegral = MiddleRightGlyphBounds|Implementation::AlignmentIntegral, /** - * Text center and line is at origin and alignment offset is integral - * - * @see @ref Alignment::LineCenter + * Leftmost cursor position and topmost line ascent is at origin. + * @see @ref Alignment::TopLeftGlyphBounds */ - LineCenterIntegral = Implementation::AlignmentLine|Implementation::AlignmentCenter|Implementation::AlignmentIntegral, + TopLeft = Implementation::AlignmentTop|Implementation::AlignmentLeft, /** - * Text start and vertical middle is at origin and alignment offset is integral - * - * @see @ref Alignment::MiddleLeft + * Top left corner of the glyph bounding rectangle is at origin. + * @see @ref Alignment::TopLeft + * @m_since_latest */ - MiddleLeftIntegral = Implementation::AlignmentMiddle|Implementation::AlignmentLeft|Implementation::AlignmentIntegral, + TopLeftGlyphBounds = TopLeft|Implementation::AlignmentGlyphBounds, /** - * Text center and vertical middle is at origin and alignment offset is integral - * - * @see @ref Alignment::MiddleCenter + * Midpoint between leftmost and rightmost cursor position and topmost line + * ascent is at origin. + * @see @ref Alignment::TopCenterGlyphBounds, + * @ref Alignment::TopCenterIntegral */ - MiddleCenterIntegral = Implementation::AlignmentMiddle|Implementation::AlignmentCenter|Implementation::AlignmentIntegral, + TopCenter = Implementation::AlignmentTop|Implementation::AlignmentCenter, /** - * Text end and vertical middle is at origin and alignment offset is integral - * - * @see @ref Alignment::MiddleRight + * Midpoint between leftmost and rightmost cursor position and topmost line + * ascent is at origin, with the horizontal offset rounded to whole units. + * @see @ref Alignment::TopCenter, + * @ref Alignment::TopCenterGlyphBoundsIntegral + * @m_since_latest + */ + TopCenterIntegral = TopCenter|Implementation::AlignmentIntegral, + + /** + * Horizontal center and top side of the glyph bounding rectangle is at + * origin. + * @see @ref Alignment::TopCenter, + * @ref Alignment::TopCenterGlyphBoundsIntegral + * @m_since_latest */ - MiddleRightIntegral = Implementation::AlignmentMiddle|Implementation::AlignmentRight|Implementation::AlignmentIntegral, + TopCenterGlyphBounds = TopCenter|Implementation::AlignmentGlyphBounds, /** - * Text center and line is at origin and alignment offset is integral - * - * @see @ref Alignment::TopCenter + * Horizontal center and top side of the glyph bounding rectangle is at + * origin, with the horizontal offset rounded to whole units. + * @see @ref Alignment::TopCenterGlyphBounds, + * @ref Alignment::TopCenterIntegral * @m_since_latest */ - TopCenterIntegral = Implementation::AlignmentTop|Implementation::AlignmentCenter|Implementation::AlignmentIntegral, + TopCenterGlyphBoundsIntegral = TopCenterGlyphBounds|Implementation::AlignmentIntegral, + + /** + * Rightmost cursor position and topmost line ascent is at origin. + * @see @ref Alignment::TopRightGlyphBounds + */ + TopRight = Implementation::AlignmentTop|Implementation::AlignmentRight, + /** + * Top right corner of the glyph bounding rectangle is at origin. + * @see @ref Alignment::TopRight + * @m_since_latest + */ + TopRightGlyphBounds = TopRight|Implementation::AlignmentGlyphBounds, }; }} diff --git a/src/Magnum/Text/Renderer.cpp b/src/Magnum/Text/Renderer.cpp index fc77c6d04..f518b2882 100644 --- a/src/Magnum/Text/Renderer.cpp +++ b/src/Magnum/Text/Renderer.cpp @@ -137,8 +137,15 @@ std::tuple, Range2D> renderVerticesInternal(AbstractFont& fo issue arises. */ CORRADE_INTERNAL_ASSERT(vertices.size() + shaper->glyphCount()*4 <= vertices.capacity()); - /* Bounds of rendered line */ + /* 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); @@ -174,34 +181,46 @@ std::tuple, Range2D> renderVerticesInternal(AbstractFont& fo {quadPosition.bottomRight(), quadTextureCoordinates.bottomRight()} }); - /* Extend the line rectangle with current quad bounds. If the - original is zero size, it gets replaced. */ - lineRectangle = Math::join(lineRectangle, quadPosition); - /* 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 */ + /* 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::AlignmentCenter) + 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(); - /* Integer alignment */ - if(UnsignedByte(alignment) & Implementation::AlignmentIntegral) - alignmentOffsetX = Math::round(alignmentOffsetX); - /* Align positions and bounds on current line */ lineRectangle = lineRectangle.translated(Vector2::xAxis(alignmentOffsetX)); for(auto it = vertices.begin()+lastLineLastVertex; it != vertices.end(); ++it) it->position.x() += alignmentOffsetX; - /* Extend the rectangle with final line bounds, similarly to what was - done for each glyph above */ + /* 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); /* Move to next line */ @@ -210,17 +229,21 @@ std::tuple, Range2D> renderVerticesInternal(AbstractFont& fo lastLineLastVertex = vertices.size(), pos != std::string::npos); - /* Vertically align the rendered text */ + /* 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::AlignmentMiddle) + 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(); - /* Integer alignment */ - if(UnsignedByte(alignment) & Implementation::AlignmentIntegral) - alignmentOffsetY = Math::round(alignmentOffsetY); - /* Align positions and bounds */ rectangle = rectangle.translated(Vector2::yAxis(alignmentOffsetY)); for(auto& v: vertices) v.position.y() += alignmentOffsetY; diff --git a/src/Magnum/Text/Test/RendererGLTest.cpp b/src/Magnum/Text/Test/RendererGLTest.cpp index d4e859824..aa3a68721 100644 --- a/src/Magnum/Text/Test/RendererGLTest.cpp +++ b/src/Magnum/Text/Test/RendererGLTest.cpp @@ -57,30 +57,64 @@ struct RendererGLTest: GL::OpenGLTester { }; const struct { - const char* name; + TestSuite::TestCaseDescriptionSourceLocation name; Alignment alignment; Vector2 offset; } RenderDataData[]{ /* Not testing all combinations, just making sure that each horizontal, - vertical and integer variant is covered */ + vertical, glyph bounds and integer variant is covered */ {"line left", Alignment::LineLeft, + /* This is the default (0) value, thus should result in no shift */ {}}, - /** @todo for all these, the initial glyph offset is first subtracted and - only then the shift by either half or full size is performed, does that - make sense? why is not done for the LineLeft case, then? */ - {"top center, integral", Alignment::TopCenterIntegral, - /* The Y shift is rounded to whole units */ - {-8.0f, -11.0f}}, + {"line left, glyph bounds", Alignment::LineLeftGlyphBounds, + /* The first glyph has X offset of 2.5, which is subtracted */ + {-2.5f, 0.0f}}, {"top left", Alignment::TopLeft, - {0.0f, -10.5f}}, + /* Ascent is 4.5, scaled by 0.5 */ + {0.0f, -2.25f}}, + {"top left, glyph bounds", Alignment::TopLeftGlyphBounds, + /* Largest Y value is 10.5f */ + {-2.5f, -10.5f}}, {"top right", Alignment::TopRight, + /* 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, + /* Basically subtracting the largest vertex value */ {-12.5f, -10.5f}}, + {"top center", Alignment::TopCenter, + /* 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, + /* The Y shift isn't whole units but only X is rounded here */ + {-2.0f, -2.25f}}, + {"top center, glyph bounds", Alignment::TopCenterGlyphBounds, + {-7.5f, -10.5f}}, + {"top center, glyph bounds, integral", Alignment::TopCenterGlyphBoundsIntegral, + /* The Y shift isn't whole units but only X is rounded here */ + {-8.0f, -10.5f}}, + {"middle left, glyph bounds", Alignment::MiddleLeftGlyphBounds, + {-2.5f, -7.125f}}, + {"middle left, glyph bounds, integral", Alignment::MiddleLeftGlyphBoundsIntegral, + /* The X shift isn't whole units but only Y is rounded here */ + {-2.5f, -7.0f}}, {"middle center", Alignment::MiddleCenter, + {-1.5f, -0.5f}}, + {"middle center, integral", Alignment::MiddleCenterIntegral, + /* Rounding happens on both X and Y in this case */ + {-2.0f, -1.0f}}, + {"middle center, glyph bounds", Alignment::MiddleCenterGlyphBounds, /* Half size of the bounds quad */ {-7.5f, -7.125f}}, - {"middle center, integral", Alignment::MiddleCenterIntegral, - /* The X shift is rounded to whole units */ + {"middle center, glyph bounds, integral", Alignment::MiddleCenterGlyphBoundsIntegral, {-8.0f, -7.0f}}, + {"bottom left", Alignment::BottomLeft, + /* Descent is -2.5; scaled by 0.5 */ + {0.0f, 1.25f}}, + {"bottom right", Alignment::BottomRight, + {-3.0f, 1.25f}}, + {"bottom right, glyph bounds", Alignment::BottomRightGlyphBounds, + {-12.5f, -3.75f}}, }; const struct { @@ -91,34 +125,68 @@ const struct { Vector2 offset0, offset1, offset2; } MultilineData[]{ {"line left", Alignment::LineLeft, + {0.0f, -0.0f}, + {0.0f, -4.0f}, + {0.0f, -12.0f}}, + {"line left, glyph bounds", Alignment::LineLeftGlyphBounds, {0.0f, 0.0f}, {0.0f, -4.0f}, {0.0f, -12.0f}}, {"middle left", Alignment::MiddleLeft, + {0.0f, 6.0f}, + {0.0f, 2.0f}, + {0.0f, -6.0f}}, + {"middle left, glyph bounds", Alignment::MiddleLeftGlyphBounds, {0.0f, 5.5f}, {0.0f, 1.5f}, {0.0f, -6.5f}}, - {"middle left, integral", Alignment::MiddleLeftIntegral, + {"middle left, glyph bounds, integral", Alignment::MiddleLeftGlyphBoundsIntegral, {0.0f, 6.0f}, {0.0f, 2.0f}, {0.0f, -6.0f}}, {"middle center", Alignment::MiddleCenter, + /* The advance for the rightmost glyph is one unit larger than the + actual bounds which makes it different */ + {-4.0f, 6.0f}, + {-2.0f, 2.0f}, + {-3.0f, -6.0f}}, + {"middle center, integral", Alignment::MiddleCenterIntegral, + {-4.0f, 6.0f}, + {-2.0f, 2.0f}, + {-3.0f, -6.0f}}, + {"middle center, glyph bounds", Alignment::MiddleCenterGlyphBounds, {-3.5f, 5.5f}, {-1.5f, 1.5f}, {-2.5f, -6.5f}}, - {"middle center, integral", Alignment::MiddleCenterIntegral, + {"middle center, glyph bounds, integral", Alignment::MiddleCenterGlyphBoundsIntegral, {-4.0f, 6.0f}, {-2.0f, 2.0f}, {-3.0f, -6.0f}}, {"top right", Alignment::TopRight, + {-8.0f, -0.5f}, + {-4.0f, -4.5f}, + {-6.0f, -12.5f}}, + {"top right, glyph bounds", Alignment::TopRightGlyphBounds, {-7.0f, -1.0f}, {-3.0f, -5.0f}, {-5.0f, -13.0f}}, {"top center", Alignment::TopCenter, + /* The advance for the rightmost glyph is one unit larger than the + actual bounds which makes it different */ + {-4.0f, -0.5f}, + {-2.0f, -4.5f}, + {-3.0f, -12.5f}}, + {"top center, integral", Alignment::TopCenterIntegral, + /* The Y shift isn't whole units but only X (which is already whole + units) would be rounded here */ + {-4.0f, -0.5f}, + {-2.0f, -4.5f}, + {-3.0f, -12.5f}}, + {"top center, glyph bounds", Alignment::TopCenterGlyphBounds, {-3.5f, -1.0f}, {-1.5f, -5.0f}, {-2.5f, -13.0f}}, - {"top center, integral", Alignment::TopCenterIntegral, + {"top center, integral", Alignment::TopCenterGlyphBoundsIntegral, {-4.0f, -1.0f}, {-2.0f, -5.0f}, {-3.0f, -13.0f}}, @@ -172,7 +240,8 @@ struct TestFont: AbstractFont { Properties doOpenFile(Containers::StringView, Float size) override { _opened = true; - return {size, 0.45f, -0.25f, 0.75f, 10}; + /* Line height isn't used for anything here so can be arbitrary */ + return {size, 4.5f, -2.5f, 10000.0f, 10}; } UnsignedInt doGlyphId(char32_t) override { return 0; } @@ -259,8 +328,12 @@ void RendererGLTest::renderData() { Vector2{ 9.0f, 4.0f} + data.offset }), TestSuite::Compare::Container); - /* Bounds */ - CORRADE_COMPARE(bounds, (Range2D{{2.5f, 3.75f}, {12.5f, 10.5f}}.translated(data.offset))); + /* Bounds. Different depending on whether or not GlyphBounds alignment is + used. */ + if(UnsignedByte(data.alignment) & Implementation::AlignmentGlyphBounds) + CORRADE_COMPARE(bounds, (Range2D{{2.5f, 3.75f}, {12.5f, 10.5f}}.translated(data.offset))); + else + CORRADE_COMPARE(bounds, (Range2D{{0.0f, -1.25f}, {3.0f, 2.25f}}.translated(data.offset))); /* Texture coordinates. First glyph is bottom, second top left, third top right. @@ -318,12 +391,10 @@ void RendererGLTest::renderMesh() { MAGNUM_VERIFY_NO_GL_ERROR(); /* Alignment offset */ - /** @todo same as in render(), figure out if the initial offset makes - sense or not */ - const Vector2 offset{-7.5f, -7.125f}; + const Vector2 offset{-1.5f, -0.5f}; /* Bounds */ - CORRADE_COMPARE(bounds, (Range2D{{2.5f, 3.75f}, {12.5f, 10.5f}}.translated(offset))); + CORRADE_COMPARE(bounds, (Range2D{{0.0f, -1.25f}, {3.0f, 2.25f}}.translated(offset))); /** @todo How to verify this on ES? */ #ifndef MAGNUM_TARGET_GLES @@ -446,12 +517,10 @@ void RendererGLTest::mutableText() { MAGNUM_VERIFY_NO_GL_ERROR(); /* Alignment offset */ - /** @todo same as in render(), figure out if the initial offset makes - sense or not */ - const Vector2 offset{-7.5f, -7.125f}; + const Vector2 offset{-1.5f, -0.5f}; /* Updated bounds and mesh vertex count */ - CORRADE_COMPARE(renderer.rectangle(), (Range2D{{2.5f, 3.75f}, {12.5f, 10.5f} }.translated(offset))); + CORRADE_COMPARE(renderer.rectangle(), (Range2D{{0.0f, -1.25f}, {3.0f, 2.25f}}.translated(offset))); CORRADE_COMPARE(renderer.mesh().count(), 3*6); /** @todo How to verify this on ES? */ @@ -503,9 +572,9 @@ void RendererGLTest::multiline() { Properties doOpenFile(Containers::StringView, Float size) override { _opened = true; - /* The ascent and descent values shouldn't be used for anything - here and so can be completely arbitrary */ - return {size, -10000.0f, 1000.0f, 8.0f, 10}; + /* Compared to the glyph bounds, which are from 0 to 2, this is + shifted by one unit, thus by 0.5 in the output */ + return {size, 1.0f, -1.0f, 8.0f, 10}; } UnsignedInt doGlyphId(char32_t) override { return 0; } @@ -539,8 +608,12 @@ void RendererGLTest::multiline() { CORRADE_COMPARE(font.size(), 0.5f); CORRADE_COMPARE(font.lineHeight(), 8.0f); - /* Bounds */ - CORRADE_COMPARE(rectangle, Range2D({0.0f, -12.0f}, {7.0f, 1.0f}).translated(data.offset0)); + /* Bounds. The advance for the rightmost glyph is one unit larger than the + actual bounds so it's different on X between the two variants */ + if(UnsignedByte(data.alignment) & Implementation::AlignmentGlyphBounds) + CORRADE_COMPARE(rectangle, Range2D({0.0f, -12.0f}, {7.0f, 1.0f}).translated(data.offset0)); + else + CORRADE_COMPARE(rectangle, Range2D({0.0f, -12.5f}, {8.0f, 0.5f}).translated(data.offset0)); /* Vertices [a] [b] [c] [d]