diff --git a/doc/changelog.dox b/doc/changelog.dox index 17b448e85..0ce3d5a83 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -1005,6 +1005,12 @@ See also: names and retrieving IDs for particular glyph names. - @ref Text::AbstractFont::fillGlyphCache() now returns a @cpp bool @ce to allow font plugin implementations to gracefully report failures +- @ref Text::AbstractFont::openFile() and + @relativeref{Text::AbstractFont,openData()} now allow picking a concrete + font ID from a font collection, along with new + @ref Text::AbstractFont::fileFontCount() and + @relativeref{Text::AbstractFont,dataFontCount()} APIs for querying font + count in a file (see [mosra/magnum#695](https://github.com/mosra/magnum/pull/695)) @subsubsection changelog-latest-changes-texturetools TextureTools library @@ -1658,6 +1664,10 @@ See also: deprecated in favor of constructors taking an explicit @ref PixelFormat. The internal texture format is now considered an implementation detail. + - @ref Text::AbstractFont::doOpenFile() and + @relativeref{Text::AbstractFont,doOpenData()} overloads taking just + file / data and size are deprecated in favor of functions taking an + explicit font index - The @cpp TextureTools::atlas() @ce utility is deprecated in favor of @ref TextureTools::AtlasLandfill, which has a vastly better packing efficiency, supports incremental packing and doesn't force the caller to @@ -3660,7 +3670,7 @@ Released 2019-10-24, tagged as - @cpp Text::AbstractFont::openSingleData() @ce and @cpp Text::AbstractFont::openData() @ce taking a list of files are deprecated in favor of - @ref Text::AbstractFont::openData(Containers::ArrayView, Float) + @cpp Text::AbstractFont::openData(Containers::ArrayView, Float) @ce and @ref Text::AbstractFont::setFileCallback() - @ref GL::Buffer::setData() and @ref GL::Buffer::setSubData() no longer has overloads taking @ref std::vector / @ref std::array, but instead relies on diff --git a/doc/credits.dox b/doc/credits.dox index 85aedd2b5..a88488617 100644 --- a/doc/credits.dox +++ b/doc/credits.dox @@ -103,6 +103,8 @@ Are the below lists missing your name or something's wrong? tick event implementation in @ref Platform::GlfwApplication - **Andrew ([\@sheerluck](https://github.com/sheerluck))** --- Gentoo package fixes +- **Andrew Snyder ([\@arsnyder16](https://github.com/arsnyder16))** --- + additions to the @ref Text library - **Andy Maloney** ([\@asmaloney](https://github.com/asmaloney)) --- CMake and GDB printer fixes, squashing documentation typos - **Andy Somogyi** ([\@andysomogyi](https://github.com/andysomogyi)) --- diff --git a/src/Magnum/Text/AbstractFont.cpp b/src/Magnum/Text/AbstractFont.cpp index e76567405..34629c2a3 100644 --- a/src/Magnum/Text/AbstractFont.cpp +++ b/src/Magnum/Text/AbstractFont.cpp @@ -4,6 +4,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026 Vladimír Vondruš + Copyright © 2026 Andrew Snyder Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -135,7 +136,24 @@ void AbstractFont::setFileCallback(Containers::Optional>(*)(const std::string&, InputFileCallbackPolicy, void*), void*) {} -bool AbstractFont::openData(Containers::ArrayView data, const Float size) { +Containers::Optional AbstractFont::dataFontCount(const Containers::ArrayView data) { + CORRADE_ASSERT(features() & FontFeature::OpenData, + "Text::AbstractFont::dataFontCount(): feature not supported", {}); + /* Similarly to openData() we accept empty data here (instead of checking + for them and failing so the check doesn't be done on the plugin side) + because for some file formats it could be valid (MagnumFont in + particular). */ + const Containers::Optional count = doDataFontCount(Containers::arrayCast(data)); + CORRADE_ASSERT(count != 0, + "Text::AbstractFont::dataFontCount(): implementation returned zero", {}); + return count; +} + +Containers::Optional AbstractFont::doDataFontCount(Containers::ArrayView) { + return 1; +} + +bool AbstractFont::openData(const Containers::ArrayView data, const Float size, const UnsignedInt fontId) { CORRADE_ASSERT(features() & FontFeature::OpenData, "Text::AbstractFont::openData(): feature not supported", false); @@ -143,7 +161,7 @@ bool AbstractFont::openData(Containers::ArrayView data, const Float the check doesn't be done on the plugin side) because for some file formats it could be valid (MagnumFont in particular). */ close(); - const Properties properties = doOpenData(Containers::arrayCast(data), size); + const Properties properties = doOpenData(Containers::arrayCast(data), size, fontId); /* If opening succeeded, save the returned values. If not, the values were set to their default values by close() already. */ @@ -159,18 +177,134 @@ bool AbstractFont::openData(Containers::ArrayView data, const Float return false; } +auto AbstractFont::doOpenData(const Containers::ArrayView data, const Float size, const UnsignedInt fontId) -> Properties { + #ifndef MAGNUM_BUILD_DEPRECATED + CORRADE_ASSERT_UNREACHABLE("Text::AbstractFont::openData(): feature advertised but not implemented", {}); + static_cast(data); + static_cast(size); + static_cast(fontId); + #else + /* If this function is not implemented, fall back to the deprecated + overload that doesn't take a font ID if the requested ID is 0, and fail + otherwise. */ + if(fontId != 0) { + Error() << "Text::AbstractFont::openData(): cannot open font at index" << fontId; + return {}; + } + + CORRADE_IGNORE_DEPRECATED_PUSH + return doOpenData(data, size); + CORRADE_IGNORE_DEPRECATED_POP + #endif +} + +#ifdef MAGNUM_BUILD_DEPRECATED auto AbstractFont::doOpenData(Containers::ArrayView, Float) -> Properties { CORRADE_ASSERT_UNREACHABLE("Text::AbstractFont::openData(): feature advertised but not implemented", {}); } +#endif + +Containers::Optional AbstractFont::fileFontCount(const Containers::StringView filename) { + /* The logic here is mirroring what's in openFile(), just with delegating + to different APIs. Comments are kept whole, just referring to different + functions, as it's easier to reason about that way. */ + + Containers::Optional count; + + /* If file loading callbacks are not set or the font implementation + supports handling them directly, call into the implementation */ + if(!_fileCallback || (doFeatures() & FontFeature::FileCallback)) { + count = doFileFontCount(filename); -bool AbstractFont::openFile(const Containers::StringView filename, const Float size) { + /* Otherwise, if loading from data is supported, use the callback and pass + the data through to dataFontCount(). Mark the file as ready to be closed + once opening is finished. */ + } else if(doFeatures() & FontFeature::OpenData) { + /* This needs to be duplicated here and in the doFileFontCount() + implementation in order to support both following cases: + - plugins that don't support FileCallback but have their own + doFileFontCount() implementation (callback needs to be used here, + because the base doFileFontCount() implementation might never get + called) + - plugins that support FileCallback but want to delegate the actual + file loading to the default implementation (callback used in the + base doFileFontCount() implementation, because this branch is + never taken in that case) */ + const Containers::Optional> data = _fileCallback(filename, InputFileCallbackPolicy::LoadTemporary, _fileCallbackUserData); + if(!data) { + Error() << "Text::AbstractFont::fileFontCount(): cannot open file" << filename; + return {}; + } + + count = doDataFontCount(*data); + _fileCallback(filename, InputFileCallbackPolicy::Close, _fileCallbackUserData); + + /* Shouldn't get here, the assert is fired already in setFileCallback() */ + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + + CORRADE_ASSERT(count != 0, + "Text::AbstractFont::fileFontCount(): implementation returned zero", {}); + return count; +} + +Containers::Optional AbstractFont::doFileFontCount(const Containers::StringView filename) { + /* The logic here is mirroring what's in doOpenFile(), just with delegating + to different APIs and having a non-asserting default implementation. + Comments are kept whole, just referring to different functions, as it's + easier to reason about that way. */ + + if(!(features() & FontFeature::OpenData)) + return 1; + + Containers::Optional count; + + /* If callbacks are set, use them. This is the same implementation as in + openFile(), see the comment there for details. */ + if(_fileCallback) { + const Containers::Optional> data = _fileCallback(filename, InputFileCallbackPolicy::LoadTemporary, _fileCallbackUserData); + if(!data) { + Error() << "Text::AbstractFont::fileFontCount(): cannot open file" << filename; + return {}; + } + + count = doDataFontCount(*data); + _fileCallback(filename, InputFileCallbackPolicy::Close, _fileCallbackUserData); + + /* Otherwise open the file directly */ + } else { + const Containers::Optional> data = Utility::Path::read(filename); + if(!data) { + Error() << "Text::AbstractFont::fileFontCount(): cannot open file" << filename; + return {}; + } + + count = doDataFontCount(*data); + } + + return count; +} + +bool AbstractFont::openFile(const Containers::StringView filename, const Float size, const UnsignedInt fontId) { close(); Properties properties; /* If file loading callbacks are not set or the font implementation supports handling them directly, call into the implementation */ if(!_fileCallback || (doFeatures() & FontFeature::FileCallback)) { - properties = doOpenFile(filename, size); + #ifdef MAGNUM_BUILD_DEPRECATED + /* Call the deprecated doOpenFile() if fontId is 0, to make plugins + that didn't get adapted to the new APIs still work. If a plugin + doesn't implement the deprecated doOpenFile(), it delegates back to + the new doOpenFile() implementation. */ + if(fontId == 0) { + CORRADE_IGNORE_DEPRECATED_PUSH + properties = doOpenFile(filename, size); + CORRADE_IGNORE_DEPRECATED_POP + } else + #endif + { + properties = doOpenFile(filename, size, fontId); + } /* Otherwise, if loading from data is supported, use the callback and pass the data through to openData(). Mark the file as ready to be closed once @@ -192,7 +326,7 @@ bool AbstractFont::openFile(const Containers::StringView filename, const Float s return isOpened(); } - properties = doOpenData(*data, size); + properties = doOpenData(*data, size, fontId); _fileCallback(filename, InputFileCallbackPolicy::Close, _fileCallbackUserData); /* Shouldn't get here, the assert is fired already in setFileCallback() */ @@ -212,7 +346,20 @@ bool AbstractFont::openFile(const Containers::StringView filename, const Float s return false; } -auto AbstractFont::doOpenFile(const Containers::StringView filename, const Float size) -> Properties { +auto AbstractFont::doOpenFile(const Containers::StringView filename, const Float size, const UnsignedInt fontId) -> Properties { + #ifdef MAGNUM_BUILD_DEPRECATED + /* If this function is not implemented and opening data isn't supported + either, assume the plugin implements only the deprecated doOpenFile() + and fail gracefully for a non-zero font ID instead of asserting below. + The reasoning is that plugin implementations should *never* assert for a + font ID out of bounds, no matter whether they were updated to the new + API or not. */ + if(!(features() & FontFeature::OpenData) && fontId != 0) { + Error() << "Text::AbstractFont::openFile(): cannot open font at index" << fontId; + return {}; + } + #endif + CORRADE_ASSERT(features() & FontFeature::OpenData, "Text::AbstractFont::openFile(): not implemented", {}); Properties properties; @@ -226,7 +373,7 @@ auto AbstractFont::doOpenFile(const Containers::StringView filename, const Float return {}; } - properties = doOpenData(*data, size); + properties = doOpenData(*data, size, fontId); _fileCallback(filename, InputFileCallbackPolicy::Close, _fileCallbackUserData); /* Otherwise open the file directly */ @@ -237,12 +384,26 @@ auto AbstractFont::doOpenFile(const Containers::StringView filename, const Float return {}; } - properties = doOpenData(*data, size); + properties = doOpenData(*data, size, fontId); } return properties; } +#ifdef MAGNUM_BUILD_DEPRECATED +auto AbstractFont::doOpenFile(const Containers::StringView filename, const Float size) -> Properties { + /* This function gets called from openFile() if fontId is 0, to call a + potential implementation in old plugins that didn't get adapted to the + new APIs yet. If a plugin doesn't implement doOpenFile() at all, this + function delegates to the default implementation that then delegates to + doOpenData() (which then again delegates to deprecated doOpenData(). If + a plugin implements the new doOpenFile(), that implementation gets + called from here for fontId 0, and from openFile() for all other font + IDs. */ + return doOpenFile(filename, size, 0); +} +#endif + void AbstractFont::close() { if(!isOpened()) return; diff --git a/src/Magnum/Text/AbstractFont.h b/src/Magnum/Text/AbstractFont.h index 4cd45f8c1..8e369bd2f 100644 --- a/src/Magnum/Text/AbstractFont.h +++ b/src/Magnum/Text/AbstractFont.h @@ -6,6 +6,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026 Vladimír Vondruš + Copyright © 2026 Andrew Snyder Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -56,7 +57,10 @@ namespace Magnum { namespace Text { @see @ref FontFeatures, @ref AbstractFont::features() */ enum class FontFeature: UnsignedByte { - /** Opening fonts from raw data using @ref AbstractFont::openData() */ + /** + * Opening fonts from raw data using @ref AbstractFont::openData() and + * @ref AbstractFont::dataFontCount(). + */ OpenData = 1 << 0, /** @@ -393,6 +397,57 @@ class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin { template void setFileCallback(Callback callback, T& userData); #endif + /** + * @brief Count of fonts in given data + * @param data Font data + * @m_since_latest + * + * Tries to open given raw data and returns count of fonts in the file. + * Available only if @ref FontFeature::OpenData is supported. A font + * index from this range can be passed to @ref openData() to select a + * particular font inside for example a TrueType Collection (`*.ttc`) + * file. On failure prints a message to @relativeref{Magnum,Error} and + * returns @relativeref{Corrade,Containers::NullOpt}, otherwise it's + * guaranteed to return at least @cpp 1 @ce. Calling this function + * doesn't affect the currently opened font in any way. + * + * Note that if a particular font plugin implementation doesn't support + * font index selection, the function may return @cpp 1 @ce without + * even checking for @p data being valid. In other words, if this + * function succeeds, it doesn't imply that @ref openData() will + * succeed as well. This function exists mainly for font introspection + * purposes. + * @see @ref fileFontCount() + */ + Containers::Optional dataFontCount(Containers::ArrayView data); + + /** + * @brief Count of fonts in given file + * @param filename Font file + * @m_since_latest + * + * Tries to open given font file and returns count of fonts inside. A + * font index from this range can be passed to @ref openFile() to + * select a particular font for example inside a TrueType Collection + * (`*.ttc`) file. On failure prints a message to + * @relativeref{Magnum,Error} and returns + * @relativeref{Corrade,Containers::NullOpt}, otherwise it's guaranteed + * to return at least @cpp 1 @ce. Calling this function doesn't affect + * the currently opened font in any way. If file loading callbacks are + * set via @ref setFileCallback() and @ref FontFeature::OpenData is + * supported, this function uses the callback to load the file and + * passes the memory view to @ref dataFontCount() instead. See + * @ref setFileCallback() for more information. + * + * Note that if a particular font plugin implementation doesn't support + * font index selection, the function may return @cpp 1 @ce without + * even checking for @p filename being valid. In other words, if this + * function succeeds, it doesn't imply that @ref openFile() will + * succeed as well. This function exists mainly for font introspection + * purposes. + */ + Containers::Optional fileFontCount(Containers::StringView filename); + /** @brief Whether any file is opened */ bool isOpened() const { return doIsOpened(); } @@ -400,19 +455,29 @@ class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin { * @brief Open raw data * @param data Font data * @param size Size to open the font in, in points + * @param fontId Font index * * Closes previous file, if it was opened, and tries to open given * raw data. Available only if @ref FontFeature::OpenData is supported. * On failure prints a message to @relativeref{Magnum,Error} and * returns @cpp false @ce. + * + * The function will fail if @p fontId is larger than the count of + * fonts in given file. The total font count can be queried using + * @ref dataFontCount(), but as font rasterization libraries commonly + * require picking a concrete font index in order to open a file at + * all and don't allow changing it afterwards, it's faster to directly + * attempt to open a concrete index without checking it against + * @ref dataFontCount() first, and handle a potential failure. * @see @ref features(), @ref openFile() */ - bool openData(Containers::ArrayView data, Float size); + bool openData(Containers::ArrayView data, Float size, UnsignedInt fontId = 0); /** * @brief Open a file * @param filename Font file * @param size Size to open the font in, in points + * @param fontId Font index * * Closes previous file, if it was opened, and tries to open given * file. On failure prints a message to @relativeref{Magnum,Error} and @@ -421,8 +486,16 @@ class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin { * this function uses the callback to load the file and passes the * memory view to @ref openData() instead. See @ref setFileCallback() * for more information. + * + * The function will fail if @p fontId is larger than the count of + * fonts in given file. The total font count can be queried using + * @ref fileFontCount(), but as font rasterization libraries commonly + * require picking a concrete font index in order to open a file at + * all and don't allow changing it afterwards, it's faster to directly + * attempt to open a concrete index without checking it against + * @ref dataFontCount() first, and handle a potential failure. */ - bool openFile(Containers::StringView filename, Float size); + bool openFile(Containers::StringView filename, Float size, UnsignedInt fontId = 0); /** * @brief Close currently opened file @@ -694,8 +767,25 @@ class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin { }; protected: + /** + * @brief Implementation for @ref fileFontCount() + * @m_since_latest + * + * If @ref FontFeature::OpenData is supported, default implementation + * opens the file and calls @ref doDataFontCount() with its contents, + * propagating its return value. It is allowed to call this function + * from your @ref doFileFontCount() implementation --- in particular, + * this implementation will also correctly handle callbacks set through + * @ref setFileCallback(). + * + * The implementation is expected to return either + * @relativeref{Corrade,Containers::NullOpt} or a non-zero value. + */ + virtual Containers::Optional doFileFontCount(Containers::StringView filename); + /** * @brief Implementation for @ref openFile() + * @m_since_latest * * If @ref doIsOpened() returns @cpp true @ce after calling this * function, it's assumed that opening was successful and the @@ -713,7 +803,23 @@ class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin { * supported --- instead, file is loaded though the callback and data * passed through to @ref doOpenData(). */ + virtual Properties doOpenFile(Containers::StringView filename, Float size, UnsignedInt fontId); + + #ifdef MAGNUM_BUILD_DEPRECATED + /** + * @brief Implementation for @ref openFile() + * @m_deprecated_since_latest Implement @ref doOpenFile(Containers::StringView, Float, UnsignedInt) + * instead. + */ + /* MSVC warns when overriding such methods and there's no way to + suppress that warning, making the RT build (which treats deprecation + warnings as errors) fail and other builds extremely noisy. So + disabling those on MSVC. */ + #if !(defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG)) + CORRADE_DEPRECATED("implement doOpenFile(Containers::StringView, Float, UnsignedInt) instead") + #endif virtual Properties doOpenFile(Containers::StringView filename, Float size); + #endif private: /** @brief Implementation for @ref features() */ @@ -733,8 +839,19 @@ class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin { /** @brief Implementation for @ref isOpened() */ virtual bool doIsOpened() const = 0; + /** + * @brief Implementation for @ref dataFontCount() + * @m_since_latest + * + * Default implementation returns @cpp 1 @ce. The implementation is + * expected to return either @relativeref{Corrade,Containers::NullOpt} + * or a non-zero value. + */ + virtual Containers::Optional doDataFontCount(Containers::ArrayView data); + /** * @brief Implementation for @ref openData() + * @m_since_latest * * If @ref doIsOpened() returns @cpp true @ce after calling this * function, it's assumed that opening was successful and the @@ -742,7 +859,23 @@ class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin { * @ref doIsOpened() returns @cpp false @ce, the returned values are * ignored. */ + virtual Properties doOpenData(Containers::ArrayView data, Float size, UnsignedInt fontId); + + #ifdef MAGNUM_BUILD_DEPRECATED + /** + * @brief Implementation for @ref openData() + * @m_deprecated_since_latest Implement @ref doOpenData(Containers::ArrayView, Float, UnsignedInt) + * instead. + */ + /* MSVC warns when overriding such methods and there's no way to + suppress that warning, making the RT build (which treats deprecation + warnings as errors) fail and other builds extremely noisy. So + disabling those on MSVC. */ + #if !(defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG)) + CORRADE_DEPRECATED("implement doOpenData(Containers::ArrayView, Float, UnsignedInt) instead") + #endif virtual Properties doOpenData(Containers::ArrayView data, Float size); + #endif /** @brief Implementation for @ref close() */ virtual void doClose() = 0; @@ -917,7 +1050,7 @@ updated interface string. */ /* Silly indentation to make the string appear in pluginInterface() docs */ #define MAGNUM_TEXT_ABSTRACTFONT_PLUGIN_INTERFACE /* [interface] */ \ -"cz.mosra.magnum.Text.AbstractFont/0.3.7" +"cz.mosra.magnum.Text.AbstractFont/0.4.0" /* [interface] */ #ifndef DOXYGEN_GENERATING_OUTPUT diff --git a/src/Magnum/Text/Test/AbstractFontTest.cpp b/src/Magnum/Text/Test/AbstractFontTest.cpp index 73b2c90c3..fb474d7d5 100644 --- a/src/Magnum/Text/Test/AbstractFontTest.cpp +++ b/src/Magnum/Text/Test/AbstractFontTest.cpp @@ -4,6 +4,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026 Vladimír Vondruš + Copyright © 2026 Andrew Snyder Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -62,13 +63,48 @@ struct AbstractFontTest: TestSuite::Tester { void construct(); + void dataFontCount(); + void dataFontCountFailed(); + void dataFontCountNotImplemented(); + void fileFontCount(); + void fileFontCountFailed(); + void fileFontCountNotImplemented(); + void fileFontCountAsData(); + void fileFontCountAsDataNotFound(); + void fileFontCountAsDataFailed(); + void fileFontCountAsDataNotImplemented(); + + void fontCountInvalid(); + void dataFontCountNotSupported(); + void openData(); + #ifdef MAGNUM_BUILD_DEPRECATED + void openDataDeprecated(); + void openDataDeprecatedNonZeroFontId(); + #endif void openDataFailed(); + #ifdef MAGNUM_BUILD_DEPRECATED + void openDataFailedDeprecated(); + #endif void openFile(); + #ifdef MAGNUM_BUILD_DEPRECATED + void openFileDeprecated(); + void openFileDeprecatedNonZeroFontId(); + #endif void openFileFailed(); + #ifdef MAGNUM_BUILD_DEPRECATED + void openFileFailedDeprecated(); + #endif void openFileAsData(); void openFileAsDataNotFound(); + #ifdef MAGNUM_BUILD_DEPRECATED + void openFileAsDataDeprecated(); + void openFileAsDataDeprecatedNonZeroFontId(); + #endif void openFileAsDataFailed(); + #ifdef MAGNUM_BUILD_DEPRECATED + void openFileAsDataFailedDeprecated(); + #endif void openFileNotImplemented(); void openDataNotSupported(); @@ -82,13 +118,35 @@ struct AbstractFontTest: TestSuite::Tester { void setFileCallbackNotImplemented(); void setFileCallbackNotSupported(); void setFileCallbackOpenFileDirectly(); + #ifdef MAGNUM_BUILD_DEPRECATED + void setFileCallbackOpenFileDirectlyDeprecated(); + void setFileCallbackOpenFileDirectlyDeprecatedNonZeroFontId(); + #endif void setFileCallbackOpenFileDirectlyFailed(); + #ifdef MAGNUM_BUILD_DEPRECATED + void setFileCallbackOpenFileDirectlyFailedDeprecated(); + #endif void setFileCallbackOpenFileThroughBaseImplementation(); + #ifdef MAGNUM_BUILD_DEPRECATED + void setFileCallbackOpenFileThroughBaseImplementationDeprecated(); + /* No non-zero font ID variant here, as the deprecated doOpenFile() that'd + delegate to the base implementation doesn't get called at all */ + #endif void setFileCallbackOpenFileThroughBaseImplementationNotFound(); void setFileCallbackOpenFileThroughBaseImplementationFailed(); + #ifdef MAGNUM_BUILD_DEPRECATED + void setFileCallbackOpenFileThroughBaseImplementationFailedDeprecated(); + #endif void setFileCallbackOpenFileAsData(); + #ifdef MAGNUM_BUILD_DEPRECATED + void setFileCallbackOpenFileAsDataDeprecated(); + void setFileCallbackOpenFileAsDataDeprecatedNonZeroFontId(); + #endif void setFileCallbackOpenFileAsDataNotFound(); void setFileCallbackOpenFileAsDataFailed(); + #ifdef MAGNUM_BUILD_DEPRECATED + void setFileCallbackOpenFileAsDataFailedDeprecated(); + #endif void properties(); void propertiesNoFont(); @@ -135,43 +193,158 @@ struct AbstractFontTest: TestSuite::Tester { #endif }; +/** @todo remove this once the deprecated APIs are gone */ +const struct { + const char* name; + UnsignedInt fontId; +} OpenData[]{ + {"", 0}, + {"non-zero font ID", 1} +}; + +const struct { + const char* name; + bool fileOpened; +} FontCountData[]{ + {"", false}, + {"with a font opened", true} +}; + AbstractFontTest::AbstractFontTest() { addTests({&AbstractFontTest::debugFeature, &AbstractFontTest::debugFeaturePacked, &AbstractFontTest::debugFeatures, &AbstractFontTest::debugFeaturesPacked, - &AbstractFontTest::construct, + &AbstractFontTest::construct}); + + addInstancedTests({&AbstractFontTest::dataFontCount, + &AbstractFontTest::dataFontCountFailed}, + Containers::arraySize(FontCountData)); + + addTests({&AbstractFontTest::dataFontCountNotImplemented}); - &AbstractFontTest::openData, - &AbstractFontTest::openDataFailed, - &AbstractFontTest::openFile, - &AbstractFontTest::openFileFailed, - &AbstractFontTest::openFileAsData, - &AbstractFontTest::openFileAsDataNotFound, - &AbstractFontTest::openFileAsDataFailed, + addInstancedTests({&AbstractFontTest::fileFontCount, + &AbstractFontTest::fileFontCountFailed}, + Containers::arraySize(FontCountData)); - &AbstractFontTest::openFileNotImplemented, - &AbstractFontTest::openDataNotSupported, - &AbstractFontTest::openDataNotImplemented, + addTests({&AbstractFontTest::fileFontCountNotImplemented}); - &AbstractFontTest::setFileCallback, + addInstancedTests({&AbstractFontTest::fileFontCountAsData, + &AbstractFontTest::fileFontCountAsDataNotFound, + &AbstractFontTest::fileFontCountAsDataFailed}, + Containers::arraySize(FontCountData)); + + addTests({&AbstractFontTest::fileFontCountAsDataNotImplemented, + &AbstractFontTest::fontCountInvalid, + &AbstractFontTest::dataFontCountNotSupported}); + + addInstancedTests({&AbstractFontTest::openData}, + Containers::arraySize(OpenData)); + + #ifdef MAGNUM_BUILD_DEPRECATED + addTests({&AbstractFontTest::openDataDeprecated, + &AbstractFontTest::openDataDeprecatedNonZeroFontId}); + #endif + + addInstancedTests({&AbstractFontTest::openDataFailed}, + Containers::arraySize(OpenData)); + + #ifdef MAGNUM_BUILD_DEPRECATED + addTests({&AbstractFontTest::openDataFailedDeprecated}); + #endif + + addInstancedTests({&AbstractFontTest::openFile}, + Containers::arraySize(OpenData)); + + #ifdef MAGNUM_BUILD_DEPRECATED + addTests({&AbstractFontTest::openFileDeprecated, + &AbstractFontTest::openFileDeprecatedNonZeroFontId}); + #endif + + addInstancedTests({&AbstractFontTest::openFileFailed}, + Containers::arraySize(OpenData)); + + #ifdef MAGNUM_BUILD_DEPRECATED + addTests({&AbstractFontTest::openFileFailedDeprecated}); + #endif + + addInstancedTests({&AbstractFontTest::openFileAsData, + &AbstractFontTest::openFileAsDataNotFound}, + Containers::arraySize(OpenData)); + + #ifdef MAGNUM_BUILD_DEPRECATED + addTests({&AbstractFontTest::openFileAsDataDeprecated, + &AbstractFontTest::openFileAsDataDeprecatedNonZeroFontId}); + #endif + + addInstancedTests({&AbstractFontTest::openFileAsDataFailed}, + Containers::arraySize(OpenData)); + + #ifdef MAGNUM_BUILD_DEPRECATED + addTests({&AbstractFontTest::openFileAsDataFailedDeprecated}); + #endif + + addInstancedTests({&AbstractFontTest::openFileNotImplemented, + &AbstractFontTest::openDataNotSupported, + &AbstractFontTest::openDataNotImplemented}, + Containers::arraySize(OpenData)); + + addTests({&AbstractFontTest::setFileCallback, &AbstractFontTest::setFileCallbackTemplate, &AbstractFontTest::setFileCallbackTemplateNull, &AbstractFontTest::setFileCallbackTemplateConst, &AbstractFontTest::setFileCallbackFileOpened, &AbstractFontTest::setFileCallbackNotImplemented, - &AbstractFontTest::setFileCallbackNotSupported, - &AbstractFontTest::setFileCallbackOpenFileDirectly, - &AbstractFontTest::setFileCallbackOpenFileDirectlyFailed, - &AbstractFontTest::setFileCallbackOpenFileThroughBaseImplementation, - &AbstractFontTest::setFileCallbackOpenFileThroughBaseImplementationNotFound, - &AbstractFontTest::setFileCallbackOpenFileThroughBaseImplementationFailed, - &AbstractFontTest::setFileCallbackOpenFileAsData, - &AbstractFontTest::setFileCallbackOpenFileAsDataNotFound, - &AbstractFontTest::setFileCallbackOpenFileAsDataFailed, - - &AbstractFontTest::properties, + &AbstractFontTest::setFileCallbackNotSupported}); + + addInstancedTests({&AbstractFontTest::setFileCallbackOpenFileDirectly}, + Containers::arraySize(OpenData)); + + #ifdef MAGNUM_BUILD_DEPRECATED + addTests({&AbstractFontTest::setFileCallbackOpenFileDirectlyDeprecated, + &AbstractFontTest::setFileCallbackOpenFileDirectlyDeprecatedNonZeroFontId}); + #endif + + addInstancedTests({&AbstractFontTest::setFileCallbackOpenFileDirectlyFailed}, + Containers::arraySize(OpenData)); + + #ifdef MAGNUM_BUILD_DEPRECATED + addTests({&AbstractFontTest::setFileCallbackOpenFileDirectlyFailedDeprecated}); + #endif + + addInstancedTests({&AbstractFontTest::setFileCallbackOpenFileThroughBaseImplementation}, + Containers::arraySize(OpenData)); + + #ifdef MAGNUM_BUILD_DEPRECATED + addTests({&AbstractFontTest::setFileCallbackOpenFileThroughBaseImplementationDeprecated}); + #endif + + addInstancedTests({&AbstractFontTest::setFileCallbackOpenFileThroughBaseImplementationNotFound, + &AbstractFontTest::setFileCallbackOpenFileThroughBaseImplementationFailed}, + Containers::arraySize(OpenData)); + + #ifdef MAGNUM_BUILD_DEPRECATED + addTests({&AbstractFontTest::setFileCallbackOpenFileThroughBaseImplementationFailedDeprecated}); + #endif + + addInstancedTests({&AbstractFontTest::setFileCallbackOpenFileAsData}, + Containers::arraySize(OpenData)); + + #ifdef MAGNUM_BUILD_DEPRECATED + addTests({&AbstractFontTest::setFileCallbackOpenFileAsDataDeprecated, + &AbstractFontTest::setFileCallbackOpenFileAsDataDeprecatedNonZeroFontId}); + #endif + + addInstancedTests({&AbstractFontTest::setFileCallbackOpenFileAsDataNotFound, + &AbstractFontTest::setFileCallbackOpenFileAsDataFailed}, + Containers::arraySize(OpenData)); + + #ifdef MAGNUM_BUILD_DEPRECATED + addTests({&AbstractFontTest::setFileCallbackOpenFileAsDataFailedDeprecated}); + #endif + + addTests({&AbstractFontTest::properties, &AbstractFontTest::propertiesNoFont, &AbstractFontTest::glyphId, @@ -269,21 +442,27 @@ void AbstractFontTest::construct() { CORRADE_VERIFY(!font.isOpened()); } -void AbstractFontTest::openData() { +void AbstractFontTest::dataFontCount() { + auto&& data = FontCountData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + struct: AbstractFont { FontFeatures doFeatures() const override { return FontFeature::OpenData; } bool doIsOpened() const override { return _opened; } void doClose() override { - CORRADE_VERIFY(_opened); - _opened = false; + CORRADE_FAIL("This should not get called."); } - Properties doOpenData(Containers::ArrayView data, Float size) override { + Containers::Optional doDataFontCount(Containers::ArrayView data) override { CORRADE_COMPARE_AS(data, - Containers::arrayView({'\xa5'}), + Containers::arrayView({'\xac'}), TestSuite::Compare::Container); + return 37; + } + + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { _opened = true; - return {size, 1.0f, 2.0f, 3.0f, 15}; + return {}; } void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} @@ -295,61 +474,103 @@ void AbstractFontTest::openData() { bool _opened = false; } font; - CORRADE_VERIFY(!font.isOpened()); - const char a5[]{'\xa5'}; - CORRADE_VERIFY(font.openData(a5, 13.0f)); - CORRADE_VERIFY(font.isOpened()); - CORRADE_COMPARE(font.size(), 13.0f); - CORRADE_COMPARE(font.ascent(), 1.0f); - CORRADE_COMPARE(font.descent(), 2.0f); - CORRADE_COMPARE(font.lineHeight(), 3.0f); - CORRADE_COMPARE(font.glyphCount(), 15); + /* Verify that the API doesn't affect the currently opened font in any way + -- it stays open if it was, and stays closed if wasn't */ + if(data.fileOpened) + CORRADE_VERIFY(font.openData(nullptr, 1.0f)); + CORRADE_COMPARE(font.isOpened(), data.fileOpened); - font.close(); - CORRADE_VERIFY(!font.isOpened()); + const char fontData[]{'\xac'}; + CORRADE_COMPARE(font.dataFontCount(fontData), 37); + + CORRADE_COMPARE(font.isOpened(), data.fileOpened); } -void AbstractFontTest::openDataFailed() { +void AbstractFontTest::dataFontCountFailed() { + auto&& data = FontCountData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + struct: AbstractFont { FontFeatures doFeatures() const override { return FontFeature::OpenData; } - bool doIsOpened() const override { return false; } - void doClose() override {} + bool doIsOpened() const override { return _opened; } + void doClose() override { + CORRADE_FAIL("This should not get called."); + } - Properties doOpenData(Containers::ArrayView, Float) override { + Containers::Optional doDataFontCount(Containers::ArrayView) override { called = true; return {}; } + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { + _opened = true; + return {}; + } + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} Vector2 doGlyphSize(UnsignedInt) override { return {}; } Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } Containers::Pointer doCreateShaper() override { return {}; } bool called = false; + + private: + bool _opened = false; } font; + /* Verify that the API doesn't affect the currently opened font in any way + -- it stays open if it was, and stays closed if wasn't */ + if(data.fileOpened) + CORRADE_VERIFY(font.openData(nullptr, 1.0f)); + CORRADE_COMPARE(font.isOpened(), data.fileOpened); + /* The implementation is expected to print an error message on its own */ - Containers::String out; - Error redirectError{&out}; - CORRADE_VERIFY(!font.openData(nullptr, 1.0f)); - CORRADE_VERIFY(!font.isOpened()); - CORRADE_VERIFY(font.called); - CORRADE_COMPARE(out, ""); + { + Containers::String out; + Error redirectError{&out}; + CORRADE_VERIFY(!font.dataFontCount(nullptr)); + CORRADE_VERIFY(font.called); + CORRADE_COMPARE(out, ""); + } + + CORRADE_COMPARE(font.isOpened(), data.fileOpened); } -void AbstractFontTest::openFile() { +void AbstractFontTest::dataFontCountNotImplemented() { struct: AbstractFont { - FontFeatures doFeatures() const override { return {}; } + FontFeatures doFeatures() const override { return FontFeature::OpenData; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + } font; + + /* Should call the default doDataFontCount() which returns 1 */ + CORRADE_COMPARE(font.dataFontCount(nullptr), 1); +} + +void AbstractFontTest::fileFontCount() { + auto&& data = FontCountData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct: AbstractFont { + FontFeatures doFeatures() const override { return FontFeature::OpenData; } bool doIsOpened() const override { return _opened; } void doClose() override { - CORRADE_VERIFY(_opened); - _opened = false; + CORRADE_FAIL("This should not get called."); } - Properties doOpenFile(Containers::StringView filename, Float size) override { - CORRADE_COMPARE(filename, "hello.ttf"); + Containers::Optional doFileFontCount(Containers::StringView filename) override { + CORRADE_COMPARE(filename, "thingy.ttc"); + return 37; + } + + Properties doOpenFile(Containers::StringView, Float, UnsignedInt) override { _opened = true; - return {size, 1.0f, 2.0f, 3.0f, 15}; + return {}; } void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} @@ -361,62 +582,105 @@ void AbstractFontTest::openFile() { bool _opened = false; } font; - CORRADE_VERIFY(!font.isOpened()); - CORRADE_VERIFY(font.openFile("hello.ttf", 13.0f)); - CORRADE_VERIFY(font.isOpened()); - CORRADE_COMPARE(font.size(), 13.0f); - CORRADE_COMPARE(font.ascent(), 1.0f); - CORRADE_COMPARE(font.descent(), 2.0f); - CORRADE_COMPARE(font.lineHeight(), 3.0f); - CORRADE_COMPARE(font.glyphCount(), 15); + /* Verify that the API doesn't affect the currently opened font in any way + -- it stays open if it was, and stays closed if wasn't */ + if(data.fileOpened) + CORRADE_VERIFY(font.openFile(nullptr, 1.0f)); + CORRADE_COMPARE(font.isOpened(), data.fileOpened); - font.close(); - CORRADE_VERIFY(!font.isOpened()); + CORRADE_COMPARE(font.fileFontCount("thingy.ttc"), 37); + + CORRADE_COMPARE(font.isOpened(), data.fileOpened); } -void AbstractFontTest::openFileFailed() { +void AbstractFontTest::fileFontCountFailed() { + auto&& data = FontCountData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + struct: AbstractFont { FontFeatures doFeatures() const override { return FontFeature::OpenData; } - bool doIsOpened() const override { return false; } - void doClose() override {} + bool doIsOpened() const override { return _opened; } + void doClose() override { + CORRADE_FAIL("This should not get called."); + } - Properties doOpenFile(Containers::StringView, Float) override { + Containers::Optional doFileFontCount(Containers::StringView) override { called = true; return {}; } + Properties doOpenFile(Containers::StringView, Float, UnsignedInt) override { + _opened = true; + return {}; + } + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} Vector2 doGlyphSize(UnsignedInt) override { return {}; } Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } Containers::Pointer doCreateShaper() override { return {}; } - bool called = false; + bool called = true; + + private: + bool _opened = false; } font; + /* Verify that the API doesn't affect the currently opened font in any way + -- it stays open if it was, and stays closed if wasn't */ + if(data.fileOpened) + CORRADE_VERIFY(font.openFile(nullptr, 1.0f)); + CORRADE_COMPARE(font.isOpened(), data.fileOpened); + /* The implementation is expected to print an error message on its own */ - Containers::String out; - Error redirectError{&out}; - CORRADE_VERIFY(!font.openFile("hello.ttf", 1.0f)); - CORRADE_VERIFY(!font.isOpened()); - CORRADE_VERIFY(font.called); - CORRADE_COMPARE(out, ""); + { + Containers::String out; + Error redirectError{&out}; + CORRADE_VERIFY(!font.fileFontCount("")); + CORRADE_VERIFY(font.called); + CORRADE_COMPARE(out, ""); + } + + CORRADE_COMPARE(font.isOpened(), data.fileOpened); } -void AbstractFontTest::openFileAsData() { +void AbstractFontTest::fileFontCountNotImplemented() { + struct: AbstractFont { + FontFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + } font; + + /* Should call the default doFileFontCount() which returns 1 if OpenData + is not supported */ + CORRADE_COMPARE(font.fileFontCount(nullptr), 1); +} + +void AbstractFontTest::fileFontCountAsData() { + auto&& data = FontCountData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + struct: AbstractFont { FontFeatures doFeatures() const override { return FontFeature::OpenData; } bool doIsOpened() const override { return _opened; } void doClose() override { - CORRADE_VERIFY(_opened); - _opened = false; + CORRADE_FAIL("This should not get called."); } - Properties doOpenData(Containers::ArrayView data, Float size) override { + Containers::Optional doDataFontCount(Containers::ArrayView data) override { CORRADE_COMPARE_AS(data, Containers::arrayView({'\xa5'}), TestSuite::Compare::Container); + return 37; + } + + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { _opened = true; - return {size, 1.0f, 2.0f, 3.0f, 15}; + return {}; } void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} @@ -428,28 +692,35 @@ void AbstractFontTest::openFileAsData() { bool _opened = false; } font; - /* doOpenFile() should call doOpenData() */ - CORRADE_VERIFY(!font.isOpened()); - CORRADE_VERIFY(font.openFile(Utility::Path::join(TEXT_TEST_DIR, "data.bin"), 13.0f)); - CORRADE_VERIFY(font.isOpened()); - CORRADE_COMPARE(font.size(), 13.0f); - CORRADE_COMPARE(font.ascent(), 1.0f); - CORRADE_COMPARE(font.descent(), 2.0f); - CORRADE_COMPARE(font.lineHeight(), 3.0f); - CORRADE_COMPARE(font.glyphCount(), 15); + /* Verify that the API doesn't affect the currently opened font in any way + -- it stays open if it was, and stays closed if wasn't */ + if(data.fileOpened) + CORRADE_VERIFY(font.openFile(Utility::Path::join(TEXT_TEST_DIR, "data.bin"), 1.0f)); + CORRADE_COMPARE(font.isOpened(), data.fileOpened); - font.close(); - CORRADE_VERIFY(!font.isOpened()); + CORRADE_COMPARE(font.fileFontCount(Utility::Path::join(TEXT_TEST_DIR, "data.bin")), 37); + + CORRADE_COMPARE(font.isOpened(), data.fileOpened); } -void AbstractFontTest::openFileAsDataNotFound() { +void AbstractFontTest::fileFontCountAsDataNotFound() { + auto&& data = FontCountData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + struct: AbstractFont { FontFeatures doFeatures() const override { return FontFeature::OpenData; } - bool doIsOpened() const override { return false; } - void doClose() override {} + bool doIsOpened() const override { return _opened; } + void doClose() override { + CORRADE_FAIL("This should not get called."); + } - Properties doOpenData(Containers::ArrayView, Float) override { - CORRADE_FAIL("This should not be called"); + Containers::Optional doDataFontCount(Containers::ArrayView) override { + CORRADE_FAIL("This should not get called."); + return {}; + } + + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { + _opened = true; return {}; } @@ -457,24 +728,733 @@ void AbstractFontTest::openFileAsDataNotFound() { Vector2 doGlyphSize(UnsignedInt) override { return {}; } Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } Containers::Pointer doCreateShaper() override { return {}; } + + private: + bool _opened = false; } font; - Containers::String out; - Error redirectError{&out}; - CORRADE_VERIFY(!font.openFile("nonexistent.foo", 12.0f)); - CORRADE_VERIFY(!font.isOpened()); - /* There's an error message from Path::read() before */ - CORRADE_COMPARE_AS(out, - "\nText::AbstractFont::openFile(): cannot open file nonexistent.foo\n", - TestSuite::Compare::StringHasSuffix); + /* Verify that the API doesn't affect the currently opened font in any way + -- it stays open if it was, and stays closed if wasn't */ + if(data.fileOpened) + CORRADE_VERIFY(font.openFile(Utility::Path::join(TEXT_TEST_DIR, "data.bin"), 1.0f)); + CORRADE_COMPARE(font.isOpened(), data.fileOpened); + + { + Containers::String out; + Error redirectError{&out}; + CORRADE_VERIFY(!font.fileFontCount("nonexistent.foo")); + /* There's an error message from Path::read() before */ + CORRADE_COMPARE_AS(out, + "\nText::AbstractFont::fileFontCount(): cannot open file nonexistent.foo\n", + TestSuite::Compare::StringHasSuffix); + } + + CORRADE_COMPARE(font.isOpened(), data.fileOpened); } -void AbstractFontTest::openFileAsDataFailed() { +void AbstractFontTest::fileFontCountAsDataFailed() { + auto&& data = FontCountData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + struct: AbstractFont { FontFeatures doFeatures() const override { return FontFeature::OpenData; } - bool doIsOpened() const override { return false; } - void doClose() override {} - + bool doIsOpened() const override { return _opened; } + void doClose() override { + CORRADE_FAIL("This should not get called."); + } + + Containers::Optional doDataFontCount(Containers::ArrayView) override { + called = true; + return {}; + } + + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { + _opened = true; + return {}; + } + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + + bool called = false; + + private: + bool _opened = false; + } font; + + /* Verify that the API doesn't affect the currently opened font in any way + -- it stays open if it was, and stays closed if wasn't */ + if(data.fileOpened) + CORRADE_VERIFY(font.openFile(Utility::Path::join(TEXT_TEST_DIR, "data.bin"), 1.0f)); + CORRADE_COMPARE(font.isOpened(), data.fileOpened); + + /* The implementation is expected to print an error message on its own */ + { + Containers::String out; + Error redirectError{&out}; + CORRADE_VERIFY(!font.fileFontCount(Utility::Path::join(TEXT_TEST_DIR, "data.bin"))); + CORRADE_VERIFY(font.called); + CORRADE_COMPARE(out, ""); + } + + CORRADE_COMPARE(font.isOpened(), data.fileOpened); +} + +void AbstractFontTest::fileFontCountAsDataNotImplemented() { + struct: AbstractFont { + FontFeatures doFeatures() const override { return FontFeature::OpenData; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + } font; + + /* Should call the default doFileFontCount() which then delegates to + the default doDataFontCount() which then returns 1 */ + CORRADE_COMPARE(font.dataFontCount(nullptr), 1); +} + +void AbstractFontTest::fontCountInvalid() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractFont { + FontFeatures doFeatures() const override { return FontFeature::OpenData; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + Containers::Optional doFileFontCount(Containers::StringView) override { + return 0; + } + + Containers::Optional doDataFontCount(Containers::ArrayView) override { + return 0; + } + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + } font; + + Containers::String out; + Error redirectError{&out}; + font.fileFontCount(nullptr); + font.dataFontCount(nullptr); + CORRADE_COMPARE_AS(out, + "Text::AbstractFont::fileFontCount(): implementation returned zero\n" + "Text::AbstractFont::dataFontCount(): implementation returned zero\n", + TestSuite::Compare::String); +} + +void AbstractFontTest::dataFontCountNotSupported() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractFont { + /* Supports neither file nor data opening */ + FontFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + } font; + + Containers::String out; + Error redirectError{&out}; + font.dataFontCount(nullptr); + CORRADE_COMPARE(out, "Text::AbstractFont::dataFontCount(): feature not supported\n"); +} + +void AbstractFontTest::openData() { + auto&& data = OpenData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct: AbstractFont { + FontFeatures doFeatures() const override { return FontFeature::OpenData; } + bool doIsOpened() const override { return _opened; } + void doClose() override { + CORRADE_VERIFY(_opened); + _opened = false; + } + + Properties doOpenData(Containers::ArrayView data, Float size, UnsignedInt fontId) override { + CORRADE_COMPARE_AS(data, + Containers::arrayView({'\xa5'}), + TestSuite::Compare::Container); + CORRADE_COMPARE(fontId, expectedFontId); + _opened = true; + return {size, 1.0f, 2.0f, 3.0f, 15}; + } + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + + UnsignedInt expectedFontId; + + private: + bool _opened = false; + } font; + font.expectedFontId = data.fontId; + + CORRADE_VERIFY(!font.isOpened()); + const char a5[]{'\xa5'}; + /* Verify that even with font ID 0 it delegates to the non-deprecated + doOpenData() overload */ + /** @todo remove the instanced font ID once the deprecated APIs are gone, + supply a constant instead */ + CORRADE_VERIFY(font.openData(a5, 13.0f, data.fontId)); + CORRADE_VERIFY(font.isOpened()); + CORRADE_COMPARE(font.size(), 13.0f); + CORRADE_COMPARE(font.ascent(), 1.0f); + CORRADE_COMPARE(font.descent(), 2.0f); + CORRADE_COMPARE(font.lineHeight(), 3.0f); + CORRADE_COMPARE(font.glyphCount(), 15); + + font.close(); + CORRADE_VERIFY(!font.isOpened()); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +void AbstractFontTest::openDataDeprecated() { + /* Like openData(), but implementing the deprecated doOpenData() and + verifying that it's correctly delegated to */ + + struct: AbstractFont { + FontFeatures doFeatures() const override { return FontFeature::OpenData; } + bool doIsOpened() const override { return _opened; } + void doClose() override { + CORRADE_VERIFY(_opened); + _opened = false; + } + + Properties doOpenData(Containers::ArrayView data, Float size) override { + CORRADE_COMPARE_AS(data, + Containers::arrayView({'\xa5'}), + TestSuite::Compare::Container); + _opened = true; + return {size, 1.0f, 2.0f, 3.0f, 15}; + } + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + + private: + bool _opened = false; + } font; + + CORRADE_VERIFY(!font.isOpened()); + const char a5[]{'\xa5'}; + /* Should delegate to the deprecated doOpenData(), and fail with font ID + being non-zero, which is tested below */ + CORRADE_VERIFY(font.openData(a5, 13.0f)); + CORRADE_VERIFY(font.isOpened()); + CORRADE_COMPARE(font.size(), 13.0f); + CORRADE_COMPARE(font.ascent(), 1.0f); + CORRADE_COMPARE(font.descent(), 2.0f); + CORRADE_COMPARE(font.lineHeight(), 3.0f); + CORRADE_COMPARE(font.glyphCount(), 15); + + font.close(); + CORRADE_VERIFY(!font.isOpened()); +} + +void AbstractFontTest::openDataDeprecatedNonZeroFontId() { + /* When openData() gets a non-zero font ID, it should not delegate to the + deprecated overload but fail directly */ + + struct: AbstractFont { + FontFeatures doFeatures() const override { return FontFeature::OpenData; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + Properties doOpenData(Containers::ArrayView, Float) override { + CORRADE_FAIL("This should not be called"); + return {}; + } + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + } font; + + CORRADE_VERIFY(!font.isOpened()); + const char a5[]{'\xa5'}; + + Containers::String out; + Error redirectError{&out}; + CORRADE_VERIFY(!font.openData(a5, 13.0f, 1)); + CORRADE_VERIFY(!font.isOpened()); + CORRADE_COMPARE(out, "Text::AbstractFont::openData(): cannot open font at index 1\n"); +} +#endif + +void AbstractFontTest::openDataFailed() { + auto&& data = OpenData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct: AbstractFont { + FontFeatures doFeatures() const override { return FontFeature::OpenData; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { + called = true; + return {}; + } + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + + bool called = false; + } font; + + /* The implementation is expected to print an error message on its own */ + Containers::String out; + Error redirectError{&out}; + /* Verify that the behavior is the same also with a potential deprecated + overload for font 0 */ + /** @todo remove the instanced font ID once the deprecated APIs are gone, + supply a constant instead */ + CORRADE_VERIFY(!font.openData(nullptr, 1.0f, data.fontId)); + CORRADE_VERIFY(!font.isOpened()); + CORRADE_VERIFY(font.called); + CORRADE_COMPARE(out, ""); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +void AbstractFontTest::openDataFailedDeprecated() { + /* Like openDataFailed(), but implementing the deprecated doOpenData() and + verifying that it's correctly delegated to */ + + struct: AbstractFont { + FontFeatures doFeatures() const override { return FontFeature::OpenData; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + Properties doOpenData(Containers::ArrayView, Float) override { + called = true; + return {}; + } + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + + bool called = false; + } font; + + /* The implementation is expected to print an error message on its own */ + Containers::String out; + Error redirectError{&out}; + CORRADE_VERIFY(!font.openData(nullptr, 1.0f)); + CORRADE_VERIFY(!font.isOpened()); + CORRADE_VERIFY(font.called); + CORRADE_COMPARE(out, ""); +} +#endif + +void AbstractFontTest::openFile() { + auto&& data = OpenData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct: AbstractFont { + FontFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return _opened; } + void doClose() override { + CORRADE_VERIFY(_opened); + _opened = false; + } + + Properties doOpenFile(Containers::StringView filename, Float size, UnsignedInt fontId) override { + CORRADE_COMPARE(filename, "hello.ttf"); + CORRADE_COMPARE(fontId, expectedFontId); + _opened = true; + return {size, 1.0f, 2.0f, 3.0f, 15}; + } + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + + UnsignedInt expectedFontId; + + private: + bool _opened = false; + } font; + font.expectedFontId = data.fontId; + + CORRADE_VERIFY(!font.isOpened()); + /* Verify that even with font ID 0 it delegates to the non-deprecated + doOpenFile() overload */ + /** @todo remove the instanced font ID once the deprecated APIs are gone, + supply a constant instead */ + CORRADE_VERIFY(font.openFile("hello.ttf", 13.0f, data.fontId)); + CORRADE_VERIFY(font.isOpened()); + CORRADE_COMPARE(font.size(), 13.0f); + CORRADE_COMPARE(font.ascent(), 1.0f); + CORRADE_COMPARE(font.descent(), 2.0f); + CORRADE_COMPARE(font.lineHeight(), 3.0f); + CORRADE_COMPARE(font.glyphCount(), 15); + + font.close(); + CORRADE_VERIFY(!font.isOpened()); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +void AbstractFontTest::openFileDeprecated() { + /* Like openFile(), but implementing the deprecated doOpenFile() and + verifying that it's correctly delegated to */ + + struct: AbstractFont { + FontFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return _opened; } + void doClose() override { + CORRADE_VERIFY(_opened); + _opened = false; + } + + Properties doOpenFile(Containers::StringView filename, Float size) override { + CORRADE_COMPARE(filename, "hello.ttf"); + _opened = true; + return {size, 1.0f, 2.0f, 3.0f, 15}; + } + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + + private: + bool _opened = false; + } font; + + CORRADE_VERIFY(!font.isOpened()); + /* Should delegate to the deprecated doOpenFile(), and fail with font ID + being non-zero, which is tested below */ + CORRADE_VERIFY(font.openFile("hello.ttf", 13.0f)); + CORRADE_VERIFY(font.isOpened()); + CORRADE_COMPARE(font.size(), 13.0f); + CORRADE_COMPARE(font.ascent(), 1.0f); + CORRADE_COMPARE(font.descent(), 2.0f); + CORRADE_COMPARE(font.lineHeight(), 3.0f); + CORRADE_COMPARE(font.glyphCount(), 15); + + font.close(); + CORRADE_VERIFY(!font.isOpened()); +} + +void AbstractFontTest::openFileDeprecatedNonZeroFontId() { + /* When openFile() gets a non-zero font ID, it should not delegate to the + deprecated overload but fail directly */ + + struct: AbstractFont { + FontFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + Properties doOpenFile(Containers::StringView, Float) override { + CORRADE_FAIL("This should not be called"); + return {}; + } + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + } font; + + CORRADE_VERIFY(!font.isOpened()); + + Containers::String out; + Error redirectError{&out}; + CORRADE_VERIFY(!font.openFile("hello.ttf", 13.0f, 1)); + CORRADE_VERIFY(!font.isOpened()); + CORRADE_COMPARE(out, "Text::AbstractFont::openFile(): cannot open font at index 1\n"); +} +#endif + +void AbstractFontTest::openFileFailed() { + auto&& data = OpenData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct: AbstractFont { + FontFeatures doFeatures() const override { return FontFeature::OpenData; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + Properties doOpenFile(Containers::StringView, Float, UnsignedInt) override { + called = true; + return {}; + } + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + + bool called = false; + } font; + + /* The implementation is expected to print an error message on its own */ + Containers::String out; + Error redirectError{&out}; + /* Verify that the behavior is the same also with a potential deprecated + overload for font 0 */ + /** @todo remove the instanced font ID once the deprecated APIs are gone, + supply a constant instead */ + CORRADE_VERIFY(!font.openFile("hello.ttf", 1.0f, data.fontId)); + CORRADE_VERIFY(!font.isOpened()); + CORRADE_VERIFY(font.called); + CORRADE_COMPARE(out, ""); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +void AbstractFontTest::openFileFailedDeprecated() { + /* Like openFileFailed(), but implementing the deprecated doOpenFile() and + verifying that it's correctly delegated to */ + + struct: AbstractFont { + FontFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + Properties doOpenFile(Containers::StringView, Float) override { + called = true; + return {}; + } + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + + bool called = false; + } font; + + /* The implementation is expected to print an error message on its own */ + Containers::String out; + Error redirectError{&out}; + CORRADE_VERIFY(!font.openFile("hello.ttf", 1.0f)); + CORRADE_VERIFY(!font.isOpened()); + CORRADE_VERIFY(font.called); + CORRADE_COMPARE(out, ""); +} +#endif + +void AbstractFontTest::openFileAsData() { + auto&& data = OpenData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct: AbstractFont { + FontFeatures doFeatures() const override { return FontFeature::OpenData; } + bool doIsOpened() const override { return _opened; } + void doClose() override {} + + Properties doOpenData(Containers::ArrayView data, Float size, UnsignedInt fontId) override { + CORRADE_COMPARE_AS(data, + Containers::arrayView({'\xa5'}), + TestSuite::Compare::Container); + CORRADE_COMPARE(fontId, expectedFontId); + _opened = true; + return {size, 1.0f, 2.0f, 3.0f, 15}; + } + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + + UnsignedInt expectedFontId; + + private: + bool _opened = false; + } font; + font.expectedFontId = data.fontId; + + /* doOpenFile() should call doOpenData() */ + CORRADE_VERIFY(!font.isOpened()); + /* Verify that even with font ID 0 it delegates to the non-deprecated + doOpenData() overload */ + /** @todo remove the instanced font ID once the deprecated APIs are gone, + supply a constant instead */ + CORRADE_VERIFY(font.openFile(Utility::Path::join(TEXT_TEST_DIR, "data.bin"), 13.0f, data.fontId)); + CORRADE_VERIFY(font.isOpened()); + CORRADE_COMPARE(font.size(), 13.0f); + CORRADE_COMPARE(font.ascent(), 1.0f); + CORRADE_COMPARE(font.descent(), 2.0f); + CORRADE_COMPARE(font.lineHeight(), 3.0f); + CORRADE_COMPARE(font.glyphCount(), 15); +} + +void AbstractFontTest::openFileAsDataNotFound() { + auto&& data = OpenData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct: AbstractFont { + FontFeatures doFeatures() const override { return FontFeature::OpenData; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { + CORRADE_FAIL("This should not be called"); + return {}; + } + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + } font; + + Containers::String out; + Error redirectError{&out}; + /* Verify that the behavior is the same also with a potential deprecated + overload for font 0 */ + /** @todo remove the instanced font ID once the deprecated APIs are gone, + supply a constant instead */ + CORRADE_VERIFY(!font.openFile("nonexistent.foo", 12.0f, data.fontId)); + CORRADE_VERIFY(!font.isOpened()); + /* There's an error message from Path::read() before */ + CORRADE_COMPARE_AS(out, + "\nText::AbstractFont::openFile(): cannot open file nonexistent.foo\n", + TestSuite::Compare::StringHasSuffix); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +void AbstractFontTest::openFileAsDataDeprecated() { + /* Like openFileAsData(), but implementing the deprecated doOpenData() and + verifying that it's correctly delegated to */ + + struct: AbstractFont { + FontFeatures doFeatures() const override { return FontFeature::OpenData; } + bool doIsOpened() const override { return _opened; } + void doClose() override { + CORRADE_VERIFY(_opened); + _opened = false; + } + + Properties doOpenData(Containers::ArrayView data, Float size) override { + CORRADE_COMPARE_AS(data, + Containers::arrayView({'\xa5'}), + TestSuite::Compare::Container); + _opened = true; + return {size, 1.0f, 2.0f, 3.0f, 15}; + } + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + + private: + bool _opened = false; + } font; + + CORRADE_VERIFY(!font.isOpened()); + /* Should delegate to the deprecated doOpenData(), and fail with font ID + being non-zero, which is tested below */ + CORRADE_VERIFY(font.openFile(Utility::Path::join(TEXT_TEST_DIR, "data.bin"), 13.0f)); + CORRADE_VERIFY(font.isOpened()); + CORRADE_COMPARE(font.size(), 13.0f); + CORRADE_COMPARE(font.ascent(), 1.0f); + CORRADE_COMPARE(font.descent(), 2.0f); + CORRADE_COMPARE(font.lineHeight(), 3.0f); + CORRADE_COMPARE(font.glyphCount(), 15); + + font.close(); + CORRADE_VERIFY(!font.isOpened()); +} + +void AbstractFontTest::openFileAsDataDeprecatedNonZeroFontId() { + /* When openFile() gets a non-zero font ID, it should not delegate to the + deprecated overload but fail directly (after actually opening the file + and delegating to data) */ + + struct: AbstractFont { + FontFeatures doFeatures() const override { return FontFeature::OpenData; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + Properties doOpenData(Containers::ArrayView, Float) override { + CORRADE_FAIL("This should not be called"); + return {}; + } + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + } font; + + Containers::String out; + Error redirectError{&out}; + CORRADE_VERIFY(!font.openFile(Utility::Path::join(TEXT_TEST_DIR, "data.bin"), 13.0f, 1)); + CORRADE_VERIFY(!font.isOpened()); + CORRADE_COMPARE(out, "Text::AbstractFont::openData(): cannot open font at index 1\n"); +} +#endif + +void AbstractFontTest::openFileAsDataFailed() { + auto&& data = OpenData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct: AbstractFont { + FontFeatures doFeatures() const override { return FontFeature::OpenData; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { + called = true; + return {}; + } + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + + bool called = false; + } font; + + /* The implementation is expected to print an error message on its own */ + Containers::String out; + Error redirectError{&out}; + /* Verify that the behavior is the same also with a potential deprecated + overload for font 0 */ + /** @todo remove the instanced font ID once the deprecated APIs are gone, + supply a constant instead */ + CORRADE_VERIFY(!font.openData(nullptr, 1.0f, data.fontId)); + CORRADE_VERIFY(!font.isOpened()); + CORRADE_VERIFY(font.called); + CORRADE_COMPARE(out, ""); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +void AbstractFontTest::openFileAsDataFailedDeprecated() { + /* Like openFileAsDataFailed(), but implementing the deprecated + doOpenData() and verifying that it's correctly delegated to */ + + struct: AbstractFont { + FontFeatures doFeatures() const override { return FontFeature::OpenData; } + bool doIsOpened() const override { return false; } + void doClose() override {} + Properties doOpenData(Containers::ArrayView, Float) override { called = true; return {}; @@ -496,8 +1476,12 @@ void AbstractFontTest::openFileAsDataFailed() { CORRADE_VERIFY(font.called); CORRADE_COMPARE(out, ""); } +#endif void AbstractFontTest::openFileNotImplemented() { + auto&& data = OpenData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractFont { @@ -514,11 +1498,28 @@ void AbstractFontTest::openFileNotImplemented() { Containers::String out; Error redirectError{&out}; - font.openFile("file.foo", 34.0f); - CORRADE_COMPARE(out, "Text::AbstractFont::openFile(): not implemented\n"); + /** @todo remove the instanced font ID and use the default argument once + the deprecated APIs are gone, as there will be no delegation variants + to test */ + font.openFile("file.foo", 34.0f, data.fontId); + { + #ifdef MAGNUM_BUILD_DEPRECATED + CORRADE_EXPECT_FAIL_IF(data.fontId != 0, + "On deprecated builds it's impossible to detect that even the deprecated doOpenFile() overload was not implemented for a non-zero font ID, and a different (non-asserting) message is printed."); + #endif + CORRADE_COMPARE(out, "Text::AbstractFont::openFile(): not implemented\n"); + } + #ifdef MAGNUM_BUILD_DEPRECATED + /* This is printed for non-zero font IDs on deprecated builds instead */ + if(data.fontId != 0) + CORRADE_COMPARE(out, "Text::AbstractFont::openFile(): cannot open font at index 1\n"); + #endif } void AbstractFontTest::openDataNotSupported() { + auto&& data = OpenData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractFont { @@ -535,11 +1536,17 @@ void AbstractFontTest::openDataNotSupported() { Containers::String out; Error redirectError{&out}; - font.openData(nullptr, 34.0f); + /** @todo remove the instanced font ID and use the default argument once + the deprecated APIs are gone, as there will be no delegation variants + to test */ + font.openData(nullptr, 34.0f, data.fontId); CORRADE_COMPARE(out, "Text::AbstractFont::openData(): feature not supported\n"); } void AbstractFontTest::openDataNotImplemented() { + auto&& data = OpenData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractFont { @@ -555,8 +1562,22 @@ void AbstractFontTest::openDataNotImplemented() { Containers::String out; Error redirectError{&out}; - font.openData(nullptr, 34.0f); - CORRADE_COMPARE(out, "Text::AbstractFont::openData(): feature advertised but not implemented\n"); + /** @todo remove the instanced font ID and use the default argument once + the deprecated APIs are gone, as there will be no delegation variants + to test */ + font.openData(nullptr, 34.0f, data.fontId); + { + #ifdef MAGNUM_BUILD_DEPRECATED + CORRADE_EXPECT_FAIL_IF(data.fontId != 0, + "On deprecated builds it's impossible to detect that even the deprecated doOpenData() overload was not implemented for a non-zero font ID, and a different (non-asserting) message is printed."); + #endif + CORRADE_COMPARE(out, "Text::AbstractFont::openData(): feature advertised but not implemented\n"); + } + #ifdef MAGNUM_BUILD_DEPRECATED + /* This is printed for non-zero font IDs on deprecated builds instead */ + if(data.fontId != 0) + CORRADE_COMPARE(out, "Text::AbstractFont::openData(): cannot open font at index 1\n"); + #endif } void AbstractFontTest::setFileCallback() { @@ -684,7 +1705,6 @@ void AbstractFontTest::setFileCallbackFileOpened() { Containers::String out; Error redirectError{&out}; - font.setFileCallback([](const std::string&, InputFileCallbackPolicy, void*) { return Containers::Optional>{}; }); @@ -738,6 +1758,83 @@ void AbstractFontTest::setFileCallbackNotSupported() { } void AbstractFontTest::setFileCallbackOpenFileDirectly() { + auto&& data = OpenData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct: AbstractFont { + FontFeatures doFeatures() const override { return FontFeature::FileCallback|FontFeature::OpenData; } + bool doIsOpened() const override { return _opened; } + void doClose() override { _opened = false; } + + Containers::Optional doFileFontCount(Containers::StringView filename) override { + /* Called because FileCallback is supported */ + CORRADE_COMPARE(filename, "file.dat"); + CORRADE_VERIFY(fileCallback()); + CORRADE_VERIFY(fileCallbackUserData()); + return 37; + } + + Containers::Optional doDataFontCount(Containers::ArrayView) override { + CORRADE_FAIL("This should not be called"); + return {}; + } + + Properties doOpenFile(Containers::StringView filename, Float size, UnsignedInt fontId) override { + /* Called because FileCallback is supported */ + CORRADE_COMPARE(filename, "file.dat"); + CORRADE_COMPARE(fontId, expectedFontId); + CORRADE_VERIFY(fileCallback()); + CORRADE_VERIFY(fileCallbackUserData()); + _opened = true; + return {size, 1.0f, 2.0f, 3.0f, 15}; + } + + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { + CORRADE_FAIL("This should not be called"); + return {}; + } + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + + UnsignedInt expectedFontId; + + private: + bool _opened = false; + } font; + font.expectedFontId = data.fontId; + + /* The callback shouldn't be called from the class itself, it's the + doOpenFile() implementation responsibility to call it. In this case the + implementation only verifies that it's set, along with the user data. */ + int dummy; + font.setFileCallback([](const std::string&, InputFileCallbackPolicy, void*) -> Containers::Optional> { + CORRADE_FAIL("This should not be called"); + return {}; + }, &dummy); + + CORRADE_COMPARE(font.fileFontCount("file.dat"), 37); + /* Verify that the behavior is the same also with a potential deprecated + overload for font 0 */ + /** @todo remove the instanced font ID once the deprecated APIs are gone, + supply a constant instead */ + CORRADE_VERIFY(font.openFile("file.dat", 42.0f, data.fontId)); + CORRADE_VERIFY(font.isOpened()); + CORRADE_COMPARE(font.size(), 42.0f); + CORRADE_COMPARE(font.ascent(), 1.0f); + CORRADE_COMPARE(font.descent(), 2.0f); + CORRADE_COMPARE(font.lineHeight(), 3.0f); + CORRADE_COMPARE(font.glyphCount(), 15); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +void AbstractFontTest::setFileCallbackOpenFileDirectlyDeprecated() { + /* Like setFileCallbackOpenFileDirectly(), but implementing the deprecated + doOpenFile() and verifying that it's correctly delegated to. There's no + deprecated doFileFontCount() variant so that part is omitted here. */ + struct: AbstractFont { FontFeatures doFeatures() const override { return FontFeature::FileCallback|FontFeature::OpenData; } bool doIsOpened() const override { return _opened; } @@ -752,6 +1849,10 @@ void AbstractFontTest::setFileCallbackOpenFileDirectly() { return {size, 1.0f, 2.0f, 3.0f, 15}; } + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { + CORRADE_FAIL("This should not be called"); + return {}; + } Properties doOpenData(Containers::ArrayView, Float) override { CORRADE_FAIL("This should not be called"); return {}; @@ -784,7 +1885,127 @@ void AbstractFontTest::setFileCallbackOpenFileDirectly() { CORRADE_COMPARE(font.glyphCount(), 15); } +void AbstractFontTest::setFileCallbackOpenFileDirectlyDeprecatedNonZeroFontId() { + /* When openFile() gets a non-zero font ID, it should not delegate to the + deprecated overload but fail directly (after actually opening the file + through the callback and delegating to data) */ + + struct: AbstractFont { + FontFeatures doFeatures() const override { return FontFeature::FileCallback|FontFeature::OpenData; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + Properties doOpenFile(Containers::StringView, Float) override { + CORRADE_FAIL("This should not be called"); + return {}; + } + + Properties doOpenData(Containers::ArrayView, Float) override { + CORRADE_FAIL("This should not be called"); + return {}; + } + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + } font; + + struct State { + bool loaded = false; + bool closed = false; + } state; + font.setFileCallback([](const std::string&, InputFileCallbackPolicy policy, State& state) -> Containers::Optional> { + if(policy == InputFileCallbackPolicy::LoadTemporary) { + state.loaded = true; + return Containers::ArrayView{}; + } + + if(policy == InputFileCallbackPolicy::Close) { + state.closed = true; + return {}; + } + + CORRADE_FAIL("Unexpected policy" << policy); + return {}; + }, state); + + Containers::String out; + Error redirectError{&out}; + CORRADE_VERIFY(!font.openFile("file.dat", 42.0f, 1)); + CORRADE_VERIFY(!font.isOpened()); + CORRADE_VERIFY(state.loaded); + CORRADE_VERIFY(state.closed); + CORRADE_COMPARE(out, "Text::AbstractFont::openData(): cannot open font at index 1\n"); +} +#endif + void AbstractFontTest::setFileCallbackOpenFileDirectlyFailed() { + auto&& data = OpenData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct: AbstractFont { + FontFeatures doFeatures() const override { return FontFeature::FileCallback|FontFeature::OpenData; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + Containers::Optional doFileFontCount(Containers::StringView) override { + countCalled = true; + return {}; + } + + Containers::Optional doDataFontCount(Containers::ArrayView) override { + CORRADE_FAIL("This should not be called"); + return {}; + } + + Properties doOpenFile(Containers::StringView, Float, UnsignedInt) override { + openCalled = true; + return {}; + } + + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { + CORRADE_FAIL("This should not be called"); + return {}; + } + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + + bool countCalled = false; + bool openCalled = false; + } font; + + font.setFileCallback([](const std::string&, InputFileCallbackPolicy, void*) -> Containers::Optional> { + CORRADE_FAIL("This shouldn't be called"); + return {}; + }); + + /* The implementation is expected to print an error message on its own */ + Containers::String out; + Error redirectError{&out}; + CORRADE_VERIFY(!font.fileFontCount("file.dat")); + CORRADE_VERIFY(font.countCalled); + CORRADE_VERIFY(!font.openCalled); + /* Verify that the behavior is the same also with a potential deprecated + overload for font 0 */ + /** @todo remove the instanced font ID once the deprecated APIs are gone, + supply a constant instead */ + CORRADE_VERIFY(!font.openFile("file.dat", 42.0f, data.fontId)); + CORRADE_VERIFY(!font.isOpened()); + CORRADE_VERIFY(font.countCalled); + CORRADE_VERIFY(font.openCalled); + CORRADE_COMPARE(out, ""); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +void AbstractFontTest::setFileCallbackOpenFileDirectlyFailedDeprecated() { + /* Like setFileCallbackOpenFileDirectly(), but implementing the deprecated + doOpenFile() and verifying that it's correctly delegated to. There's no + deprecated doFileFontCount() variant so that part is omitted here. */ + struct: AbstractFont { FontFeatures doFeatures() const override { return FontFeature::FileCallback|FontFeature::OpenData; } bool doIsOpened() const override { return false; } @@ -795,6 +2016,10 @@ void AbstractFontTest::setFileCallbackOpenFileDirectlyFailed() { return {}; } + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { + CORRADE_FAIL("This should not be called"); + return {}; + } Properties doOpenData(Containers::ArrayView, Float) override { CORRADE_FAIL("This should not be called"); return {}; @@ -821,8 +2046,116 @@ void AbstractFontTest::setFileCallbackOpenFileDirectlyFailed() { CORRADE_VERIFY(font.called); CORRADE_COMPARE(out, ""); } +#endif void AbstractFontTest::setFileCallbackOpenFileThroughBaseImplementation() { + auto&& data = OpenData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct: AbstractFont { + FontFeatures doFeatures() const override { return FontFeature::FileCallback|FontFeature::OpenData; } + bool doIsOpened() const override { return _opened; } + void doClose() override { _opened = false; } + + Containers::Optional doFileFontCount(Containers::StringView filename) override { + CORRADE_COMPARE(filename, "file.dat"); + CORRADE_VERIFY(fileCallback()); + CORRADE_VERIFY(fileCallbackUserData()); + countCalled = true; + return AbstractFont::doFileFontCount(filename); + } + + Containers::Optional doDataFontCount(Containers::ArrayView data) override { + CORRADE_COMPARE_AS(data, + Containers::arrayView({'\xb0'}), + TestSuite::Compare::Container); + return 37; + } + + Properties doOpenFile(Containers::StringView filename, Float size, UnsignedInt fontId) override { + CORRADE_COMPARE(filename, "file.dat"); + CORRADE_COMPARE(fontId, expectedFontId); + CORRADE_VERIFY(fileCallback()); + CORRADE_VERIFY(fileCallbackUserData()); + openCalled = true; + return AbstractFont::doOpenFile(filename, size, fontId); + } + + Properties doOpenData(Containers::ArrayView data, Float size, UnsignedInt fontId) override { + CORRADE_COMPARE_AS(data, + Containers::arrayView({'\xb0'}), + TestSuite::Compare::Container); + CORRADE_COMPARE(fontId, expectedFontId); + _opened = true; + return {size, 1.0f, 2.0f, 3.0f, 15}; + } + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + + UnsignedInt expectedFontId; + bool countCalled = false; + bool openCalled = false; + + private: + bool _opened = false; + } font; + font.expectedFontId = data.fontId; + + struct State { + const char data = '\xb0'; + Int loaded = 0; + Int closed = 0; + } state; + font.setFileCallback([](const std::string& filename, InputFileCallbackPolicy policy, State& state) -> Containers::Optional> { + CORRADE_COMPARE(Containers::StringView{filename}, "file.dat"); + + if(policy == InputFileCallbackPolicy::LoadTemporary) { + ++state.loaded; + return Containers::arrayView(&state.data, 1); + } + + if(policy == InputFileCallbackPolicy::Close) { + ++state.closed; + return {}; + } + + CORRADE_FAIL("Unexpected policy" << policy); + return {}; + }, state); + + CORRADE_COMPARE(font.fileFontCount("file.dat"), 37); + CORRADE_VERIFY(font.countCalled); + CORRADE_VERIFY(!font.openCalled); + CORRADE_COMPARE(state.loaded, 1); + CORRADE_COMPARE(state.closed, 1); + /* Verify that the behavior is the same also with a potential deprecated + overload for font 0 */ + /** @todo remove the instanced font ID once the deprecated APIs are gone, + supply a constant instead */ + CORRADE_VERIFY(font.openFile("file.dat", 42.0f, data.fontId)); + CORRADE_VERIFY(font.isOpened()); + CORRADE_VERIFY(font.countCalled); + CORRADE_VERIFY(font.openCalled); + CORRADE_COMPARE(state.loaded, 2); + CORRADE_COMPARE(state.closed, 2); + CORRADE_COMPARE(font.size(), 42.0f); + CORRADE_COMPARE(font.ascent(), 1.0f); + CORRADE_COMPARE(font.descent(), 2.0f); + CORRADE_COMPARE(font.lineHeight(), 3.0f); + CORRADE_COMPARE(font.glyphCount(), 15); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +void AbstractFontTest::setFileCallbackOpenFileThroughBaseImplementationDeprecated() { + /* Like setFileCallbackOpenFileThroughBaseImplementation(), but + implementing the deprecated doOpenFile() and doOpenData() and verifying + that it's correctly delegated to. There's no deprecated + doFileFontCount() or doDataFontCount() variant so that part is omitted + here. */ + struct: AbstractFont { FontFeatures doFeatures() const override { return FontFeature::FileCallback|FontFeature::OpenData; } bool doIsOpened() const override { return _opened; } @@ -833,7 +2166,9 @@ void AbstractFontTest::setFileCallbackOpenFileThroughBaseImplementation() { CORRADE_VERIFY(fileCallback()); CORRADE_VERIFY(fileCallbackUserData()); called = true; + CORRADE_IGNORE_DEPRECATED_PUSH return AbstractFont::doOpenFile(filename, size); + CORRADE_IGNORE_DEPRECATED_POP } Properties doOpenData(Containers::ArrayView data, Float size) override { @@ -888,20 +2223,102 @@ void AbstractFontTest::setFileCallbackOpenFileThroughBaseImplementation() { CORRADE_COMPARE(font.lineHeight(), 3.0f); CORRADE_COMPARE(font.glyphCount(), 15); } +#endif + +void AbstractFontTest::setFileCallbackOpenFileThroughBaseImplementationNotFound() { + auto&& data = OpenData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct: AbstractFont { + FontFeatures doFeatures() const override { return FontFeature::FileCallback|FontFeature::OpenData; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + Containers::Optional doFileFontCount(Containers::StringView filename) override { + countCalled = true; + return AbstractFont::doFileFontCount(filename); + } + + Containers::Optional doDataFontCount(Containers::ArrayView) override { + CORRADE_FAIL("This should not be called"); + return 37; + } + + Properties doOpenFile(Containers::StringView filename, Float size, UnsignedInt fontId) override { + openCalled = true; + return AbstractFont::doOpenFile(filename, size, fontId); + } + + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { + CORRADE_FAIL("This should not be called"); + return {}; + } + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + + bool countCalled = false; + bool openCalled = false; + } font; + + Int fileCallbackCalled = 0; + font.setFileCallback([](const std::string&, InputFileCallbackPolicy policy, Int& fileCallbackCalled) -> Containers::Optional> { + /* The callback should be only called to open the file, not to close it + afterwards */ + CORRADE_COMPARE(policy, InputFileCallbackPolicy::LoadTemporary); + ++fileCallbackCalled; + return {}; + }, fileCallbackCalled); + + Containers::String out; + Error redirectError{&out}; + CORRADE_VERIFY(!font.fileFontCount("file.dat")); + CORRADE_VERIFY(font.countCalled); + CORRADE_VERIFY(!font.openCalled); + CORRADE_COMPARE(fileCallbackCalled, 1); + /* Verify that the behavior is the same also with a potential deprecated + overload for font 0 */ + /** @todo remove the instanced font ID once the deprecated APIs are gone, + supply a constant instead */ + CORRADE_VERIFY(!font.openFile("file.dat", 42.0f, data.fontId)); + CORRADE_VERIFY(!font.isOpened()); + CORRADE_VERIFY(font.countCalled); + CORRADE_VERIFY(font.openCalled); + CORRADE_COMPARE(fileCallbackCalled, 2); + CORRADE_COMPARE_AS(out, + "Text::AbstractFont::fileFontCount(): cannot open file file.dat\n" + "Text::AbstractFont::openFile(): cannot open file file.dat\n", + TestSuite::Compare::String); +} + +void AbstractFontTest::setFileCallbackOpenFileThroughBaseImplementationFailed() { + auto&& data = OpenData[testCaseInstanceId()]; + setTestCaseDescription(data.name); -void AbstractFontTest::setFileCallbackOpenFileThroughBaseImplementationNotFound() { struct: AbstractFont { FontFeatures doFeatures() const override { return FontFeature::FileCallback|FontFeature::OpenData; } bool doIsOpened() const override { return false; } void doClose() override {} - Properties doOpenFile(Containers::StringView filename, Float size) override { - called = true; - return AbstractFont::doOpenFile(filename, size); + Containers::Optional doFileFontCount(Containers::StringView filename) override { + fileCountCalled = true; + return AbstractFont::doFileFontCount(filename); } - Properties doOpenData(Containers::ArrayView, Float) override { - CORRADE_FAIL("This should not be called"); + Containers::Optional doDataFontCount(Containers::ArrayView) override { + dataCountCalled = true; + return {}; + } + + Properties doOpenFile(Containers::StringView filename, Float size, UnsignedInt fontId) override { + openFileCalled = true; + return AbstractFont::doOpenFile(filename, size, fontId); + } + + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { + openDataCalled = true; return {}; } @@ -910,28 +2327,64 @@ void AbstractFontTest::setFileCallbackOpenFileThroughBaseImplementationNotFound( Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } Containers::Pointer doCreateShaper() override { return {}; } - bool called = false; + bool fileCountCalled = false; + bool dataCountCalled = false; + bool openFileCalled = false; + bool openDataCalled = false; } font; - bool fileCallbackCalled = false; - font.setFileCallback([](const std::string&, InputFileCallbackPolicy policy, bool& fileCallbackCalled) -> Containers::Optional> { - /* The callback should be only called to open the file, not to close it - afterwards */ - CORRADE_COMPARE(policy, InputFileCallbackPolicy::LoadTemporary); - fileCallbackCalled = true; + struct State { + Int loaded = 0; + Int closed = 0; + } state; + font.setFileCallback([](const std::string&, InputFileCallbackPolicy policy, State& state) -> Containers::Optional> { + if(policy == InputFileCallbackPolicy::LoadTemporary) { + ++state.loaded; + return Containers::ArrayView{}; + } + + if(policy == InputFileCallbackPolicy::Close) { + ++state.closed; + return {}; + } + + CORRADE_FAIL("Unexpected policy" << policy); return {}; - }, fileCallbackCalled); + }, state); + /* The implementation is expected to print an error message on its own */ Containers::String out; Error redirectError{&out}; - CORRADE_VERIFY(!font.openFile("file.dat", 42.0f)); + CORRADE_VERIFY(!font.fileFontCount("file.dat")); + CORRADE_VERIFY(font.fileCountCalled); + CORRADE_VERIFY(font.dataCountCalled); + CORRADE_VERIFY(!font.openFileCalled); + CORRADE_VERIFY(!font.openDataCalled); + CORRADE_COMPARE(state.loaded, 1); + CORRADE_COMPARE(state.closed, 1); + /* Verify that the behavior is the same also with a potential deprecated + overload for font 0 */ + /** @todo remove the instanced font ID once the deprecated APIs are gone, + supply a constant instead */ + CORRADE_VERIFY(!font.openFile("file.dat", 42.0f, data.fontId)); CORRADE_VERIFY(!font.isOpened()); - CORRADE_VERIFY(font.called); - CORRADE_VERIFY(fileCallbackCalled); - CORRADE_COMPARE(out, "Text::AbstractFont::openFile(): cannot open file file.dat\n"); + CORRADE_VERIFY(font.fileCountCalled); + CORRADE_VERIFY(font.dataCountCalled); + CORRADE_VERIFY(font.openFileCalled); + CORRADE_VERIFY(font.openDataCalled); + CORRADE_COMPARE(state.loaded, 2); + CORRADE_COMPARE(state.closed, 2); + CORRADE_COMPARE(out, ""); } -void AbstractFontTest::setFileCallbackOpenFileThroughBaseImplementationFailed() { +#ifdef MAGNUM_BUILD_DEPRECATED +void AbstractFontTest::setFileCallbackOpenFileThroughBaseImplementationFailedDeprecated() { + /* Like setFileCallbackOpenFileThroughBaseImplementationFailed(), but + implementing the deprecated doOpenFile() and doOpenData() and verifying + that it's correctly delegated to. There's no deprecated + doFileFontCount() or doDataFontCount() variant so that part is omitted + here. */ + struct: AbstractFont { FontFeatures doFeatures() const override { return FontFeature::FileCallback|FontFeature::OpenData; } bool doIsOpened() const override { return false; } @@ -939,7 +2392,9 @@ void AbstractFontTest::setFileCallbackOpenFileThroughBaseImplementationFailed() Properties doOpenFile(Containers::StringView filename, Float size) override { openFileCalled = true; + CORRADE_IGNORE_DEPRECATED_PUSH return AbstractFont::doOpenFile(filename, size); + CORRADE_IGNORE_DEPRECATED_POP } Properties doOpenData(Containers::ArrayView, Float) override { @@ -986,13 +2441,110 @@ void AbstractFontTest::setFileCallbackOpenFileThroughBaseImplementationFailed() CORRADE_VERIFY(state.closed); CORRADE_COMPARE(out, ""); } +#endif void AbstractFontTest::setFileCallbackOpenFileAsData() { + auto&& data = OpenData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct: AbstractFont { + FontFeatures doFeatures() const override { return FontFeature::OpenData; } + bool doIsOpened() const override { return _opened; } + void doClose() override { _opened = false; } + + Containers::Optional doFileFontCount(Containers::StringView) override { + CORRADE_FAIL("This should not be called"); + return {}; + } + + Containers::Optional doDataFontCount(Containers::ArrayView data) override { + CORRADE_COMPARE_AS(data, + Containers::arrayView({'\xb0'}), + TestSuite::Compare::Container); + return 37; + } + + Properties doOpenFile(Containers::StringView, Float, UnsignedInt) override { + CORRADE_FAIL("This should not be called"); + return {}; + } + + Properties doOpenData(Containers::ArrayView data, Float size, UnsignedInt fontId) override { + CORRADE_COMPARE_AS(data, + Containers::arrayView({'\xb0'}), + TestSuite::Compare::Container); + CORRADE_COMPARE(fontId, expectedFontId); + _opened = true; + return {size, 1.0f, 2.0f, 3.0f, 15}; + } + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + + UnsignedInt expectedFontId; + + private: + bool _opened = false; + } font; + font.expectedFontId = data.fontId; + + struct State { + const char data = '\xb0'; + Int loaded = 0; + Int closed = 0; + } state; + font.setFileCallback([](const std::string& filename, InputFileCallbackPolicy policy, State& state) -> Containers::Optional> { + CORRADE_COMPARE(Containers::StringView{filename}, "file.dat"); + + if(policy == InputFileCallbackPolicy::LoadTemporary) { + ++state.loaded; + return Containers::arrayView(&state.data, 1); + } + + if(policy == InputFileCallbackPolicy::Close) { + ++state.closed; + return {}; + } + + CORRADE_FAIL("Unexpected policy" << policy); + return {}; + }, state); + + CORRADE_COMPARE(font.fileFontCount("file.dat"), 37); + CORRADE_COMPARE(state.loaded, 1); + CORRADE_COMPARE(state.closed, 1); + /* Verify that the behavior is the same also with a potential deprecated + overload for font 0 */ + /** @todo remove the instanced font ID once the deprecated APIs are gone, + supply a constant instead */ + CORRADE_VERIFY(font.openFile("file.dat", 13.0f, data.fontId)); + CORRADE_VERIFY(font.isOpened()); + CORRADE_COMPARE(state.loaded, 2); + CORRADE_COMPARE(state.closed, 2); + CORRADE_COMPARE(font.size(), 13.0f); + CORRADE_COMPARE(font.ascent(), 1.0f); + CORRADE_COMPARE(font.descent(), 2.0f); + CORRADE_COMPARE(font.lineHeight(), 3.0f); + CORRADE_COMPARE(font.glyphCount(), 15); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +void AbstractFontTest::setFileCallbackOpenFileAsDataDeprecated() { + /* Like setFileCallbackOpenFileAsData(), but implementing the deprecated + doOpenData() and verifying that it's correctly delegated to. There's no + deprecated doDataFontCount() variant so that part is omitted here. */ + struct: AbstractFont { FontFeatures doFeatures() const override { return FontFeature::OpenData; } bool doIsOpened() const override { return _opened; } void doClose() override { _opened = false; } + Properties doOpenFile(Containers::StringView, Float, UnsignedInt) override { + CORRADE_FAIL("This should not be called"); + return {}; + } Properties doOpenFile(Containers::StringView, Float) override { CORRADE_FAIL("This should not be called"); return {}; @@ -1048,18 +2600,92 @@ void AbstractFontTest::setFileCallbackOpenFileAsData() { CORRADE_COMPARE(font.glyphCount(), 15); } +void AbstractFontTest::setFileCallbackOpenFileAsDataDeprecatedNonZeroFontId() { + /* When openData() gets a non-zero font ID, it should not delegate to the + deprecated overload but fail directly */ + + struct: AbstractFont { + FontFeatures doFeatures() const override { return FontFeature::OpenData; } + bool doIsOpened() const override { return _opened; } + void doClose() override { _opened = false; } + + Properties doOpenFile(Containers::StringView, Float, UnsignedInt) override { + CORRADE_FAIL("This should not be called"); + return {}; + } + Properties doOpenFile(Containers::StringView, Float) override { + CORRADE_FAIL("This should not be called"); + return {}; + } + + Properties doOpenData(Containers::ArrayView, Float) override { + CORRADE_FAIL("This should not be called"); + return {}; + } + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + + private: + bool _opened = false; + } font; + + struct State { + bool loaded = false; + bool closed = false; + } state; + font.setFileCallback([](const std::string&, InputFileCallbackPolicy policy, State& state) -> Containers::Optional> { + if(policy == InputFileCallbackPolicy::LoadTemporary) { + state.loaded = true; + return Containers::ArrayView{}; + } + + if(policy == InputFileCallbackPolicy::Close) { + state.closed = true; + return {}; + } + + CORRADE_FAIL("Unexpected policy" << policy); + return {}; + }, state); + + Containers::String out; + Error redirectError{&out}; + CORRADE_VERIFY(!font.openFile("file.dat", 13.0f, 1)); + CORRADE_VERIFY(!font.isOpened()); + CORRADE_VERIFY(state.loaded); + CORRADE_VERIFY(state.closed); + CORRADE_COMPARE(out, "Text::AbstractFont::openData(): cannot open font at index 1\n"); +} +#endif + void AbstractFontTest::setFileCallbackOpenFileAsDataNotFound() { + auto&& data = OpenData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + struct: AbstractFont { FontFeatures doFeatures() const override { return FontFeature::OpenData; } bool doIsOpened() const override { return false; } void doClose() override {} - Properties doOpenFile(Containers::StringView, Float) override { + Containers::Optional doFileFontCount(Containers::StringView) override { CORRADE_FAIL("This should not be called"); return {}; } - Properties doOpenData(Containers::ArrayView, Float) override { + Containers::Optional doDataFontCount(Containers::ArrayView) override { + CORRADE_FAIL("This should not be called"); + return {}; + } + + Properties doOpenFile(Containers::StringView, Float, UnsignedInt) override { + CORRADE_FAIL("This should not be called"); + return {}; + } + + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { CORRADE_FAIL("This should not be called"); return {}; } @@ -1070,29 +2696,126 @@ void AbstractFontTest::setFileCallbackOpenFileAsDataNotFound() { Containers::Pointer doCreateShaper() override { return {}; } } font; - bool fileCallbackCalled = false; - font.setFileCallback([](const std::string&, InputFileCallbackPolicy policy, bool& fileCallbackCalled) -> Containers::Optional> { + Int fileCallbackCalled = 0; + font.setFileCallback([](const std::string&, InputFileCallbackPolicy policy, Int& fileCallbackCalled) -> Containers::Optional> { /* The callback should be only called to open the file, not to close it afterwards */ CORRADE_COMPARE(policy, InputFileCallbackPolicy::LoadTemporary); - fileCallbackCalled = true; + ++fileCallbackCalled; return {}; }, fileCallbackCalled); Containers::String out; Error redirectError{&out}; - CORRADE_VERIFY(!font.openFile("file.dat", 132.0f)); + CORRADE_VERIFY(!font.fileFontCount("file.dat")); + CORRADE_COMPARE(fileCallbackCalled, 1); + /* Verify that the behavior is the same also with a potential deprecated + overload for font 0 */ + /** @todo remove the instanced font ID once the deprecated APIs are gone, + supply a constant instead */ + CORRADE_VERIFY(!font.openFile("file.dat", 132.0f, data.fontId)); CORRADE_VERIFY(!font.isOpened()); - CORRADE_VERIFY(fileCallbackCalled); - CORRADE_COMPARE(out, "Text::AbstractFont::openFile(): cannot open file file.dat\n"); + CORRADE_COMPARE(fileCallbackCalled, 2); + CORRADE_COMPARE_AS(out, + "Text::AbstractFont::fileFontCount(): cannot open file file.dat\n" + "Text::AbstractFont::openFile(): cannot open file file.dat\n", + TestSuite::Compare::String); } void AbstractFontTest::setFileCallbackOpenFileAsDataFailed() { + auto&& data = OpenData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct: AbstractFont { + FontFeatures doFeatures() const override { return FontFeature::OpenData; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + Containers::Optional doFileFontCount(Containers::StringView) override { + CORRADE_FAIL("This should not be called"); + return {}; + } + + Containers::Optional doDataFontCount(Containers::ArrayView) override { + countCalled = true; + return {}; + } + + Properties doOpenFile(Containers::StringView, Float, UnsignedInt) override { + CORRADE_FAIL("This should not be called"); + return {}; + } + + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { + openCalled = true; + return {}; + } + + void doGlyphIdsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) override {} + Vector2 doGlyphSize(UnsignedInt) override { return {}; } + Vector2 doGlyphAdvance(UnsignedInt) override { return {}; } + Containers::Pointer doCreateShaper() override { return {}; } + + bool countCalled = false; + bool openCalled = false; + } font; + + struct State { + Int loaded = 0; + Int closed = 0; + } state; + font.setFileCallback([](const std::string&, InputFileCallbackPolicy policy, State& state) -> Containers::Optional> { + if(policy == InputFileCallbackPolicy::LoadTemporary) { + ++state.loaded; + return Containers::ArrayView{}; + } + + if(policy == InputFileCallbackPolicy::Close) { + ++state.closed; + return {}; + } + + CORRADE_FAIL("Unexpected policy" << policy); + return {}; + }, state); + + /* The implementation is expected to print an error message on its own */ + Containers::String out; + Error redirectError{&out}; + CORRADE_VERIFY(!font.fileFontCount("file.dat")); + CORRADE_VERIFY(font.countCalled); + CORRADE_VERIFY(!font.openCalled); + CORRADE_COMPARE(state.loaded, 1); + CORRADE_COMPARE(state.closed, 1); + /* Verify that the behavior is the same also with a potential deprecated + overload for font 0 */ + /** @todo remove the instanced font ID once the deprecated APIs are gone, + supply a constant instead */ + CORRADE_VERIFY(!font.openFile("file.dat", 132.0f, data.fontId)); + CORRADE_VERIFY(!font.isOpened()); + CORRADE_VERIFY(font.countCalled); + CORRADE_VERIFY(font.openCalled); + CORRADE_COMPARE(state.loaded, 2); + CORRADE_COMPARE(state.closed, 2); + CORRADE_COMPARE(out, ""); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +void AbstractFontTest::setFileCallbackOpenFileAsDataFailedDeprecated() { + /* Like setFileCallbackOpenFileAsDataFailed(), but implementing the + deprecated doOpenData() and verifying that it's correctly delegated to. + There's no deprecated doDataFontCount() variant so that part is omitted + here. */ + struct: AbstractFont { FontFeatures doFeatures() const override { return FontFeature::OpenData; } bool doIsOpened() const override { return false; } void doClose() override {} + Properties doOpenFile(Containers::StringView, Float, UnsignedInt) override { + CORRADE_FAIL("This should not be called"); + return {}; + } Properties doOpenFile(Containers::StringView, Float) override { CORRADE_FAIL("This should not be called"); return {}; @@ -1140,6 +2863,7 @@ void AbstractFontTest::setFileCallbackOpenFileAsDataFailed() { CORRADE_VERIFY(state.closed); CORRADE_COMPARE(out, ""); } +#endif void AbstractFontTest::properties() { struct: AbstractFont { @@ -1147,7 +2871,7 @@ void AbstractFontTest::properties() { bool doIsOpened() const override { return _opened; } void doClose() override {} - Properties doOpenData(Containers::ArrayView, Float size) override { + Properties doOpenData(Containers::ArrayView, Float size, UnsignedInt) override { _opened = true; return {size, 1.0f, 2.0f, 3.0f, 15}; } @@ -1206,7 +2930,7 @@ void AbstractFontTest::glyphId() { bool doIsOpened() const override { return _opened; } void doClose() override {} - Properties doOpenData(Containers::ArrayView, Float) override { + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { _opened = true; return {0.0f, 0.0f, 0.0f, 0.0f, 1280}; } @@ -1295,7 +3019,7 @@ void AbstractFontTest::glyphIdOutOfRange() { bool doIsOpened() const override { return _opened; } void doClose() override {} - Properties doOpenData(Containers::ArrayView, Float) override { + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { _opened = true; return {0.0f, 0.0f, 0.0f, 0.0f, 4}; } @@ -1334,7 +3058,7 @@ void AbstractFontTest::glyphName() { bool doIsOpened() const override { return _opened; } void doClose() override {} - Properties doOpenData(Containers::ArrayView, Float) override { + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { _opened = true; return {0.0f, 0.0f, 0.0f, 0.0f, 4}; } @@ -1369,7 +3093,7 @@ void AbstractFontTest::glyphNameNotImplemented() { bool doIsOpened() const override { return _opened; } void doClose() override {} - Properties doOpenData(Containers::ArrayView, Float) override { + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { _opened = true; return {0.0f, 0.0f, 0.0f, 0.0f, 4}; } @@ -1423,7 +3147,7 @@ void AbstractFontTest::glyphNameOutOfRange() { bool doIsOpened() const override { return _opened; } void doClose() override {} - Properties doOpenData(Containers::ArrayView, Float) override { + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { _opened = true; return {0.0f, 0.0f, 0.0f, 0.0f, 4}; } @@ -1456,7 +3180,7 @@ void AbstractFontTest::glyphSizeAdvance() { bool doIsOpened() const override { return _opened; } void doClose() override {} - Properties doOpenData(Containers::ArrayView, Float) override { + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { _opened = true; return {0.0f, 0.0f, 0.0f, 0.0f, 98}; } @@ -1505,7 +3229,7 @@ void AbstractFontTest::glyphSizeAdvanceOutOfRange() { bool doIsOpened() const override { return _opened; } void doClose() override {} - Properties doOpenData(Containers::ArrayView, Float) override { + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { _opened = true; return {0.0f, 0.0f, 0.0f, 0.0f, 3}; } @@ -1544,7 +3268,7 @@ void AbstractFontTest::fillGlyphCache() { bool doIsOpened() const override { return _opened; } void doClose() override {} - Properties doOpenData(Containers::ArrayView, Float) override { + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { _opened = true; return {0.0f, 0.0f, 0.0f, 0.0f, 17}; } @@ -1596,7 +3320,7 @@ void AbstractFontTest::fillGlyphCacheOutOfRange() { bool doIsOpened() const override { return _opened; } void doClose() override {} - Properties doOpenData(Containers::ArrayView, Float) override { + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { _opened = true; return {0.0f, 0.0f, 0.0f, 0.0f, 16}; } @@ -1633,7 +3357,7 @@ void AbstractFontTest::fillGlyphCacheNotUnique() { bool doIsOpened() const override { return _opened; } void doClose() override {} - Properties doOpenData(Containers::ArrayView, Float) override { + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { _opened = true; return {0.0f, 0.0f, 0.0f, 0.0f, 16}; } @@ -1666,7 +3390,7 @@ void AbstractFontTest::fillGlyphCacheFromString() { bool doIsOpened() const override { return _opened; } void doClose() override {} - Properties doOpenData(Containers::ArrayView, Float) override { + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { _opened = true; return {0.0f, 0.0f, 0.0f, 0.0f, 17}; } @@ -1734,7 +3458,7 @@ void AbstractFontTest::fillGlyphCacheFailed() { bool doIsOpened() const override { return _opened; } void doClose() override {} - Properties doOpenData(Containers::ArrayView, Float) override { + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { _opened = true; return {0.0f, 0.0f, 0.0f, 0.0f, 1}; } @@ -1808,7 +3532,7 @@ void AbstractFontTest::fillGlyphCacheNotImplemented() { bool doIsOpened() const override { return _opened; } void doClose() override {} - Properties doOpenData(Containers::ArrayView, Float) override { + Properties doOpenData(Containers::ArrayView, Float, UnsignedInt) override { _opened = true; return {0.0f, 0.0f, 0.0f, 0.0f, 1}; }