Browse Source

Text: add glyphQuadBounds().

For when one needs to get the rectangle for aligning with
Alignment::*GlyphBounds but doesn't want to generate the actual quads
just yet.
pull/674/head
Vladimír Vondruš 2 years ago
parent
commit
1553328cd7
  1. 7
      doc/changelog.dox
  2. 22
      src/Magnum/Text/Renderer.cpp
  3. 40
      src/Magnum/Text/Renderer.h
  4. 53
      src/Magnum/Text/Test/RendererTest.cpp

7
doc/changelog.dox

@ -458,9 +458,10 @@ See also:
@ref TextureTools::AtlasLandfill allowing more efficient and incremental @ref TextureTools::AtlasLandfill allowing more efficient and incremental
glyph packing together with support for texture arrays glyph packing together with support for texture arrays
- New @ref Text::renderLineGlyphPositionsInto(), - New @ref Text::renderLineGlyphPositionsInto(),
@ref Text::renderGlyphQuadsInto(), @ref Text::alignRenderedLine(), @ref Text::renderGlyphQuadsInto(), @ref Text::glyphQuadBounds(),
@ref Text::alignRenderedBlock() and @ref Text::renderGlyphQuadIndicesInto() @ref Text::alignRenderedLine(), @ref Text::alignRenderedBlock() and
APIs providing low-level access to the text renderer building blocks @ref Text::renderGlyphQuadIndicesInto() APIs providing low-level access to
the text renderer building blocks
- New @ref Text::glyphRangeForBytes() API for providing byte-to-glyph mapping - New @ref Text::glyphRangeForBytes() API for providing byte-to-glyph mapping
for arbitrarily complex shapers using the output from for arbitrarily complex shapers using the output from
@ref Text::AbstractShaper::glyphClustersInto() @ref Text::AbstractShaper::glyphClustersInto()

22
src/Magnum/Text/Renderer.cpp

