Browse Source

Text: AbstractFont::fillGlyphCache() overload taking glyph IDs directly.

pull/638/head
Vladimír Vondruš 2 years ago
parent
commit
631b9b8ae5
  1. 3
      doc/changelog.dox
  2. 69
      src/Magnum/Text/AbstractFont.cpp
  3. 28
      src/Magnum/Text/AbstractFont.h
  4. 241
      src/Magnum/Text/Test/AbstractFontTest.cpp
  5. 5
      src/MagnumPlugins/MagnumFontConverter/Test/MagnumFontConverterTest.cpp

3
doc/changelog.dox

@ -837,6 +837,9 @@ See also:
- Added a @ref Text::GlyphCache::GlyphCache(NoCreateT) and
@ref Text::DistanceFieldGlyphCache::DistanceFieldGlyphCache(NoCreateT)
constructor allowing to construct the object without a GL context present
- New @ref Text::AbstractFont::fillGlyphCache() overload taking a list of
glyph IDs instead of a UTF-8 string to allow filling the glyph cache with
glyphs that don't directly map to Unicode
- @ref Text::AbstractFont::fillGlyphCache() now returns a @cpp bool @ce to
allow font plugin implementations to gracefully report failures

69
src/Magnum/Text/AbstractFont.cpp

@ -27,7 +27,9 @@
#include <string> /** @todo remove once file callbacks are <string>-free */
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/BitArray.h>
#include <Corrade/Containers/EnumSet.hpp>
#include <Corrade/Containers/GrowableArray.h>
#include <Corrade/Containers/Optional.h>
#include <Corrade/Containers/StridedArrayView.h>
#include <Corrade/Containers/String.h>
@ -294,14 +296,71 @@ bool AbstractFont::fillGlyphCache(AbstractGlyphCache& cache, const Containers::S
CORRADE_ASSERT(!(features() & FontFeature::PreparedGlyphCache),
"Text::AbstractFont::fillGlyphCache(): feature not supported", {});
const Containers::Optional<Containers::Array<char32_t>> utf32 = Utility::Unicode::utf32(characters);
CORRADE_ASSERT(utf32,
"Text::AbstractFont::fillGlyphCache(): not a valid UTF-8 string:" << characters, {});
struct Glyph {
char32_t character;
UnsignedInt glyph;
};
/* Convert UTF-8 to Unicode codepoints */
Containers::Array<Glyph> glyphs;
arrayReserve(glyphs, characters.size());
for(std::size_t i = 0; i != characters.size(); ) {
const Containers::Pair<char32_t, std::size_t> next = Utility::Unicode::nextChar(characters, i);
CORRADE_ASSERT(next.first() != U'\xffffffff',
"Text::AbstractFont::fillGlyphCache(): not a valid UTF-8 string:" << characters, {});
arrayAppend(glyphs, InPlaceInit, next.first(), 0u);
i = next.second();
}
/* Convert the codepoints to glyph IDs */
glyphIdsInto(stridedArrayView(glyphs).slice(&Glyph::character),
stridedArrayView(glyphs).slice(&Glyph::glyph));
/* If this font isn't in the cache yet, include also the invalid glyph */
if(!cache.findFont(*this))
arrayAppend(glyphs, InPlaceInit, U'\0', 0u);
/* Create a unique (ordered) set */
/** @todo reuse the memory from `glyphs` for this somehow? tho there could
be thousands of glyphs and the `glyphs` might be just a few entries */
Containers::BitArray uniqueGlyphs{ValueInit, _glyphCount};
for(const Glyph& glyph: glyphs)
uniqueGlyphs.set(glyph.glyph);
/* Convert the unique set back to a list of glyph IDs, reusing the original
glyph memory */
const std::size_t uniqueCount = uniqueGlyphs.count();
CORRADE_INTERNAL_ASSERT(uniqueCount <= glyphs.size());
std::size_t offset = 0;
for(UnsignedInt i = 0; i != uniqueGlyphs.size(); ++i)
if(uniqueGlyphs[i]) glyphs[offset++].glyph = i;
CORRADE_INTERNAL_ASSERT(offset == uniqueCount);
/* Pass the unique set to the implementation */
return doFillGlyphCache(cache, stridedArrayView(glyphs).slice(&Glyph::glyph).prefix(uniqueCount));
}
bool AbstractFont::fillGlyphCache(AbstractGlyphCache& cache, const Containers::StridedArrayView1D<const UnsignedInt>& glyphs) {
CORRADE_ASSERT(isOpened(),
"Text::AbstractFont::fillGlyphCache(): no font opened", {});
CORRADE_ASSERT(!(features() & FontFeature::PreparedGlyphCache),
"Text::AbstractFont::fillGlyphCache(): feature not supported", {});
#ifndef CORRADE_NO_DEBUG_ASSERT
Containers::BitArray uniqueGlyphs{ValueInit, _glyphCount};
for(const UnsignedInt& glyph: glyphs) {
CORRADE_DEBUG_ASSERT(glyph < _glyphCount,
"Text::AbstractFont::fillGlyphCache(): index" << glyph << "out of range for" << _glyphCount << "glyphs", {});
CORRADE_DEBUG_ASSERT(!uniqueGlyphs[glyph],
"Text::AbstractFont::fillGlyphCache(): duplicate glyph" << glyph, {});
uniqueGlyphs.set(glyph);
}
#endif
return doFillGlyphCache(cache, *utf32);
return doFillGlyphCache(cache, glyphs);
}
bool AbstractFont::doFillGlyphCache(AbstractGlyphCache&, Containers::ArrayView<const char32_t>) {
bool AbstractFont::doFillGlyphCache(AbstractGlyphCache&, const Containers::StridedArrayView1D<const UnsignedInt>&) {
CORRADE_ASSERT_UNREACHABLE("Text::AbstractFont::fillGlyphCache(): feature advertised but not implemented", {});
return {};
}

28
src/Magnum/Text/AbstractFont.h

@ -503,20 +503,34 @@ class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin {
Vector2 glyphAdvance(UnsignedInt glyph);
/**
* @brief Fill glyph cache with given character set
* @brief Fill glyph cache with given glyph IDs
* @param cache Glyph cache instance
* @param characters UTF-8 characters to render
* @param glyphs Glyph IDs to render
* @m_since_latest
*
* Fills the cache with given characters. Fonts having
* Fills the cache with given glyph IDs. Fonts having
* @ref FontFeature::PreparedGlyphCache do not support partial glyph
* cache filling, use @ref createGlyphCache() instead. Expects that a
* font is opened and @p characters is valid UTF-8.
* font is opened and @p glyphs are all unique and less than
* @ref glyphCount().
*
* On success returns @cpp true @ce. On failure, for example if the
* @p cache doesn't have expected format or the @p characters can't
* fit, prints a message to @relativeref{Magnum,Error} and returns
* @cpp false @ce.
*/
bool fillGlyphCache(AbstractGlyphCache& cache, const Containers::StridedArrayView1D<const UnsignedInt>& glyphs);
/**
* @brief Fill glyph cache with given character set
* @param cache Glyph cache instance
* @param characters UTF-8 characters to render
*
* Converts @p characters to a list of Unicode codepoints, gets glyph
* IDs for them using @ref glyphIdsInto(), removes duplicates, adds the
* glyph @cpp 0 @ce if the font is not in @p cache already, and
* delegates to @ref fillGlyphCache(AbstractGlyphCache&, const Containers::StridedArrayView1D<const UnsignedInt>&).
*/
bool fillGlyphCache(AbstractGlyphCache& cache, Containers::StringView characters);
/**
@ -684,10 +698,10 @@ class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin {
/**
* @brief Implementation for @ref fillGlyphCache()
*
* The string is converted from UTF-8 to UTF-32, duplicate characters
* are *not* removed.
* The @p glyphs are guaranteed to be unique and all less than
* @ref glyphCount().
*/
virtual bool doFillGlyphCache(AbstractGlyphCache& cache, Containers::ArrayView<const char32_t> characters);
virtual bool doFillGlyphCache(AbstractGlyphCache& cache, const Containers::StridedArrayView1D<const UnsignedInt>& glyphs);
/** @brief Implementation for @ref createGlyphCache() */
virtual Containers::Pointer<AbstractGlyphCache> doCreateGlyphCache();

241
src/Magnum/Text/Test/AbstractFontTest.cpp

@ -91,6 +91,9 @@ struct AbstractFontTest: TestSuite::Tester {
void glyphSizeAdvanceOutOfRange();
void fillGlyphCache();
void fillGlyphCacheOutOfRange();
void fillGlyphCacheNotUnique();
void fillGlyphCacheFromString();
void fillGlyphCacheFailed();
void fillGlyphCacheNotSupported();
void fillGlyphCacheNotImplemented();
@ -157,6 +160,9 @@ AbstractFontTest::AbstractFontTest() {
&AbstractFontTest::glyphSizeAdvanceOutOfRange,
&AbstractFontTest::fillGlyphCache,
&AbstractFontTest::fillGlyphCacheOutOfRange,
&AbstractFontTest::fillGlyphCacheNotUnique,
&AbstractFontTest::fillGlyphCacheFromString,
&AbstractFontTest::fillGlyphCacheFailed,
&AbstractFontTest::fillGlyphCacheNotSupported,
&AbstractFontTest::fillGlyphCacheNotImplemented,
@ -1020,59 +1026,226 @@ struct DummyGlyphCache: AbstractGlyphCache {
void AbstractFontTest::fillGlyphCache() {
struct MyFont: AbstractFont {
FontFeatures doFeatures() const override { return {}; }
bool doIsOpened() const override { return true; }
FontFeatures doFeatures() const override {
return FontFeature::OpenData;
}
bool doIsOpened() const override { return _opened; }
void doClose() override {}
void doGlyphIdsInto(const Containers::StridedArrayView1D<const char32_t>&, const Containers::StridedArrayView1D<UnsignedInt>&) override {}
Properties doOpenData(Containers::ArrayView<const char>, Float) override {
_opened = true;
return {0.0f, 0.0f, 0.0f, 0.0f, 17};
}
void doGlyphIdsInto(const Containers::StridedArrayView1D<const char32_t>&, const Containers::StridedArrayView1D<UnsignedInt>&) override {
CORRADE_FAIL("This should not be called.");
}
Vector2 doGlyphSize(UnsignedInt) override { return {}; }
Vector2 doGlyphAdvance(UnsignedInt) override { return {}; }
Containers::Pointer<AbstractShaper> doCreateShaper() override { return {}; }
bool doFillGlyphCache(AbstractGlyphCache& cache, Containers::ArrayView<const char32_t> characters) override {
bool doFillGlyphCache(AbstractGlyphCache& cache, const Containers::StridedArrayView1D<const UnsignedInt>& glyphs) override {
CORRADE_COMPARE(cache.size(), (Vector3i{100, 100, 1}));
CORRADE_COMPARE_AS(characters, Containers::arrayView<char32_t>({
'h', 'e', 'l', 'o'
/* The glyph list isn't sorted in this case, nothing is implicitly
added to it either */
CORRADE_COMPARE_AS(glyphs, Containers::arrayView({
16u, 5u, 11u, 2u
}), TestSuite::Compare::Container);
called = true;
return true;
}
bool called = false;
private:
bool _opened = false;
} font;
/* Capture correct function name */
CORRADE_VERIFY(true);
/* Have to explicitly open in order to make glyphCount() non-zero */
CORRADE_VERIFY(font.openData(nullptr, 0.0f));
DummyGlyphCache cache{PixelFormat::R8Unorm, {100, 100}};
CORRADE_VERIFY(font.fillGlyphCache(cache, "helo"));
CORRADE_VERIFY(font.fillGlyphCache(cache, Containers::arrayView({16u, 5u, 11u, 2u})));
CORRADE_VERIFY(font.called);
}
void AbstractFontTest::fillGlyphCacheFailed() {
void AbstractFontTest::fillGlyphCacheOutOfRange() {
CORRADE_SKIP_IF_NO_DEBUG_ASSERT();
struct MyFont: AbstractFont {
FontFeatures doFeatures() const override { return {}; }
bool doIsOpened() const override { return true; }
FontFeatures doFeatures() const override {
return FontFeature::OpenData;
}
bool doIsOpened() const override { return _opened; }
void doClose() override {}
Properties doOpenData(Containers::ArrayView<const char>, Float) override {
_opened = true;
return {0.0f, 0.0f, 0.0f, 0.0f, 16};
}
void doGlyphIdsInto(const Containers::StridedArrayView1D<const char32_t>&, const Containers::StridedArrayView1D<UnsignedInt>&) override {}
Vector2 doGlyphSize(UnsignedInt) override { return {}; }
Vector2 doGlyphAdvance(UnsignedInt) override { return {}; }
Containers::Pointer<AbstractShaper> doCreateShaper() override { return {}; }
private:
bool _opened = false;
} font;
/* Have to explicitly open in order to make glyphCount() non-zero */
CORRADE_VERIFY(font.openData(nullptr, 0.0f));
std::ostringstream out;
Error redirectError{&out};
DummyGlyphCache cache{PixelFormat::R8Unorm, {100, 100}};
font.fillGlyphCache(cache, Containers::arrayView({0u, 15u, 3u, 16u, 80u}));
CORRADE_COMPARE(out.str(),
"Text::AbstractFont::fillGlyphCache(): index 16 out of range for 16 glyphs\n");
}
void AbstractFontTest::fillGlyphCacheNotUnique() {
CORRADE_SKIP_IF_NO_DEBUG_ASSERT();
struct MyFont: AbstractFont {
FontFeatures doFeatures() const override {
return FontFeature::OpenData;
}
bool doIsOpened() const override { return _opened; }
void doClose() override {}
Properties doOpenData(Containers::ArrayView<const char>, Float) override {
_opened = true;
return {0.0f, 0.0f, 0.0f, 0.0f, 16};
}
void doGlyphIdsInto(const Containers::StridedArrayView1D<const char32_t>&, const Containers::StridedArrayView1D<UnsignedInt>&) override {}
Vector2 doGlyphSize(UnsignedInt) override { return {}; }
Vector2 doGlyphAdvance(UnsignedInt) override { return {}; }
Containers::Pointer<AbstractShaper> doCreateShaper() override { return {}; }
bool doFillGlyphCache(AbstractGlyphCache&, Containers::ArrayView<const char32_t>) override {
private:
bool _opened = false;
} font;
/* Have to explicitly open in order to make glyphCount() non-zero */
CORRADE_VERIFY(font.openData(nullptr, 0.0f));
std::ostringstream out;
Error redirectError{&out};
DummyGlyphCache cache{PixelFormat::R8Unorm, {100, 100}};
font.fillGlyphCache(cache, Containers::arrayView({0u, 15u, 3u, 15u, 80u}));
CORRADE_COMPARE(out.str(),
"Text::AbstractFont::fillGlyphCache(): duplicate glyph 15\n");
}
void AbstractFontTest::fillGlyphCacheFromString() {
struct MyFont: AbstractFont {
FontFeatures doFeatures() const override {
return FontFeature::OpenData;
}
bool doIsOpened() const override { return _opened; }
void doClose() override {}
Properties doOpenData(Containers::ArrayView<const char>, Float) override {
_opened = true;
return {0.0f, 0.0f, 0.0f, 0.0f, 17};
}
void doGlyphIdsInto(const Containers::StridedArrayView1D<const char32_t>& characters, const Containers::StridedArrayView1D<UnsignedInt>& glyphs) override {
CORRADE_COMPARE_AS(characters, Containers::arrayView<char32_t>({
'h', 'e', 'l', 'l', 'o'
}), TestSuite::Compare::Container);
glyphs[0] = 16;
glyphs[1] = 2;
glyphs[2] = 11;
glyphs[3] = 11;
glyphs[4] = 5;
++glyphIdsIntoCalled;
}
Vector2 doGlyphSize(UnsignedInt) override { return {}; }
Vector2 doGlyphAdvance(UnsignedInt) override { return {}; }
Containers::Pointer<AbstractShaper> doCreateShaper() override { return {}; }
bool doFillGlyphCache(AbstractGlyphCache& cache, const Containers::StridedArrayView1D<const UnsignedInt>& glyphs) override {
CORRADE_COMPARE(cache.size(), (Vector3i{100, 100, 1}));
/* The array should be sorted by ID, without duplicates and with
the first ID being 0 if the cache doesn't have this font yet */
if(!cache.fontCount())
CORRADE_COMPARE_AS(glyphs, Containers::arrayView({
0u, 2u, 5u, 11u, 16u
}), TestSuite::Compare::Container);
else
CORRADE_COMPARE_AS(glyphs, Containers::arrayView({
2u, 5u, 11u, 16u
}), TestSuite::Compare::Container);
++fillGlyphCacheCalled;
return true;
}
Int glyphIdsIntoCalled = 0,
fillGlyphCacheCalled = 0;
private:
bool _opened = false;
} font;
/* Have to explicitly open in order to make glyphCount() non-zero */
CORRADE_VERIFY(font.openData(nullptr, 0.0f));
DummyGlyphCache cache{PixelFormat::R8Unorm, {100, 100}};
/* First time it should include the zero glyph as well */
CORRADE_VERIFY(font.fillGlyphCache(cache, "hello"));
CORRADE_COMPARE(font.glyphIdsIntoCalled, 1);
CORRADE_COMPARE(font.fillGlyphCacheCalled, 1);
/* Second time not anymore */
cache.addFont(10, &font);
CORRADE_VERIFY(font.fillGlyphCache(cache, "hello"));
CORRADE_COMPARE(font.glyphIdsIntoCalled, 2);
CORRADE_COMPARE(font.fillGlyphCacheCalled, 2);
}
void AbstractFontTest::fillGlyphCacheFailed() {
struct MyFont: AbstractFont {
FontFeatures doFeatures() const override {
return FontFeature::OpenData;
}
bool doIsOpened() const override { return _opened; }
void doClose() override {}
Properties doOpenData(Containers::ArrayView<const char>, Float) override {
_opened = true;
return {0.0f, 0.0f, 0.0f, 0.0f, 1};
}
void doGlyphIdsInto(const Containers::StridedArrayView1D<const char32_t>&, const Containers::StridedArrayView1D<UnsignedInt>& glyphs) override {
/* Set all to 0 to avoid an assert that the IDs are out of range */
for(UnsignedInt& i: glyphs)
i = 0;
}
Vector2 doGlyphSize(UnsignedInt) override { return {}; }
Vector2 doGlyphAdvance(UnsignedInt) override { return {}; }
Containers::Pointer<AbstractShaper> doCreateShaper() override { return {}; }
bool doFillGlyphCache(AbstractGlyphCache&, const Containers::StridedArrayView1D<const UnsignedInt>&) override {
return false;
}
bool called = false;
private:
bool _opened = false;
} font;
/* Capture correct function name */
CORRADE_VERIFY(true);
/* Have to explicitly open in order to make glyphCount() non-zero */
CORRADE_VERIFY(font.openData(nullptr, 0.0f));
DummyGlyphCache cache{PixelFormat::R8Unorm, {100, 100}};
CORRADE_VERIFY(!font.fillGlyphCache(cache, Containers::ArrayView<const UnsignedInt>{}));
CORRADE_VERIFY(!font.fillGlyphCache(cache, ""));
}
@ -1084,7 +1257,11 @@ void AbstractFontTest::fillGlyphCacheNotSupported() {
bool doIsOpened() const override { return true; }
void doClose() override {}
void doGlyphIdsInto(const Containers::StridedArrayView1D<const char32_t>&, const Containers::StridedArrayView1D<UnsignedInt>&) override {}
void doGlyphIdsInto(const Containers::StridedArrayView1D<const char32_t>&, const Containers::StridedArrayView1D<UnsignedInt>& glyphs) override {
/* Set all to 0 to avoid an assert that the IDs are out of range */
for(UnsignedInt& i: glyphs)
i = 0;
}
Vector2 doGlyphSize(UnsignedInt) override { return {}; }
Vector2 doGlyphAdvance(UnsignedInt) override { return {}; }
Containers::Pointer<AbstractShaper> doCreateShaper() override { return {}; }
@ -1093,29 +1270,48 @@ void AbstractFontTest::fillGlyphCacheNotSupported() {
std::ostringstream out;
Error redirectError{&out};
DummyGlyphCache cache{PixelFormat::R8Unorm, {100, 100}};
font.fillGlyphCache(cache, Containers::arrayView({0u, 15u}));
font.fillGlyphCache(cache, "hello");
CORRADE_COMPARE(out.str(), "Text::AbstractFont::fillGlyphCache(): feature not supported\n");
CORRADE_COMPARE(out.str(),
"Text::AbstractFont::fillGlyphCache(): feature not supported\n"
"Text::AbstractFont::fillGlyphCache(): feature not supported\n");
}
void AbstractFontTest::fillGlyphCacheNotImplemented() {
CORRADE_SKIP_IF_NO_ASSERT();
struct MyFont: AbstractFont {
FontFeatures doFeatures() const override { return {}; }
bool doIsOpened() const override { return true; }
FontFeatures doFeatures() const override {
return FontFeature::OpenData;
}
bool doIsOpened() const override { return _opened; }
void doClose() override {}
Properties doOpenData(Containers::ArrayView<const char>, Float) override {
_opened = true;
return {0.0f, 0.0f, 0.0f, 0.0f, 1};
}
void doGlyphIdsInto(const Containers::StridedArrayView1D<const char32_t>&, const Containers::StridedArrayView1D<UnsignedInt>&) override {}
Vector2 doGlyphSize(UnsignedInt) override { return {}; }
Vector2 doGlyphAdvance(UnsignedInt) override { return {}; }
Containers::Pointer<AbstractShaper> doCreateShaper() override { return {}; }
private:
bool _opened = false;
} font;
/* Have to explicitly open in order to make glyphCount() non-zero */
CORRADE_VERIFY(font.openData(nullptr, 0.0f));
std::ostringstream out;
Error redirectError{&out};
DummyGlyphCache cache{PixelFormat::R8Unorm, {100, 100}};
font.fillGlyphCache(cache, Containers::arrayView({0u}));
font.fillGlyphCache(cache, "hello");
CORRADE_COMPARE(out.str(), "Text::AbstractFont::fillGlyphCache(): feature advertised but not implemented\n");
CORRADE_COMPARE(out.str(),
"Text::AbstractFont::fillGlyphCache(): feature advertised but not implemented\n"
"Text::AbstractFont::fillGlyphCache(): feature advertised but not implemented\n");
}
void AbstractFontTest::fillGlyphCacheNoFont() {
@ -1135,8 +1331,11 @@ void AbstractFontTest::fillGlyphCacheNoFont() {
std::ostringstream out;
Error redirectError{&out};
DummyGlyphCache cache{PixelFormat::R8Unorm, {100, 100}};
font.fillGlyphCache(cache, Containers::arrayView({0u, 15u}));
font.fillGlyphCache(cache, "hello");
CORRADE_COMPARE(out.str(), "Text::AbstractFont::fillGlyphCache(): no font opened\n");
CORRADE_COMPARE(out.str(),
"Text::AbstractFont::fillGlyphCache(): no font opened\n"
"Text::AbstractFont::fillGlyphCache(): no font opened\n");
}
void AbstractFontTest::fillGlyphCacheInvalidUtf8() {

5
src/MagnumPlugins/MagnumFontConverter/Test/MagnumFontConverterTest.cpp

@ -523,7 +523,10 @@ void MagnumFontConverterTest::exportFontImageConversionFailed() {
return {16.0f, 25.0f, -10.0f, 39.7333f, 3};
}
void doGlyphIdsInto(const Containers::StridedArrayView1D<const char32_t>&, const Containers::StridedArrayView1D<UnsignedInt>&) override {}
void doGlyphIdsInto(const Containers::StridedArrayView1D<const char32_t>&, const Containers::StridedArrayView1D<UnsignedInt>& glyphs) override {
for(UnsignedInt& i: glyphs)
i = 0;
}
Vector2 doGlyphSize(UnsignedInt) override { return {}; }
Vector2 doGlyphAdvance(UnsignedInt) override { return {}; }
Containers::Pointer<AbstractShaper> doCreateShaper() override { return nullptr; }

Loading…
Cancel
Save