diff --git a/src/MagnumPlugins/MagnumFont/MagnumFont.cpp b/src/MagnumPlugins/MagnumFont/MagnumFont.cpp index dcaa0f3b1..a2572c395 100644 --- a/src/MagnumPlugins/MagnumFont/MagnumFont.cpp +++ b/src/MagnumPlugins/MagnumFont/MagnumFont.cpp @@ -26,6 +26,7 @@ #include "MagnumFont.h" #include +#include #include #include #include @@ -64,13 +65,14 @@ struct MagnumFont::Data { namespace { class MagnumFontLayouter: public AbstractLayouter { public: - explicit MagnumFontLayouter(const Containers::StridedArrayView1D& glyphAdvance, const AbstractGlyphCache& cache, Float fontSize, Float textSize, Containers::Array&& glyphs); + explicit MagnumFontLayouter(const Containers::StridedArrayView1D& glyphAdvance, const AbstractGlyphCache& cache, UnsignedInt fontId, Float fontSize, Float textSize, Containers::Array&& glyphs); private: Containers::Triple doRenderGlyph(UnsignedInt i) override; const Containers::StridedArrayView1D _glyphAdvance; const AbstractGlyphCache& _cache; + const UnsignedInt _fontId; const Float _fontSize, _textSize; const Containers::Array _glyphs; }; @@ -173,18 +175,46 @@ Containers::Pointer MagnumFont::doCreateGlyphCache() { _opened->conf.value("originalImageSize"), _opened->image->size(), _opened->conf.value("padding")}; - cache->setImage({}, *_opened->image); + /* Copy the opened image data directly to the GL texture because (unlike + image()) it matches the actual image size if it differs from + originalImageSize. A potential other way would be to create a + DistanceFieldGlyphCache instead, and call setDistanceFieldImage() on it, + but the font file itself doesn't contain any info about whether it + actually is a distance field, so that would be not really any better. */ + /** @todo clean this up once there's a way to upload the processed image + directly from the base class */ + cache->texture().setSubImage(0, {}, *_opened->image); - /* Fill glyph map */ const std::vector glyphs = _opened->conf.groups("glyph"); - for(std::size_t i = 0; i != glyphs.size(); ++i) - cache->insert(i, glyphs[i]->value("position"), glyphs[i]->value("rectangle")); + + /* Set the global invalid glyph to the same as the per-font invalid + glyph. */ + if(!glyphs.empty()) + cache->setInvalidGlyph(glyphs[0]->value("position"), glyphs[0]->value("rectangle")); + + /* Add a font, fill the glyph map */ + const UnsignedInt fontId = cache->addFont(glyphs.size(), this); + for(std::size_t i = 0; i < glyphs.size(); ++i) + cache->addGlyph(fontId, i, glyphs[i]->value("position"), glyphs[i]->value("rectangle")); /* GCC 4.8 needs extra help here */ return Containers::Pointer{Utility::move(cache)}; } Containers::Pointer MagnumFont::doLayout(const AbstractGlyphCache& cache, const Float size, const Containers::StringView text) { + /* Not yet, at least */ + if(cache.size().z() != 1) { + Error{} << "Text::MagnumFont::layout(): array glyph caches are not supported"; + return {}; + } + + /* Find this font in the cache */ + Containers::Optional fontId = cache.findFont(this); + if(!fontId) { + Error{} << "Text::MagnumFont::layout(): font not found among" << cache.fontCount() << "fonts in passed glyph cache"; + return {}; + } + /* Get glyph codes from characters */ Containers::Array glyphs; arrayReserve(glyphs, text.size()); @@ -195,25 +225,26 @@ Containers::Pointer MagnumFont::doLayout(const AbstractGlyphCa i = codepointNext.second(); } - return Containers::pointer(stridedArrayView(_opened->glyphs).slice(&Data::Glyph::advance), cache, this->size(), size, Utility::move(glyphs)); + return Containers::pointer(stridedArrayView(_opened->glyphs).slice(&Data::Glyph::advance), cache, *fontId, this->size(), size, Utility::move(glyphs)); } namespace { -MagnumFontLayouter::MagnumFontLayouter(const Containers::StridedArrayView1D& glyphAdvance, const AbstractGlyphCache& cache, const Float fontSize, const Float textSize, Containers::Array&& glyphs): AbstractLayouter{UnsignedInt(glyphs.size())}, _glyphAdvance{glyphAdvance}, _cache(cache), _fontSize{fontSize}, _textSize{textSize}, _glyphs{Utility::move(glyphs)} {} +MagnumFontLayouter::MagnumFontLayouter(const Containers::StridedArrayView1D& glyphAdvance, const AbstractGlyphCache& cache, const UnsignedInt fontId, const Float fontSize, const Float textSize, Containers::Array&& glyphs): AbstractLayouter{UnsignedInt(glyphs.size())}, _glyphAdvance{glyphAdvance}, _cache(cache), _fontId{fontId}, _fontSize{fontSize}, _textSize{textSize}, _glyphs{Utility::move(glyphs)} {} Containers::Triple MagnumFontLayouter::doRenderGlyph(const UnsignedInt i) { - /* Position of the texture in the resulting glyph, texture coordinates */ - Vector2i position; - Range2Di rectangle; - std::tie(position, rectangle) = _cache[_glyphs[i]]; + /* Offset of the glyph rectangle relative to the cursor, layer, texture + coordinates. We checked that the glyph cache is 2D in doLayout() so the + layer can be ignored. */ + const Containers::Triple glyph = _cache.glyph(_fontId, _glyphs[i]); + CORRADE_INTERNAL_ASSERT(glyph.second() == 0); /* Normalized texture coordinates */ - const auto textureCoordinates = Range2D(rectangle).scaled(1.0f/Vector2(_cache.textureSize())); + const auto textureCoordinates = Range2D{glyph.third()}.scaled(1.0f/Vector2{_cache.size().xy()}); /* Quad rectangle, computed from texture rectangle, denormalized to requested text size */ - const auto quadRectangle = Range2D(Range2Di::fromSize(position, rectangle.size())).scaled(Vector2(_textSize/_fontSize)); + const auto quadRectangle = Range2D{Range2Di::fromSize(glyph.first(), glyph.third().size())}.scaled(Vector2{_textSize/_fontSize}); /* Advance for given glyph, denormalized to requested text size */ const Vector2 advance = _glyphAdvance[_glyphs[i]]*(_textSize/_fontSize); diff --git a/src/MagnumPlugins/MagnumFont/Test/CMakeLists.txt b/src/MagnumPlugins/MagnumFont/Test/CMakeLists.txt index 4656e0a51..711eb1ca8 100644 --- a/src/MagnumPlugins/MagnumFont/Test/CMakeLists.txt +++ b/src/MagnumPlugins/MagnumFont/Test/CMakeLists.txt @@ -72,6 +72,8 @@ if(MAGNUM_BUILD_GL_TESTS) MagnumTrade MagnumOpenGLTester FILES + font-processed.conf + font-processed.tga font.conf font-empty.conf font.tga) diff --git a/src/MagnumPlugins/MagnumFont/Test/MagnumFontGLTest.cpp b/src/MagnumPlugins/MagnumFont/Test/MagnumFontGLTest.cpp index bfdae1b04..da53346f8 100644 --- a/src/MagnumPlugins/MagnumFont/Test/MagnumFontGLTest.cpp +++ b/src/MagnumPlugins/MagnumFont/Test/MagnumFontGLTest.cpp @@ -24,8 +24,10 @@ */ #include +#include #include #include /** @todo remove once AbstractFont is -free */ +#include #include #include "Magnum/Image.h" @@ -35,6 +37,7 @@ #ifdef MAGNUM_TARGET_GLES #include "Magnum/DebugTools/TextureImage.h" #endif +#include "Magnum/Math/Range.h" #include "Magnum/GL/OpenGLTester.h" #include "Magnum/Text/AbstractFont.h" #include "Magnum/Text/GlyphCache.h" @@ -48,6 +51,7 @@ struct MagnumFontGLTest: GL::OpenGLTester { explicit MagnumFontGLTest(); void createGlyphCache(); + void createGlyphCacheProcessedImage(); void createGlyphCacheNoGlyphs(); /* Explicitly forbid system-wide plugin dependencies */ @@ -57,6 +61,7 @@ struct MagnumFontGLTest: GL::OpenGLTester { MagnumFontGLTest::MagnumFontGLTest() { addTests({&MagnumFontGLTest::createGlyphCache, + &MagnumFontGLTest::createGlyphCacheProcessedImage, &MagnumFontGLTest::createGlyphCacheNoGlyphs}); /* Load the plugins directly from the build tree. Otherwise they're either @@ -81,16 +86,28 @@ void MagnumFontGLTest::createGlyphCache() { CORRADE_VERIFY(cache); MAGNUM_VERIFY_NO_GL_ERROR(); + /* The font should associate itself with the cache */ + CORRADE_COMPARE(cache->fontCount(), 1); + CORRADE_COMPARE(cache->findFont(font.get()), 0); + /* Verify glyph contents */ - CORRADE_COMPARE(cache->glyphCount(), 4); - CORRADE_COMPARE((*cache)[0], std::make_pair( + CORRADE_COMPARE(cache->glyphCount(), 5); + CORRADE_COMPARE(cache->fontGlyphCount(0), 4); + CORRADE_COMPARE(cache->glyph(0), Containers::triple( + Vector2i{-16, -8}, + 0, + Range2Di{{0, 0}, {32, 16}})); + CORRADE_COMPARE(cache->glyph(0, 0), Containers::triple( Vector2i{-16, -8}, + 0, Range2Di{{0, 0}, {32, 16}})); - CORRADE_COMPARE((*cache)[font->glyphId(U'W')], std::make_pair( + CORRADE_COMPARE(cache->glyph(0, font->glyphId(U'W')), Containers::triple( Vector2i{9, 26}, + 0, Range2Di{{0, 4}, {40, 64}})); - CORRADE_COMPARE((*cache)[font->glyphId(U'e')], std::make_pair( + CORRADE_COMPARE(cache->glyph(0, font->glyphId(U'e')), Containers::triple( Vector2i{9, 4}, + 0, Range2Di{{20, 0}, {128, 48}})); /* ě has deliberately the same glyph data as e */ UnsignedInt eId = font->glyphId( @@ -104,25 +121,105 @@ void MagnumFontGLTest::createGlyphCache() { U'ě' #endif ); - CORRADE_COMPARE((*cache)[eId], std::make_pair( + CORRADE_COMPARE(cache->glyph(0, eId), Containers::triple( Vector2i{9, 4}, + 0, Range2Di{{20, 0}, {128, 48}})); if(!(_importerManager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_importerManager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found, not testing glyph cache contents"); + #ifdef MAGNUM_TARGET_GLES2 + CORRADE_SKIP("Luminance format used on GLES2 isn't usable for framebuffer reading, can't verify texture contents."); + #else /* Verify the actual texture. It should be the image file verbatim. On GLES we cannot really verify that the size matches, but at least something. */ #ifndef MAGNUM_TARGET_GLES Image2D image = static_cast(*cache).texture().image(0, {PixelFormat::R8Unorm}); #else - Image2D image = DebugTools::textureSubImage(static_cast(*cache).texture(), 0, {{}, {8, 4}}, {PixelFormat::R8Unorm}); + Image2D image = DebugTools::textureSubImage(static_cast(*cache).texture(), 0, {{}, {128, 64}}, {PixelFormat::R8Unorm}); #endif + MAGNUM_VERIFY_NO_GL_ERROR(); CORRADE_COMPARE_WITH(image, Utility::Path::join(MAGNUMFONT_TEST_DIR, "font.tga"), DebugTools::CompareImageToFile{_importerManager}); + #endif +} + +void MagnumFontGLTest::createGlyphCacheProcessedImage() { + /* Compared to createGlyphCache(), this tests the case where the image size + is different from the actual size to which glyphs are positioned */ + + Containers::Pointer font = _fontManager.instantiate("MagnumFont"); + + CORRADE_VERIFY(font->openFile(Utility::Path::join(MAGNUMFONT_TEST_DIR, "font-processed.conf"), 0.0f)); + + Containers::Pointer cache = font->createGlyphCache(); + CORRADE_VERIFY(cache); + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* The font should associate itself with the cache */ + CORRADE_COMPARE(cache->fontCount(), 1); + CORRADE_COMPARE(cache->findFont(font.get()), 0); + + /* Verify glyph contents */ + CORRADE_COMPARE(cache->glyphCount(), 5); + CORRADE_COMPARE(cache->fontGlyphCount(0), 4); + CORRADE_COMPARE(cache->glyph(0), Containers::triple( + Vector2i{-16, -8}, + 0, + Range2Di{{0, 0}, {32, 16}})); + CORRADE_COMPARE(cache->glyph(0, 0), Containers::triple( + Vector2i{-16, -8}, + 0, + Range2Di{{0, 0}, {32, 16}})); + CORRADE_COMPARE(cache->glyph(0, font->glyphId(U'W')), Containers::triple( + Vector2i{9, 26}, + 0, + Range2Di{{0, 4}, {40, 64}})); + CORRADE_COMPARE(cache->glyph(0, font->glyphId(U'e')), Containers::triple( + Vector2i{9, 4}, + 0, + Range2Di{{20, 0}, {128, 48}})); + /* ě has deliberately the same glyph data as e */ + UnsignedInt eId = font->glyphId( + /* MSVC (but not clang-cl) doesn't support UTF-8 in char32_t literals + but it does it regular strings. Still a problem in MSVC 2022, what a + trash fire, can't you just give up on those codepage insanities + already, ffs?! */ + #if defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG) + U'\u011B' + #else + U'ě' + #endif + ); + CORRADE_COMPARE(cache->glyph(0, eId), Containers::triple( + Vector2i{9, 4}, + 0, + Range2Di{{20, 0}, {128, 48}})); + + if(!(_importerManager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_importerManager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found, not testing glyph cache contents"); + + #ifdef MAGNUM_TARGET_GLES2 + CORRADE_SKIP("Luminance format used on GLES2 isn't usable for framebuffer reading, can't verify texture contents."); + #else + /* Verify the actual texture. It should be the image file verbatim. On GLES + we cannot really verify that the size matches, but at least + something. */ + #ifndef MAGNUM_TARGET_GLES + Image2D image = static_cast(*cache).texture().image(0, {PixelFormat::R8Unorm}); + #else + Image2D image = DebugTools::textureSubImage(static_cast(*cache).texture(), 0, {{}, {8, 4}}, {PixelFormat::R8Unorm}); + #endif + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH(image, + Utility::Path::join(MAGNUMFONT_TEST_DIR, "font-processed.tga"), + DebugTools::CompareImageToFile{_importerManager}); + #endif } void MagnumFontGLTest::createGlyphCacheNoGlyphs() { @@ -134,11 +231,17 @@ void MagnumFontGLTest::createGlyphCacheNoGlyphs() { CORRADE_VERIFY(cache); MAGNUM_VERIFY_NO_GL_ERROR(); + /* The font should associate itself with the cache even in this case */ + CORRADE_COMPARE(cache->fontCount(), 1); + CORRADE_COMPARE(cache->findFont(font.get()), 0); + /* There's just the empty glyph added by the cache itself, nothing else */ CORRADE_COMPARE(cache->glyphCount(), 1); - CORRADE_COMPARE((*cache)[0], std::make_pair( - Vector2i{0, 0}, - Range2Di{{0, 0}, {0, 0}})); + CORRADE_COMPARE(cache->fontGlyphCount(0), 0); + CORRADE_COMPARE(cache->glyph(0), Containers::triple( + Vector2i{}, + 0, + Range2Di{})); /* Not testing the image as there's no special codepath taken for it if there are no glyphs */ diff --git a/src/MagnumPlugins/MagnumFont/Test/MagnumFontTest.cpp b/src/MagnumPlugins/MagnumFont/Test/MagnumFontTest.cpp index ed654adb4..e77530336 100644 --- a/src/MagnumPlugins/MagnumFont/Test/MagnumFontTest.cpp +++ b/src/MagnumPlugins/MagnumFont/Test/MagnumFontTest.cpp @@ -24,6 +24,7 @@ */ #include +#include #include #include #include /** @todo remove once AbstractFont is -free */ @@ -33,6 +34,8 @@ #include #include "Magnum/FileCallback.h" +#include "Magnum/PixelFormat.h" +#include "Magnum/Math/Range.h" #include "Magnum/Text/AbstractFont.h" #include "Magnum/Text/AbstractGlyphCache.h" #include "Magnum/Trade/AbstractImporter.h" @@ -48,6 +51,8 @@ struct MagnumFontTest: TestSuite::Tester { void properties(); void layout(); void layoutNoGlyphsInCache(); + void layoutNoFontInCache(); + void layoutArrayCache(); void fileCallbackImage(); void fileCallbackImageNotFound(); @@ -73,6 +78,8 @@ MagnumFontTest::MagnumFontTest() { Containers::arraySize(LayoutData)); addTests({&MagnumFontTest::layoutNoGlyphsInCache, + &MagnumFontTest::layoutNoFontInCache, + &MagnumFontTest::layoutArrayCache, &MagnumFontTest::fileCallbackImage, &MagnumFontTest::fileCallbackImageNotFound}); @@ -143,11 +150,14 @@ void MagnumFontTest::layout() { GlyphCacheFeatures doFeatures() const override { return {}; } void doSetImage(const Vector2i&, const ImageView2D&) override {} - } cache{Vector2i{256}}; - cache.insert(font->glyphId(U'W'), {25, 34}, {{0, 8}, {16, 128}}); - cache.insert(font->glyphId(U'e'), {25, 12}, {{16, 4}, {64, 32}}); + } cache{PixelFormat::R8Unorm, Vector2i{256}}; + + UnsignedInt fontId = cache.addFont(20, font.get()); + + cache.addGlyph(fontId, font->glyphId(U'W'), {25, 34}, {{0, 8}, {16, 128}}); + cache.addGlyph(fontId, font->glyphId(U'e'), {25, 12}, {{16, 4}, {64, 32}}); /* ě has deliberately the same glyph data as e */ - cache.insert(font->glyphId( + cache.addGlyph(fontId, font->glyphId( /* MSVC (but not clang-cl) doesn't support UTF-8 in char32_t literals but it does it regular strings. Still a problem in MSVC 2022, what a trash fire, can't you just give up on those codepage insanities @@ -194,13 +204,16 @@ void MagnumFontTest::layoutNoGlyphsInCache() { CORRADE_VERIFY(font->openFile(Utility::Path::join(MAGNUMFONT_TEST_DIR, "font.conf"), 0.0f)); - /* Tests the case where createGlyphCache() was accidentally not called */ struct: AbstractGlyphCache { using AbstractGlyphCache::AbstractGlyphCache; GlyphCacheFeatures doFeatures() const override { return {}; } void doSetImage(const Vector2i&, const ImageView2D&) override {} - } cache{Vector2i{256}}; + } cache{PixelFormat::R8Unorm, Vector2i{256}}; + + /* Add a font that is associated with this one but createGlyphCache() was + actually not called for it */ + cache.addFont(15, font.get()); auto layouter = font->layout(cache, 0.5f, "Wave"); CORRADE_VERIFY(layouter); @@ -229,6 +242,45 @@ void MagnumFontTest::layoutNoGlyphsInCache() { CORRADE_COMPARE(cursorPosition, Vector2(0.375f, 0.0f)); } +void MagnumFontTest::layoutNoFontInCache() { + Containers::Pointer font = _fontManager.instantiate("MagnumFont"); + + CORRADE_VERIFY(font->openFile(Utility::Path::join(MAGNUMFONT_TEST_DIR, "font.conf"), 0.0f)); + + struct: AbstractGlyphCache { + using AbstractGlyphCache::AbstractGlyphCache; + + GlyphCacheFeatures doFeatures() const override { return {}; } + void doSetImage(const Vector2i&, const ImageView2D&) override {} + } cache{PixelFormat::R8Unorm, Vector2i{256}}; + + /* Add a font that isn't associated with this one */ + cache.addFont(15); + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!font->layout(cache, 0.5f, "Wave")); + CORRADE_COMPARE(out.str(), "Text::MagnumFont::layout(): font not found among 1 fonts in passed glyph cache\n"); +} + +void MagnumFontTest::layoutArrayCache() { + Containers::Pointer font = _fontManager.instantiate("MagnumFont"); + + CORRADE_VERIFY(font->openFile(Utility::Path::join(MAGNUMFONT_TEST_DIR, "font.conf"), 0.0f)); + + struct: AbstractGlyphCache { + using AbstractGlyphCache::AbstractGlyphCache; + + GlyphCacheFeatures doFeatures() const override { return {}; } + void doSetImage(const Vector2i&, const ImageView2D&) override {} + } cache{PixelFormat::R8Unorm, {256, 128, 3}}; + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!font->layout(cache, 0.5f, "Wave")); + CORRADE_COMPARE(out.str(), "Text::MagnumFont::layout(): array glyph caches are not supported\n"); +} + void MagnumFontTest::fileCallbackImage() { Containers::Pointer font = _fontManager.instantiate("MagnumFont"); CORRADE_VERIFY(font->features() & FontFeature::FileCallback); diff --git a/src/MagnumPlugins/MagnumFont/Test/font-processed.conf b/src/MagnumPlugins/MagnumFont/Test/font-processed.conf new file mode 100644 index 000000000..f893ebd07 --- /dev/null +++ b/src/MagnumPlugins/MagnumFont/Test/font-processed.conf @@ -0,0 +1,39 @@ +version=1 +image=font-processed.tga +originalImageSize=128 64 +padding=16 8 +fontSize=16 +ascent=25 +descent=-10 +lineHeight=39.7333 +[char] +unicode=57 +glyph=2 +[char] +unicode=61 +glyph=0 +[char] +unicode=65 +glyph=1 +[char] +unicode=76 +glyph=0 +[char] +unicode=11B +glyph=3 +[glyph] +advance=8 0 +position=0 0 +rectangle=16 8 16 8 +[glyph] +advance=12 0 +position=25 12 +rectangle=36 8 112 40 +[glyph] +advance=23 0 +position=25 34 +rectangle=16 12 24 56 +[glyph] +advance=12 0 +position=25 12 +rectangle=36 8 112 40 diff --git a/src/MagnumPlugins/MagnumFont/Test/font-processed.tga b/src/MagnumPlugins/MagnumFont/Test/font-processed.tga new file mode 100644 index 000000000..823f2d667 Binary files /dev/null and b/src/MagnumPlugins/MagnumFont/Test/font-processed.tga differ diff --git a/src/MagnumPlugins/MagnumFont/Test/font.tga b/src/MagnumPlugins/MagnumFont/Test/font.tga index 823f2d667..d449ccadd 100644 Binary files a/src/MagnumPlugins/MagnumFont/Test/font.tga and b/src/MagnumPlugins/MagnumFont/Test/font.tga differ diff --git a/src/MagnumPlugins/MagnumFontConverter/MagnumFontConverter.cpp b/src/MagnumPlugins/MagnumFontConverter/MagnumFontConverter.cpp index e14d96ef9..51cdab76e 100644 --- a/src/MagnumPlugins/MagnumFontConverter/MagnumFontConverter.cpp +++ b/src/MagnumPlugins/MagnumFontConverter/MagnumFontConverter.cpp @@ -27,9 +27,11 @@ #include /* std::sort() */ #include +#include #include #include -#include +#include +#include #include #include @@ -52,8 +54,24 @@ FontConverterFeatures MagnumFontConverter::doFeatures() const { } std::vector>> MagnumFontConverter::doExportFontToData(AbstractFont& font, AbstractGlyphCache& cache, const std::string& filename, const std::u32string& characters) const { - if(!(cache.features() & GlyphCacheFeature::ImageDownload)) { - Error{} << "Text::MagnumFontConverter::exportFontToData(): passed glyph cache doesn't support image download"; + if(cache.size().z() != 1) { + Error{} << "Text::MagnumFontConverter::exportFontToData(): exporting array glyph caches is not supported"; + return {}; + } + if(cache.features() & GlyphCacheFeature::ImageProcessing && !(cache.features() >= GlyphCacheFeature::ProcessedImageDownload)) { + Error{} << "Text::MagnumFontConverter::exportFontToData(): glyph cache has image processing but doesn't support image download"; + return {}; + } + + Containers::Optional fontId = cache.findFont(&font); + #ifdef MAGNUM_BUILD_DEPRECATED + /* Make it work with the old-style glyph cache filling that adds exactly + one font into the cache and doesn't associate any pointer with it */ + if(!fontId && cache.fontCount() == 1 && cache.fontPointer(0) == nullptr) + fontId = 0; + #endif + if(!fontId) { + Error{} << "Text::MagnumFontConverter::exportFontToData(): font not found among" << cache.fontCount() << "fonts in passed glyph cache"; return {}; } @@ -61,7 +79,7 @@ std::vector>> MagnumFontConverter configuration.setValue("version", 1); configuration.setValue("image", Utility::Path::split(filename).second() + ".tga"); - configuration.setValue("originalImageSize", cache.textureSize()); + configuration.setValue("originalImageSize", cache.size().xy()); configuration.setValue("padding", cache.padding()); configuration.setValue("fontSize", font.size()); configuration.setValue("ascent", font.ascent()); @@ -70,8 +88,11 @@ std::vector>> MagnumFontConverter /* Get the glyphs and sort them for predictable output */ std::vector>> sortedGlyphs; - for(const std::pair>& glyph: cache) - sortedGlyphs.emplace_back(glyph); + const Containers::StridedArrayView1D offsets = cache.glyphOffsets(); + const Containers::StridedArrayView1D rectangles = cache.glyphRectangles(); + for(UnsignedInt fontGlyphId = 0; fontGlyphId != cache.fontGlyphCount(*fontId); ++fontGlyphId) + if(const UnsignedInt glyphId = cache.glyphId(*fontId, fontGlyphId)) + sortedGlyphs.emplace_back(fontGlyphId, std::make_pair(offsets[glyphId], rectangles[glyphId])); std::sort(sortedGlyphs.begin(), sortedGlyphs.end(), [](const std::pair>& a, const std::pair>& b) { @@ -108,11 +129,15 @@ std::vector>> MagnumFontConverter from the values so they aren't added twice when using the font later */ /** @todo Some better way to handle this padding stuff */ for(UnsignedInt oldGlyphId: inverseGlyphIdMap) { - std::pair glyph = cache[oldGlyphId]; + /** @todo this branch is messy, clean up; also there's now a + distinction between a cache-global invalid glyph and font-local, + what to do there? */ + Containers::Triple glyph = + oldGlyphId ? cache.glyph(*fontId, oldGlyphId) : cache.glyph(0); Utility::ConfigurationGroup* group = configuration.addGroup("glyph"); group->setValue("advance", font.glyphAdvance(oldGlyphId)); - group->setValue("position", glyph.first+cache.padding()); - group->setValue("rectangle", glyph.second.padded(-cache.padding())); + group->setValue("position", glyph.first() + cache.padding()); + group->setValue("rectangle", glyph.third().padded(-cache.padding())); } std::ostringstream confOut; @@ -121,9 +146,20 @@ std::vector>> MagnumFontConverter Containers::Array confData{confStr.size()}; std::copy(confStr.begin(), confStr.end(), confData.begin()); - /* Save cache image */ - Containers::Optional> tgaData = Trade::TgaImageConverter().convertToData(cache.image()); - if(!tgaData) return {}; + /* Save cache image. Either the source image or the processed one if the + cache has image processing. */ + Containers::Optional> tgaData; + if(cache.features() & GlyphCacheFeature::ImageProcessing) { + const Image3D image3 = cache.processedImage(); + tgaData = Trade::TgaImageConverter().convertToData(ImageView2D{image3.format(), image3.size().xy(), image3.data()}); + } else { + const ImageView3D image3 = cache.image(); + tgaData = Trade::TgaImageConverter().convertToData(ImageView2D{image3.format(), image3.size().xy(), image3.data()}); + } + if(!tgaData) { + Error{} << "Text::MagnumFontConverter::exportFontToData(): cannot create a TGA image"; + return {}; + } std::vector>> out; out.emplace_back(filename + ".conf", Utility::move(confData)); diff --git a/src/MagnumPlugins/MagnumFontConverter/MagnumFontConverter.h b/src/MagnumPlugins/MagnumFontConverter/MagnumFontConverter.h index f75cd2408..9b6ceada5 100644 --- a/src/MagnumPlugins/MagnumFontConverter/MagnumFontConverter.h +++ b/src/MagnumPlugins/MagnumFontConverter/MagnumFontConverter.h @@ -56,7 +56,13 @@ namespace Magnum { namespace Text { Expects filename prefix, creates two files, `prefix.conf` and `prefix.tga`. See @ref MagnumFont for more information about the font. The plugin requires the -passed @ref AbstractGlyphCache to support @ref GlyphCacheFeature::ImageDownload. +passed @ref AbstractGlyphCache to be 2D, have a format compatible with the +@relativeref{Trade,TgaImageConverter} plugin and either not have +@ref GlyphCacheFeature::ImageProcessing or support both +@ref GlyphCacheFeature::ImageProcessing and +@relativeref{GlyphCacheFeature,ProcessedImageDownload}. For a cache with +multiple fonts, @ref AbstractGlyphCache::findFont() is used to match the font +with the passed instance. @section Text-MagnumFontConverter-usage Usage diff --git a/src/MagnumPlugins/MagnumFontConverter/Test/CMakeLists.txt b/src/MagnumPlugins/MagnumFontConverter/Test/CMakeLists.txt index 15e73bd23..ff02c6680 100644 --- a/src/MagnumPlugins/MagnumFontConverter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/MagnumFontConverter/Test/CMakeLists.txt @@ -60,6 +60,8 @@ corrade_add_test(MagnumFontConverterTest MagnumFontConverterTest.cpp MagnumText MagnumTrade FILES + ../../MagnumFont/Test/font-processed.conf + ../../MagnumFont/Test/font-processed.tga ../../MagnumFont/Test/font.conf ../../MagnumFont/Test/font.tga font-empty-cache.conf diff --git a/src/MagnumPlugins/MagnumFontConverter/Test/MagnumFontConverterTest.cpp b/src/MagnumPlugins/MagnumFontConverter/Test/MagnumFontConverterTest.cpp index 4f846d79a..3c6b97195 100644 --- a/src/MagnumPlugins/MagnumFontConverter/Test/MagnumFontConverterTest.cpp +++ b/src/MagnumPlugins/MagnumFontConverter/Test/MagnumFontConverterTest.cpp @@ -26,9 +26,11 @@ #include #include #include -#include /** @todo remove once AbstractFont is -free */ +#include +#include /** @todo remove once AbstractFontConverter is STL-free */ #include #include +#include #include #include @@ -36,6 +38,7 @@ #include "Magnum/ImageView.h" #include "Magnum/PixelFormat.h" #include "Magnum/DebugTools/CompareImage.h" +#include "Magnum/Math/Range.h" #include "Magnum/Text/AbstractGlyphCache.h" #include "Magnum/Text/AbstractFont.h" #include "Magnum/Text/AbstractFontConverter.h" @@ -51,8 +54,16 @@ struct MagnumFontConverterTest: TestSuite::Tester { explicit MagnumFontConverterTest(); void exportFont(); + #ifdef MAGNUM_BUILD_DEPRECATED + void exportFontOldStyleCache(); + #endif void exportFontEmptyCache(); - void exportFontNoGlyphCacheImageDownload(); + void exportFontImageProcessingGlyphCache(); + void exportFontImageProcessingGlyphCacheNoDownload(); + + void exportFontArrayCache(); + void exportFontNotFoundInCache(); + void exportFontImageConversionFailed(); /* Explicitly forbid system-wide plugin dependencies */ PluginManager::Manager _imageConverterManager{"nonexistent"}; @@ -62,8 +73,16 @@ struct MagnumFontConverterTest: TestSuite::Tester { MagnumFontConverterTest::MagnumFontConverterTest() { addTests({&MagnumFontConverterTest::exportFont, + #ifdef MAGNUM_BUILD_DEPRECATED + &MagnumFontConverterTest::exportFontOldStyleCache, + #endif &MagnumFontConverterTest::exportFontEmptyCache, - &MagnumFontConverterTest::exportFontNoGlyphCacheImageDownload}); + &MagnumFontConverterTest::exportFontImageProcessingGlyphCache, + &MagnumFontConverterTest::exportFontImageProcessingGlyphCacheNoDownload, + + &MagnumFontConverterTest::exportFontArrayCache, + &MagnumFontConverterTest::exportFontNotFoundInCache, + &MagnumFontConverterTest::exportFontImageConversionFailed}); /* Load the plugins directly from the build tree. Otherwise they're either static and already loaded or not present in the build tree. */ @@ -143,19 +162,95 @@ void MagnumFontConverterTest::exportFont() { MyFont font; font.openFile({}, {}); + /* Create a cache. Two fonts, only the second one should be added. */ struct: AbstractGlyphCache { using AbstractGlyphCache::AbstractGlyphCache; - GlyphCacheFeatures doFeatures() const override { return GlyphCacheFeature::ImageDownload; } + GlyphCacheFeatures doFeatures() const override { return {}; } + void doSetImage(const Vector2i&, const ImageView2D&) override {} + } cache{PixelFormat::R8Unorm, {128, 64}, {16, 8}}; + + /* Override the not found glyph to be in bounds as well */ + cache.setInvalidGlyph({}, {{16, 8}, {16, 8}}); + + /* This font and all its glyphs should be skipped */ + UnsignedInt unusedFontId = cache.addFont(56); + cache.addGlyph(unusedFontId, 33, {16, 20}, {{60, 40}, {80, 50}}); + + UnsignedInt fontId = cache.addFont(25, &font); + cache.addGlyph(fontId, font.glyphId(U'W'), {25, 34}, {{16, 12}, {24, 56}}); + cache.addGlyph(fontId, font.glyphId(U'e'), {25, 12}, {{36, 8}, {112, 40}}); + /* ě has deliberately the same glyph data as e */ + cache.addGlyph(fontId, font.glyphId( + /* MSVC (but not clang-cl) doesn't support UTF-8 in char32_t literals + but it does it regular strings. Still a problem in MSVC 2022, what a + trash fire, can't you just give up on those codepage insanities + already, ffs?! */ + #if defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG) + U'\u011B' + #else + U'ě' + #endif + ), {25, 12}, {{36, 8}, {112, 40}}); + + /* Set the cache image to some non-trivial contents. Compared to the + exportFontImageProcessingGlyphCache() test the image is 16x bigger, so + do some fancy expansion there. */ + const char pixels[]{ + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v' + }; + for(std::size_t y = 0; y != 16; ++y) + for(std::size_t x = 0; x != 16; ++x) + Utility::copy( + Containers::StridedArrayView2D{pixels, {4, 8}}, + cache.image().pixels()[0].exceptPrefix({y, x}).every({16, 16})); + + /* Convert the file */ + Containers::Pointer converter = _fontConverterManager.instantiate("MagnumFontConverter"); + CORRADE_VERIFY(converter->exportFontToFile(font, cache, Utility::Path::join(MAGNUMFONTCONVERTER_TEST_WRITE_DIR, "font"), "Waveě")); + + /* Verify font parameters */ + CORRADE_COMPARE_AS(confFilename, + Utility::Path::join(MAGNUMFONT_TEST_DIR, "font.conf"), + TestSuite::Compare::File); + + if(!(_importerManager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_importerManager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found, not testing glyph cache contents"); + + /* Verify font image */ + CORRADE_COMPARE_WITH(tgaFilename, + Utility::Path::join(MAGNUMFONT_TEST_DIR, "font.tga"), + DebugTools::CompareImageFile{_importerManager}); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +void MagnumFontConverterTest::exportFontOldStyleCache() { + /* Like exportFont(), but using the deprecated cache APIs to verify that + the cache contents are still copied the same */ + + Containers::String confFilename = Utility::Path::join(MAGNUMFONTCONVERTER_TEST_WRITE_DIR, "font.conf"); + Containers::String tgaFilename = Utility::Path::join(MAGNUMFONTCONVERTER_TEST_WRITE_DIR, "font.tga"); + /* Remove previously created files */ + if(Utility::Path::exists(confFilename)) + CORRADE_VERIFY(Utility::Path::remove(confFilename)); + if(Utility::Path::exists(tgaFilename)) + CORRADE_VERIFY(Utility::Path::remove(tgaFilename)); + + MyFont font; + font.openFile({}, {}); + + /* Create a cache the old way, i.e. insert() which results in exactly one + font added and no association with a pointer */ + CORRADE_IGNORE_DEPRECATED_PUSH + struct: AbstractGlyphCache { + using AbstractGlyphCache::AbstractGlyphCache; + + GlyphCacheFeatures doFeatures() const override { return {}; } void doSetImage(const Vector2i&, const ImageView2D&) override {} - Image2D doImage() override { - return Image2D{PixelFormat::R8Unorm, {8, 4}, Containers::Array{InPlaceInit, { - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', - 'o', 'p', 'q', 'r', 's', 't', 'u', 'v' - }}}; - } } cache{{128, 64}, {16, 8}}; /* Override the not found glyph to be in bounds as well */ cache.insert(0, {}, {{16, 8}, {16, 8}}); @@ -173,6 +268,22 @@ void MagnumFontConverterTest::exportFont() { U'ě' #endif ), {25, 12}, {{36, 8}, {112, 40}}); + CORRADE_IGNORE_DEPRECATED_POP + + /* Set the cache image to some non-trivial contents. There's no "old way" + to do this, also compared to the exportFontImageProcessingGlyphCache() + test the image is 16x bigger, so do some fancy expansion there. */ + const char pixels[]{ + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v' + }; + for(std::size_t y = 0; y != 16; ++y) + for(std::size_t x = 0; x != 16; ++x) + Utility::copy( + Containers::StridedArrayView2D{pixels, {4, 8}}, + cache.image().pixels()[0].exceptPrefix({y, x}).every({16, 16})); /* Convert the file */ Containers::Pointer converter = _fontConverterManager.instantiate("MagnumFontConverter"); @@ -192,6 +303,7 @@ void MagnumFontConverterTest::exportFont() { Utility::Path::join(MAGNUMFONT_TEST_DIR, "font.tga"), DebugTools::CompareImageFile{_importerManager}); } +#endif void MagnumFontConverterTest::exportFontEmptyCache() { Containers::String confFilename = Utility::Path::join(MAGNUMFONTCONVERTER_TEST_WRITE_DIR, "font-empty-cache.conf"); @@ -208,12 +320,13 @@ void MagnumFontConverterTest::exportFontEmptyCache() { struct: AbstractGlyphCache { using AbstractGlyphCache::AbstractGlyphCache; - GlyphCacheFeatures doFeatures() const override { return GlyphCacheFeature::ImageDownload; } + GlyphCacheFeatures doFeatures() const override { return {}; } void doSetImage(const Vector2i&, const ImageView2D&) override {} - Image2D doImage() override { - return Image2D{PixelFormat::R8Unorm, {8, 4}, Containers::Array{ValueInit, 8*4}}; - } - } cache{{8, 4}}; + } cache{PixelFormat::R8Unorm, {8, 4}}; + + /* Associate the font with the cache. The case where it's not even that is + tested in exportFontNotFoundInCache() below. */ + cache.addFont(0, &font); /* Convert the file */ Containers::Pointer converter = _fontConverterManager.instantiate("MagnumFontConverter"); @@ -234,7 +347,104 @@ void MagnumFontConverterTest::exportFontEmptyCache() { DebugTools::CompareImageFile{_importerManager}); } -void MagnumFontConverterTest::exportFontNoGlyphCacheImageDownload() { +void MagnumFontConverterTest::exportFontImageProcessingGlyphCache() { + /* Like exportFont(), but the image is processed to a 16x smaller one. The + rest stays the same, i.e. the offsets and sizes are still relative to + the original 128x64 image. */ + + Containers::String confFilename = Utility::Path::join(MAGNUMFONTCONVERTER_TEST_WRITE_DIR, "font-processed.conf"); + Containers::String tgaFilename = Utility::Path::join(MAGNUMFONTCONVERTER_TEST_WRITE_DIR, "font-processed.tga"); + /* Remove previously created files */ + if(Utility::Path::exists(confFilename)) + CORRADE_VERIFY(Utility::Path::remove(confFilename)); + if(Utility::Path::exists(tgaFilename)) + CORRADE_VERIFY(Utility::Path::remove(tgaFilename)); + + MyFont font; + font.openFile({}, {}); + + struct: AbstractGlyphCache { + using AbstractGlyphCache::AbstractGlyphCache; + + GlyphCacheFeatures doFeatures() const override { return GlyphCacheFeature::ProcessedImageDownload; } + void doSetImage(const Vector2i&, const ImageView2D&) override {} + Image3D doProcessedImage() override { + return Image3D{PixelFormat::R8Unorm, {8, 4, 1}, Containers::Array{InPlaceInit, { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v' + }}}; + } + } cache{PixelFormat::R8Unorm, {128, 64}, {16, 8}}; + /* Override the not found glyph to be in bounds as well */ + cache.setInvalidGlyph({}, {{16, 8}, {16, 8}}); + UnsignedInt fontId = cache.addFont(25, &font); + cache.addGlyph(fontId, font.glyphId(U'W'), {25, 34}, {{16, 12}, {24, 56}}); + cache.addGlyph(fontId, font.glyphId(U'e'), {25, 12}, {{36, 8}, {112, 40}}); + /* ě has deliberately the same glyph data as e */ + cache.addGlyph(fontId, font.glyphId( + /* MSVC (but not clang-cl) doesn't support UTF-8 in char32_t literals + but it does it regular strings. Still a problem in MSVC 2022, what a + trash fire, can't you just give up on those codepage insanities + already, ffs?! */ + #if defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG) + U'\u011B' + #else + U'ě' + #endif + ), {25, 12}, {{36, 8}, {112, 40}}); + + /* Convert the file */ + Containers::Pointer converter = _fontConverterManager.instantiate("MagnumFontConverter"); + CORRADE_VERIFY(converter->exportFontToFile(font, cache, Utility::Path::join(MAGNUMFONTCONVERTER_TEST_WRITE_DIR, "font-processed"), "Waveě")); + + /* Verify font parameters */ + CORRADE_COMPARE_AS(confFilename, + Utility::Path::join(MAGNUMFONT_TEST_DIR, "font-processed.conf"), + TestSuite::Compare::File); + + if(!(_importerManager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_importerManager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found, not testing glyph cache contents"); + + /* Verify font image */ + CORRADE_COMPARE_WITH(tgaFilename, + Utility::Path::join(MAGNUMFONT_TEST_DIR, "font-processed.tga"), + DebugTools::CompareImageFile{_importerManager}); +} + +void MagnumFontConverterTest::exportFontImageProcessingGlyphCacheNoDownload() { + struct: AbstractFont { + /* Supports neither file nor data opening */ + FontFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + UnsignedInt doGlyphId(char32_t) override { return {}; } + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doLayout(const AbstractGlyphCache&, Float, Containers::StringView) override { + return nullptr; + } + } font; + + struct: AbstractGlyphCache { + using AbstractGlyphCache::AbstractGlyphCache; + + GlyphCacheFeatures doFeatures() const override { return GlyphCacheFeature::ImageProcessing; } + void doSetImage(const Vector2i&, const ImageView2D&) override {} + } cache{PixelFormat::R8Unorm, {100, 100}}; + + Containers::Pointer converter = _fontConverterManager.instantiate("MagnumFontConverter"); + + std::ostringstream out; + Error redirectError{&out}; + converter->exportFontToFile(font, cache, Utility::Path::join(MAGNUMFONTCONVERTER_TEST_WRITE_DIR, "font"), "Wave"); + CORRADE_COMPARE(out.str(), "Text::MagnumFontConverter::exportFontToData(): glyph cache has image processing but doesn't support image download\n"); +} + +void MagnumFontConverterTest::exportFontArrayCache() { struct: AbstractFont { /* Supports neither file nor data opening */ FontFeatures doFeatures() const override { return {}; } @@ -254,14 +464,91 @@ void MagnumFontConverterTest::exportFontNoGlyphCacheImageDownload() { GlyphCacheFeatures doFeatures() const override { return {}; } void doSetImage(const Vector2i&, const ImageView2D&) override {} - } cache{{100, 100}}; + } cache{PixelFormat::R8Unorm, {100, 100, 2}}; + + cache.addFont(15, &font); + + Containers::Pointer converter = _fontConverterManager.instantiate("MagnumFontConverter"); + + std::ostringstream out; + Error redirectError{&out}; + converter->exportFontToFile(font, cache, Utility::Path::join(MAGNUMFONTCONVERTER_TEST_WRITE_DIR, "font"), "Wave"); + CORRADE_COMPARE(out.str(), "Text::MagnumFontConverter::exportFontToData(): exporting array glyph caches is not supported\n"); +} + +void MagnumFontConverterTest::exportFontNotFoundInCache() { + struct: AbstractFont { + /* Supports neither file nor data opening */ + FontFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + UnsignedInt doGlyphId(char32_t) override { return {}; } + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doLayout(const AbstractGlyphCache&, Float, Containers::StringView) override { + return nullptr; + } + } font1, font2; + + struct: AbstractGlyphCache { + using AbstractGlyphCache::AbstractGlyphCache; + + GlyphCacheFeatures doFeatures() const override { return {}; } + void doSetImage(const Vector2i&, const ImageView2D&) override {} + } cache{PixelFormat::R8Unorm, {100, 100}}; + + cache.addFont(15, &font2); + cache.addFont(33); + + Containers::Pointer converter = _fontConverterManager.instantiate("MagnumFontConverter"); + + std::ostringstream out; + Error redirectError{&out}; + converter->exportFontToFile(font1, cache, Utility::Path::join(MAGNUMFONTCONVERTER_TEST_WRITE_DIR, "font"), "Wave"); + CORRADE_COMPARE(out.str(), "Text::MagnumFontConverter::exportFontToData(): font not found among 2 fonts in passed glyph cache\n"); +} + +void MagnumFontConverterTest::exportFontImageConversionFailed() { + struct: AbstractFont { + FontFeatures doFeatures() const override { return {}; } + void doClose() override { _opened = false; } + bool doIsOpened() const override { return _opened; } + Properties doOpenFile(Containers::StringView, Float) override { + _opened = true; + return {16.0f, 25.0f, -10.0f, 39.7333f, 3}; + } + + UnsignedInt doGlyphId(char32_t) override { return {}; } + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doLayout(const AbstractGlyphCache&, Float, Containers::StringView) override { + return nullptr; + } + + private: + bool _opened; + } font; + + struct: AbstractGlyphCache { + using AbstractGlyphCache::AbstractGlyphCache; + + GlyphCacheFeatures doFeatures() const override { return {}; } + void doSetImage(const Vector2i&, const ImageView2D&) override {} + } cache{PixelFormat::R32F, {100, 100}}; + + font.openFile({}, 0.0f); + + cache.addFont(15, &font); Containers::Pointer converter = _fontConverterManager.instantiate("MagnumFontConverter"); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!converter->exportFontToFile(font, cache, Utility::Path::join(MAGNUMFONTCONVERTER_TEST_WRITE_DIR, "font"), "Wave")); - CORRADE_COMPARE(out.str(), "Text::MagnumFontConverter::exportFontToData(): passed glyph cache doesn't support image download\n"); + converter->exportFontToFile(font, cache, Utility::Path::join(MAGNUMFONTCONVERTER_TEST_WRITE_DIR, "font"), "Wave"); + CORRADE_COMPARE(out.str(), + "Trade::TgaImageConverter::convertToData(): unsupported pixel format PixelFormat::R32F\n" + "Text::MagnumFontConverter::exportFontToData(): cannot create a TGA image\n"); } }}}}