@ -191,6 +191,28 @@ Range2D renderGlyphQuadsInto(const AbstractGlyphCache& cache, const Float scale,
return renderGlyphQuadsInto(cache, scale, glyphPositions, glyphIds, vertexPositions, vertexTextureCoordinates, nullptr); return renderGlyphQuadsInto(cache, scale, glyphPositions, glyphIds, vertexPositions, vertexTextureCoordinates, nullptr);
} }
Range2D glyphQuadBounds(const AbstractGlyphCache& cache, const Float scale, const Containers::StridedArrayView1D<const Vector2>& glyphPositions, const Containers::StridedArrayView1D<const UnsignedInt>& glyphIds) {
CORRADE_ASSERT(glyphIds.size() == glyphPositions.size(),
"Text::glyphQuadBounds(): expected glyphIds and glyphPositions views to have the same size, got" << glyphIds.size() << "and" << glyphPositions.size(), {});
/* Direct views on the cache data */
const Containers::StridedArrayView1D<const Vector2i> cacheGlyphOffsets = cache.glyphOffsets();
const Containers::StridedArrayView1D<const Range2Di> cacheGlyphRectangles = cache.glyphRectangles();
/* Basically what renderGlyphQuadsInto() does above, but producing just the
rectangle */
Range2D rectangle;
for(std::size_t i = 0; i != glyphIds.size(); ++i) {
const UnsignedInt glyphId = glyphIds[i];
const Range2D quad = Range2D::fromSize(
glyphPositions[i] + Vector2{cacheGlyphOffsets[glyphId]}*scale,
Vector2{cacheGlyphRectangles[glyphId].size()}*scale);
rectangle = Math::join(rectangle, quad);
}
return rectangle;
}
Range2D alignRenderedLine(const Range2D& lineRectangle, const LayoutDirection direction, const Alignment alignment, const Containers::StridedArrayView1D<Vector2>& positions) { Range2D alignRenderedLine(const Range2D& lineRectangle, const LayoutDirection direction, const Alignment alignment, const Containers::StridedArrayView1D<Vector2>& positions) {
CORRADE_ASSERT(direction == LayoutDirection::HorizontalTopToBottom, CORRADE_ASSERT(direction == LayoutDirection::HorizontalTopToBottom,
"Text::alignRenderedLine(): only" << LayoutDirection::HorizontalTopToBottom << "is supported right now, got" << direction, {}); "Text::alignRenderedLine(): only" << LayoutDirection::HorizontalTopToBottom << "is supported right now, got" << direction, {});

40
src/Magnum/Text/Renderer.h

@ -27,7 +27,7 @@
*/ */
/** @file /** @file
* @brief Class @ref Magnum::Text::AbstractRenderer, 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(), @ref Magnum::Text::renderGlyphQuadIndicesInto(), @ref Magnum::Text::glyphRangeForBytes() * @brief Class @ref Magnum::Text::AbstractRenderer, typedef @ref Magnum::Text::Renderer2D, @ref Magnum::Text::Renderer3D, function @ref Magnum::Text::renderLineGlyphPositionsInto(), @ref Magnum::Text::renderGlyphQuadsInto(), @ref Magnum::Text::glyphQuadBounds(), @ref Magnum::Text::alignRenderedLine(), @ref Magnum::Text::alignRenderedBlock(), @ref Magnum::Text::renderGlyphQuadIndicesInto(), @ref Magnum::Text::glyphRangeForBytes()
*/ */
#include "Magnum/Magnum.h" #include "Magnum/Magnum.h"
@ -155,6 +155,7 @@ overload to get just 2D texture coordinates out. Use the
@ref renderGlyphQuadsInto(const AbstractGlyphCache&, Float, const Containers::StridedArrayView1D<const Vector2>&, const Containers::StridedArrayView1D<const UnsignedInt>&, const Containers::StridedArrayView1D<Vector2>&, const Containers::StridedArrayView1D<Vector2>&) @ref renderGlyphQuadsInto(const AbstractGlyphCache&, Float, const Containers::StridedArrayView1D<const Vector2>&, const Containers::StridedArrayView1D<const UnsignedInt>&, const Containers::StridedArrayView1D<Vector2>&, const Containers::StridedArrayView1D<Vector2>&)
overload if you already have cache-global glyph IDs. Use overload if you already have cache-global glyph IDs. Use
@ref renderGlyphQuadIndicesInto() to populate the corresponding index array. @ref renderGlyphQuadIndicesInto() to populate the corresponding index array.
@see @ref glyphQuadBounds()
*/ */
MAGNUM_TEXT_EXPORT Range2D renderGlyphQuadsInto(const AbstractFont& font, Float size, const AbstractGlyphCache& cache, const Containers::StridedArrayView1D<const Vector2>& glyphPositions, const Containers::StridedArrayView1D<const UnsignedInt>& fontGlyphIds, const Containers::StridedArrayView1D<Vector2>& vertexPositions, const Containers::StridedArrayView1D<Vector3>& vertexTextureCoordinates); MAGNUM_TEXT_EXPORT Range2D renderGlyphQuadsInto(const AbstractFont& font, Float size, const AbstractGlyphCache& cache, const Containers::StridedArrayView1D<const Vector2>& glyphPositions, const Containers::StridedArrayView1D<const UnsignedInt>& fontGlyphIds, const Containers::StridedArrayView1D<Vector2>& vertexPositions, const Containers::StridedArrayView1D<Vector3>& vertexTextureCoordinates);
@ -187,6 +188,7 @@ mapping from font-specific glyph IDs to cache-global IDs happens in this case.
As with the above overload, to optimize memory use, it's possible to alias As with the above overload, to optimize memory use, it's possible to alias
@p glyphPositions and @p glyphIds with @cpp vertexPositions.every(4) @ce and @p glyphPositions and @p glyphIds with @cpp vertexPositions.every(4) @ce and
@cpp vertexTextureCoordinates.every(4) @ce. @cpp vertexTextureCoordinates.every(4) @ce.
@see @ref AbstractGlyphCache::glyphIdsInto(), @ref glyphQuadBounds()
*/ */
MAGNUM_TEXT_EXPORT Range2D renderGlyphQuadsInto(const AbstractGlyphCache& cache, Float scale, const Containers::StridedArrayView1D<const Vector2>& glyphPositions, const Containers::StridedArrayView1D<const UnsignedInt>& glyphIds, const Containers::StridedArrayView1D<Vector2>& vertexPositions, const Containers::StridedArrayView1D<Vector3>& vertexTextureCoordinates); MAGNUM_TEXT_EXPORT Range2D renderGlyphQuadsInto(const AbstractGlyphCache& cache, Float scale, const Containers::StridedArrayView1D<const Vector2>& glyphPositions, const Containers::StridedArrayView1D<const UnsignedInt>& glyphIds, const Containers::StridedArrayView1D<Vector2>& vertexPositions, const Containers::StridedArrayView1D<Vector3>& vertexTextureCoordinates);
@ -200,6 +202,29 @@ depth is @cpp 1 @ce.
*/ */
MAGNUM_TEXT_EXPORT Range2D renderGlyphQuadsInto(const AbstractGlyphCache& cache, Float scale, const Containers::StridedArrayView1D<const Vector2>& glyphPositions, const Containers::StridedArrayView1D<const UnsignedInt>& glyphIds, const Containers::StridedArrayView1D<Vector2>& vertexPositions, const Containers::StridedArrayView1D<Vector2>& vertexTextureCoordinates); MAGNUM_TEXT_EXPORT Range2D renderGlyphQuadsInto(const AbstractGlyphCache& cache, Float scale, const Containers::StridedArrayView1D<const Vector2>& glyphPositions, const Containers::StridedArrayView1D<const UnsignedInt>& glyphIds, const Containers::StridedArrayView1D<Vector2>& vertexPositions, const Containers::StridedArrayView1D<Vector2>& vertexTextureCoordinates);
/**
@brief Calculate glyph quad bounds from cache-global glyph IDs
@param[in] cache Glyph cache to query for glyph rectangles
@param[in] scale Size to render the glyphs at divided by size of
the input font
@param[in] glyphPositions Glyph positions coming from an earlier call to
@ref renderLineGlyphPositionsInto()
@param[in] glyphIds Cache-global glyph IDs
@return Rectangle spanning the glyph quads
@m_since_latest
Returns the same rectangle as @ref renderGlyphQuadsInto(const AbstractGlyphCache&, Float, const Containers::StridedArrayView1D<const Vector2>&, const Containers::StridedArrayView1D<const UnsignedInt>&, const Containers::StridedArrayView1D<Vector2>&, const Containers::StridedArrayView1D<Vector3>&)
but without actually generating the glyph quads. Use the returned value for
@ref alignRenderedLine() with a `*GlyphBounds` @ref Alignment value.
Note that, unlike with @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>&),
this function doesn't have an overload taking font-local glyph IDs, as it
doesn't have any scratch space to store them. Use
@ref AbstractGlyphCache::glyphIdsInto() instead to convert them to cache-global
and then call this function with the result.
*/
MAGNUM_TEXT_EXPORT Range2D glyphQuadBounds(const AbstractGlyphCache& cache, Float scale, const Containers::StridedArrayView1D<const Vector2>& glyphPositions, const Containers::StridedArrayView1D<const UnsignedInt>& glyphIds);
/** /**
@brief Align a rendered line @brief Align a rendered line
@param[in] lineRectangle Rectangle spanning the whole line @param[in] lineRectangle Rectangle spanning the whole line
@ -223,9 +248,12 @@ If @p alignment isn't `*GlyphBounds`, this function should get glyph
and @p lineRectangle being all rectangles returned by that function combined and @p lineRectangle being all rectangles returned by that function combined
together with @ref Math::join(). together with @ref Math::join().
If @p alignment is `*GlyphBounds`, this function should get vertex @p positions If @p alignment is `*GlyphBounds`, this function should get glyph @p positions
for a whole line coming from @ref renderGlyphQuadsInto() and @p lineRectangle for the whole line coming from @ref renderLineGlyphPositionsInto() and a
being all rectangles returned by that function combined together with @p lineRectangle being generated with @ref glyphQuadBounds() from those
positions. Alternatively, it 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(). @ref Math::join().
The @p positions are translated in one axis based on the @p inputRectangle and The @p positions are translated in one axis based on the @p inputRectangle and
@ -312,8 +340,8 @@ Assuming @p clusters is a view containing cluster IDs returned from
@ref AbstractShaper::glyphClustersInto() and @p begin and @p end are byte @ref AbstractShaper::glyphClustersInto() and @p begin and @p end are byte
positions in the text passed to @ref AbstractShaper::shape() for which the positions in the text passed to @ref AbstractShaper::shape() for which the
cluster IDs were retrieved, returns a range in the glyph array that contains cluster IDs were retrieved, returns a range in the glyph array that contains
given range. Assumes that @p clusters are either monotonically non-dereasing or given range. Assumes that @p clusters are either monotonically non-decreasing
non-increasing. or non-increasing.
If @p clusters are empty or @p end is less or equal to all @p clusters, returns If @p clusters are empty or @p end is less or equal to all @p clusters, returns
@cpp {0, 0} @ce. If @p begin is greater than all @p clusters are, both return @cpp {0, 0} @ce. If @p begin is greater than all @p clusters are, both return

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

@ -62,6 +62,9 @@ struct RendererTest: TestSuite::Tester {
void glyphQuads2D(); void glyphQuads2D();
void glyphQuads2DArrayGlyphCache(); void glyphQuads2DArrayGlyphCache();
void glyphQuadBounds();
void glyphQuadBoundsInvalidViewSizes();
void alignLine(); void alignLine();
void alignLineInvalidDirection(); void alignLineInvalidDirection();
@ -344,7 +347,10 @@ RendererTest::RendererTest() {
addInstancedTests({&RendererTest::glyphQuads2D}, addInstancedTests({&RendererTest::glyphQuads2D},
Containers::arraySize(GlyphQuadsData)); Containers::arraySize(GlyphQuadsData));
addTests({&RendererTest::glyphQuads2DArrayGlyphCache}); addTests({&RendererTest::glyphQuads2DArrayGlyphCache,
&RendererTest::glyphQuadBounds,
&RendererTest::glyphQuadBoundsInvalidViewSizes});
addInstancedTests({&RendererTest::alignLine}, addInstancedTests({&RendererTest::alignLine},
Containers::arraySize(AlignLineData)); Containers::arraySize(AlignLineData));
@ -877,6 +883,51 @@ void RendererTest::glyphQuads2DArrayGlyphCache() {
CORRADE_COMPARE(out, "Text::renderGlyphQuadsInto(): can't use this overload with an array glyph cache\n"); CORRADE_COMPARE(out, "Text::renderGlyphQuadsInto(): can't use this overload with an array glyph cache\n");
} }
void RendererTest::glyphQuadBounds() {
/* Input like in glyphQuads(), verifying just the output rectangle */
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[]{
/* Glyph 0 is the cache-global invalid glyph */
1, 3, 2
};
/* The font is opened at 2.5, rendering at 1.25, so everything will be
scaled by 0.5 */
Range2D rectangle = Text::glyphQuadBounds(cache, 1.25f/2.5f, glyphPositions, glyphIds);
CORRADE_COMPARE(rectangle, (Range2D{{102.5f, 198.5f}, {114.5f, 210.0f}}));
}
void RendererTest::glyphQuadBoundsInvalidViewSizes() {
CORRADE_SKIP_IF_NO_ASSERT();
TestFont font;
font.openFile({}, 5.0f);
DummyGlyphCache cache{PixelFormat::R8Unorm, {20, 20}};
cache.addFont(96, &font);
Vector2 glyphPositions[4];
Vector2 glyphPositionsInvalid[5];
UnsignedInt glyphIds[4]{};
UnsignedInt glyphIdsInvalid[3];
Containers::String out;
Error redirectError{&out};
Text::glyphQuadBounds(cache, 2.0f, glyphPositions, glyphIdsInvalid);
Text::glyphQuadBounds(cache, 2.0f, glyphPositionsInvalid, glyphIds);
CORRADE_COMPARE_AS(out,
"Text::glyphQuadBounds(): expected glyphIds and glyphPositions views to have the same size, got 3 and 4\n"
"Text::glyphQuadBounds(): expected glyphIds and glyphPositions views to have the same size, got 4 and 5\n",
TestSuite::Compare::String);
}
void RendererTest::alignLine() { void RendererTest::alignLine() {
auto&& data = AlignLineData[testCaseInstanceId()]; auto&& data = AlignLineData[testCaseInstanceId()];
setTestCaseDescription(data.name); setTestCaseDescription(data.name);

Loading…
Cancel
Save