From 4c9fc15962f960b54f3377cc424ce299d2f1bf8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 6 Oct 2023 17:13:44 +0200 Subject: [PATCH] MagnumFont{,Converter}: port to the new AbstractGlyphCache API. The internals are still rather ew, that's for another time, but the goal here was to make it compile and correctly handle the new variability in created and passed glyph cache instances. In particular: - The MagnumFont createGlyphCache() now uploads the texture image directly, skipping a copy through the CPU-side image which may not have a size matching the input image. That's kind of nasty (and too tied to OpenGL), but will be cleaned up once the GlyphCache APIs are fixed to allow this in a nicer way. - The MagnumFontConverter will now correctly select a font if the cache contains more than one, properly deal with glyph caches that either do or not do image processing, and will fail for array glyph caches as that's not supported by the current format. It also now has an early special-case handling for glyph caches with processed images, where the actual image may have different size (and possibly format), to match what MagnumFont expects. --- src/MagnumPlugins/MagnumFont/MagnumFont.cpp | 57 ++- .../MagnumFont/Test/CMakeLists.txt | 2 + .../MagnumFont/Test/MagnumFontGLTest.cpp | 121 ++++++- .../MagnumFont/Test/MagnumFontTest.cpp | 64 +++- .../MagnumFont/Test/font-processed.conf | 39 +++ .../MagnumFont/Test/font-processed.tga | Bin 0 -> 50 bytes src/MagnumPlugins/MagnumFont/Test/font.tga | Bin 50 -> 1042 bytes .../MagnumFontConverter.cpp | 60 +++- .../MagnumFontConverter/MagnumFontConverter.h | 8 +- .../MagnumFontConverter/Test/CMakeLists.txt | 2 + .../Test/MagnumFontConverterTest.cpp | 329 ++++++++++++++++-- 11 files changed, 620 insertions(+), 62 deletions(-) create mode 100644 src/MagnumPlugins/MagnumFont/Test/font-processed.conf create mode 100644 src/MagnumPlugins/MagnumFont/Test/font-processed.tga 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 0000000000000000000000000000000000000000..823f2d6670ab3c4151a6170f849319aaa6ee8355 GIT binary patch literal 50 ycmZQzU}k^;4h9wm4h925BV!X&Gjj{e#H8eu)U@=B%&hF3+`RmP!lL4m(lP*|Z3|)m literal 0 HcmV?d00001 diff --git a/src/MagnumPlugins/MagnumFont/Test/font.tga b/src/MagnumPlugins/MagnumFont/Test/font.tga index 823f2d6670ab3c4151a6170f849319aaa6ee8355..d449ccadd15391657db1207c1f51c987c0e2bd58 100644 GIT binary patch literal 1042 zcmd_dK^B2v7y!U;>Afyfir94(B!!5uc7wUHl~eq)n}{s_zR|=Y /* 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"); } }}}}