From 8ae70a038abb0667c953553f4a57b46b693af4c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 11 Jun 2026 18:36:14 +0200 Subject: [PATCH] Text: allow picking a concrete font ID in a collection. The AbstractFont::openFile() and openData() now get an additional argument specifying a font index to allow picking a concrete font index for example in a TTC collection. This index has to be specified upfront with no possibility to change it afterwards, because that's how both FreeType and stb_truetype work. Thus there are also new fileFontCount() and dataFontCount() APIs taking a filename / data argument, instead of this being a fontCount() query on an opened file. The implication from this is that -- unlike basically all other APIs in Trade and elsewhere -- specifying an out-of-bounds font index isn't an assertion (i.e., a programmer error) but rather a graceful failure, because requireing the user to first call fileFontCount() and then openFile() would mean having to open and parse the same file twice, which is undesirable overhead. While this isn't breaking for end users, as it's just a new optional argument to openFile() and openData(), it's a breaking change for plugin interfaces. The old doOpenFile() and doOpenData() interfaces are still there to help transitioning existing plugins, but are marked as deprecated. Delegating to those turned out to be quite involved, so there are many new tests verifying this compatibility code path. The plugin interface string version is bumped but in the next commits I'm making use of the doOpenData() / doOpenFile() API breakage to do more changes. Those won't result in further plugin interface changes, so this set of commits should be treated as a single indivisible change to the plugin interface. For this reason, uses of AbstractFont outside of AbstractFontTest (including the MagnumFont plugin) aren't adapted yet -- they pass tests as before, but compile only with deprecated APIs enabled. Co-authored-by: Andrew Snyder --- doc/changelog.dox | 12 +- doc/credits.dox | 2 + src/Magnum/Text/AbstractFont.cpp | 177 +- src/Magnum/Text/AbstractFont.h | 141 +- src/Magnum/Text/Test/AbstractFontTest.cpp | 2040 +++++++++++++++++++-- 5 files changed, 2201 insertions(+), 171 deletions(-) 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}; }