diff --git a/doc/changelog.dox b/doc/changelog.dox index a5faed3fb..efc53428c 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -458,9 +458,10 @@ See also: @ref TextureTools::AtlasLandfill allowing more efficient and incremental glyph packing together with support for texture arrays - New @ref Text::renderLineGlyphPositionsInto(), - @ref Text::renderGlyphQuadsInto(), @ref Text::alignRenderedLine(), - @ref Text::alignRenderedBlock() and @ref Text::renderGlyphQuadIndicesInto() - APIs providing low-level access to the text renderer building blocks + @ref Text::renderGlyphQuadsInto(), @ref Text::glyphQuadBounds(), + @ref Text::alignRenderedLine(), @ref Text::alignRenderedBlock() and + @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 for arbitrarily complex shapers using the output from @ref Text::AbstractShaper::glyphClustersInto() diff --git a/src/Magnum/Text/Renderer.cpp b/src/Magnum/Text/Renderer.cpp index b06c70f32..bca3afeba 100644 --- a/src/Magnum/Text/Renderer.cpp +++ b/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); } +Range2D glyphQuadBounds(const AbstractGlyphCache& cache, const Float scale, const Containers::StridedArrayView1D& glyphPositions, const Containers::StridedArrayView1D& 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 cacheGlyphOffsets = cache.glyphOffsets(); + const Containers::StridedArrayView1D 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& positions) { CORRADE_ASSERT(direction == LayoutDirection::HorizontalTopToBottom, "Text::alignRenderedLine(): only" << LayoutDirection::HorizontalTopToBottom << "is supported right now, got" << direction, {}); diff --git a/src/Magnum/Text/Renderer.h b/src/Magnum/Text/Renderer.h index e909aa507..9743822d1 100644 --- a/src/Magnum/Text/Renderer.h +++ b/src/Magnum/Text/Renderer.h @@ -27,7 +27,7 @@ */ /** @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" @@ -155,6 +155,7 @@ overload to get just 2D texture coordinates out. Use the @ref renderGlyphQuadsInto(const AbstractGlyphCache&, Float, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) overload if you already have cache-global glyph IDs. Use @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& glyphPositions, const Containers::StridedArrayView1D& fontGlyphIds, const Containers::StridedArrayView1D& vertexPositions, const Containers::StridedArrayView1D& 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 @p glyphPositions and @p glyphIds with @cpp vertexPositions.every(4) @ce and @cpp vertexTextureCoordinates.every(4) @ce. +@see @ref AbstractGlyphCache::glyphIdsInto(), @ref glyphQuadBounds() */ MAGNUM_TEXT_EXPORT Range2D renderGlyphQuadsInto(const AbstractGlyphCache& cache, Float scale, const Containers::StridedArrayView1D& glyphPositions, const Containers::StridedArrayView1D& glyphIds, const Containers::StridedArrayView1D& vertexPositions, const Containers::StridedArrayView1D& vertexTextureCoordinates); @@ -200,6 +202,29 @@ depth is @cpp 1 @ce. */ MAGNUM_TEXT_EXPORT Range2D renderGlyphQuadsInto(const AbstractGlyphCache& cache, Float scale, const Containers::StridedArrayView1D& glyphPositions, const Containers::StridedArrayView1D& glyphIds, const Containers::StridedArrayView1D& vertexPositions, const Containers::StridedArrayView1D& 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 Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) +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 Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&), +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& glyphPositions, const Containers::StridedArrayView1D& glyphIds); + /** @brief Align a rendered 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 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 +If @p alignment is `*GlyphBounds`, this function should get glyph @p positions +for the whole line coming from @ref renderLineGlyphPositionsInto() and a +@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(). 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 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 -given range. Assumes that @p clusters are either monotonically non-dereasing or -non-increasing. +given range. Assumes that @p clusters are either monotonically non-decreasing +or non-increasing. 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 diff --git a/src/Magnum/Text/Test/RendererTest.cpp b/src/Magnum/Text/Test/RendererTest.cpp index bc2975491..be9b97ec5 100644 --- a/src/Magnum/Text/Test/RendererTest.cpp +++ b/src/Magnum/Text/Test/RendererTest.cpp @@ -62,6 +62,9 @@ struct RendererTest: TestSuite::Tester { void glyphQuads2D(); void glyphQuads2DArrayGlyphCache(); + void glyphQuadBounds(); + void glyphQuadBoundsInvalidViewSizes(); + void alignLine(); void alignLineInvalidDirection(); @@ -344,7 +347,10 @@ RendererTest::RendererTest() { addInstancedTests({&RendererTest::glyphQuads2D}, Containers::arraySize(GlyphQuadsData)); - addTests({&RendererTest::glyphQuads2DArrayGlyphCache}); + addTests({&RendererTest::glyphQuads2DArrayGlyphCache, + + &RendererTest::glyphQuadBounds, + &RendererTest::glyphQuadBoundsInvalidViewSizes}); addInstancedTests({&RendererTest::alignLine}, 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"); } +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() { auto&& data = AlignLineData[testCaseInstanceId()]; setTestCaseDescription(data.name);