Browse Source

Text: implement file callbacks for AbstractFont.

Basically mirroring the API of Trade::AbstractImporter, as that proved
to be useful. The old crazy openSingleData() and openData(horribleStuff)
are deprecated and will be removed in a future release.

No backwards compatibility is provided for font plugins, these need to
be adapted.
audio-import
Vladimír Vondruš 7 years ago
parent
commit
344e00ce11
  1. 7
      doc/changelog.dox
  2. 87
      doc/snippets/MagnumText.cpp
  3. 118
      src/Magnum/Text/AbstractFont.cpp
  4. 277
      src/Magnum/Text/AbstractFont.h
  5. 589
      src/Magnum/Text/Test/AbstractFontTest.cpp
  6. 3
      src/Magnum/Trade/AbstractImporter.h
  7. 99
      src/MagnumPlugins/MagnumFont/MagnumFont.cpp
  8. 13
      src/MagnumPlugins/MagnumFont/MagnumFont.h
  9. 48
      src/MagnumPlugins/MagnumFont/Test/MagnumFontTest.cpp

7
doc/changelog.dox

@ -90,6 +90,8 @@ See also:
@ref GL. The @ref Text::Renderer, @ref Text::GlyphCache and
@ref Text::DistanceFieldGlyphCache classes are now built only if
`TARGET_GL` is enabled (done by default).
- New @ref Text::AbstractFont::setFileCallback() to allow opening multi-file
fonts with an API similar to @ref Trade::AbstractImporter
@subsection changelog-latest-changes Changes and improvements
@ -206,6 +208,11 @@ See also:
- @cpp DebugTools::ForceRendererOptions::scale() @ce is deprecated in favor
of @ref DebugTools::ForceRendererOptions::size(), as that's more consistent
with the documentation and the corresponding setter.
- @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<const char>, Float)
and @ref Text::AbstractFont::setFileCallback()
@section changelog-2019-01 2019.01

87
doc/snippets/MagnumText.cpp

@ -23,6 +23,12 @@
DEALINGS IN THE SOFTWARE.
*/
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/Optional.h>
#include <Corrade/PluginManager/Manager.h>
#include <Corrade/Utility/Directory.h>
#include "Magnum/FileCallback.h"
#include "Magnum/Shaders/Vector.h"
#include "Magnum/Text/AbstractFont.h"
#include "Magnum/Text/DistanceFieldGlyphCache.h"
@ -33,6 +39,87 @@ using namespace Magnum::Math::Literals;
int main() {
{
/* [AbstractFont-usage] */
PluginManager::Manager<Text::AbstractFont> manager;
Containers::Pointer<Text::AbstractFont> font =
manager.loadAndInstantiate("StbTrueTypeFont");
if(!font || !font->openFile("font.ttf", 16.0f))
Fatal{} << "Can't open font.ttf with StbTrueTypeFont";
Text::GlyphCache cache{Vector2i{512}};
font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789?!:;,. ");
/* [AbstractFont-usage] */
}
#if defined(CORRADE_TARGET_UNIX) || (defined(CORRADE_TARGET_WINDOWS) && !defined(CORRADE_TARGET_WINDOWS_RT))
{
Containers::Pointer<Text::AbstractFont> font;
/* [AbstractFont-usage-callbacks] */
struct Data {
std::unordered_map<std::string,
Containers::Array<const char, Utility::Directory::MapDeleter>> files;
} data;
font->setFileCallback([](const std::string& filename,
InputFileCallbackPolicy policy, Data& data)
-> Containers::Optional<Containers::ArrayView<const char>>
{
auto found = data.files.find(filename);
/* Discard the memory mapping, if not needed anymore */
if(policy == InputFileCallbackPolicy::Close) {
if(found != data.files.end()) data.files.erase(found);
return {};
}
/* Load if not there yet */
if(found == data.files.end()) found = data.files.emplace(
filename, Utility::Directory::mapRead(filename)).first;
return Containers::arrayView(found->second);
}, data);
font->openFile("magnum-font.conf", 13.0f);
/* [AbstractFont-usage-callbacks] */
}
#endif
{
Containers::Pointer<Text::AbstractFont> font;
/* [AbstractFont-setFileCallback] */
font->setFileCallback([](const std::string& filename,
InputFileCallbackPolicy, void*) {
Utility::Resource rs("data");
return Containers::optional(rs.getRaw(filename));
});
/* [AbstractFont-setFileCallback] */
}
{
Containers::Pointer<Text::AbstractFont> font;
/* [AbstractFont-setFileCallback-template] */
struct Data {
std::unordered_map<std::string, Containers::Array<char>> files;
} data;
font->setFileCallback([](const std::string& filename,
InputFileCallbackPolicy, Data& data)
-> Containers::Optional<Containers::ArrayView<const char>>
{
auto found = data.files.find(filename);
if(found == data.files.end()) {
if(!Utility::Directory::exists(filename))
return Containers::NullOpt;
found = data.files.emplace(filename, Utility::Directory::read(filename)).first;
}
return Containers::ArrayView<const char>{found->second};
}, data);
/* [AbstractFont-setFileCallback-template] */
}
{
/* [DistanceFieldGlyphCache-usage] */
Containers::Pointer<Text::AbstractFont> font;

118
src/Magnum/Text/AbstractFont.cpp

@ -26,9 +26,12 @@
#include "AbstractFont.h"
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/EnumSet.hpp>
#include <Corrade/Containers/Optional.h>
#include <Corrade/Utility/Directory.h>
#include <Corrade/Utility/Unicode.h>
#include "Magnum/FileCallback.h"
#include "Magnum/Math/Functions.h"
#include "Magnum/Text/AbstractGlyphCache.h"
@ -60,7 +63,18 @@ AbstractFont::AbstractFont() = default;
AbstractFont::AbstractFont(PluginManager::AbstractManager& manager, const std::string& plugin): AbstractPlugin{manager, plugin} {}
bool AbstractFont::openData(const std::vector<std::pair<std::string, Containers::ArrayView<const char>>>& data, const Float size) {
void AbstractFont::setFileCallback(Containers::Optional<Containers::ArrayView<const char>>(*callback)(const std::string&, InputFileCallbackPolicy, void*), void* const userData) {
CORRADE_ASSERT(!isOpened(), "Text::AbstractFont::setFileCallback(): can't be set while a font is opened", );
CORRADE_ASSERT(features() & (Feature::FileCallback|Feature::OpenData), "Text::AbstractFont::setFileCallback(): font plugin supports neither loading from data nor via callbacks, callbacks can't be used", );
_fileCallback = callback;
_fileCallbackUserData = userData;
doSetFileCallback(callback, userData);
}
void AbstractFont::doSetFileCallback(Containers::Optional<Containers::ArrayView<const char>>(*)(const std::string&, InputFileCallbackPolicy, void*), void*) {}
bool AbstractFont::openData(Containers::ArrayView<const char> data, const Float size) {
CORRADE_ASSERT(features() & Feature::OpenData,
"Text::AbstractFont::openData(): feature not supported", false);
@ -77,40 +91,62 @@ bool AbstractFont::openData(const std::vector<std::pair<std::string, Containers:
return isOpened();
}
auto AbstractFont::doOpenData(const std::vector<std::pair<std::string, Containers::ArrayView<const char>>>& data, const Float size) -> Metrics {
CORRADE_ASSERT(!(features() & Feature::MultiFile),
"Text::AbstractFont::openData(): feature advertised but not implemented", {});
CORRADE_ASSERT(data.size() == 1,
"Text::AbstractFont::openData(): expected just one file for single-file format", {});
auto AbstractFont::doOpenData(Containers::ArrayView<const char>, Float) -> Metrics {
CORRADE_ASSERT(false, "Text::AbstractFont::openData(): feature advertised but not implemented", {});
return {};
}
#ifdef MAGNUM_BUILD_DEPRECATED
bool AbstractFont::openData(const std::vector<std::pair<std::string, Containers::ArrayView<const char>>>& data, const Float size) {
close();
return doOpenSingleData(data[0].second, size);
}
bool AbstractFont::openSingleData(const Containers::ArrayView<const char> data, const Float size) {
CORRADE_ASSERT(features() & Feature::OpenData,
"Text::AbstractFont::openSingleData(): feature not supported", false);
CORRADE_ASSERT(!(features() & Feature::MultiFile),
"Text::AbstractFont::openSingleData(): the format is not single-file", false);
setFileCallback([](const std::string& file, InputFileCallbackPolicy, const std::vector<std::pair<std::string, Containers::ArrayView<const char>>>& data) -> Containers::Optional<Containers::ArrayView<const char>> {
for(auto&& i: data) if(i.first == file) return i.second;
return {};
}, data);
close();
const Metrics metrics = doOpenSingleData(data, size);
_size = metrics.size;
_ascent = metrics.ascent;
_descent = metrics.descent;
_lineHeight = metrics.lineHeight;
CORRADE_INTERNAL_ASSERT(isOpened() || (!_size && !_ascent && !_descent && !_lineHeight));
return isOpened();
return !data.empty() && openData(data.front().second, size);
}
auto AbstractFont::doOpenSingleData(Containers::ArrayView<const char>, Float) -> Metrics {
CORRADE_ASSERT(false, "Text::AbstractFont::openSingleData(): feature advertised but not implemented", {});
return {};
bool AbstractFont::openSingleData(const Containers::ArrayView<const char> data, const Float size) {
return openData(data, size);
}
#endif
bool AbstractFont::openFile(const std::string& filename, const Float size) {
close();
const Metrics metrics = doOpenFile(filename, size);
Metrics metrics;
/* If file loading callbacks are not set or the font implementation
supports handling them directly, call into the implementation */
if(!_fileCallback || (doFeatures() & Feature::FileCallback)) {
metrics = doOpenFile(filename, size);
/* 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
opening is finished. */
} else if(doFeatures() & Feature::OpenData) {
/* This needs to be duplicated here and in the doOpenFile()
implementation in order to support both following cases:
- plugins that don't support FileCallback but have their own
doOpenFile() implementation (callback needs to be used here,
because the base doOpenFile() 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 doOpenFile() implementation, because this branch is never
taken in that case) */
const Containers::Optional<Containers::ArrayView<const char>> data = _fileCallback(filename, InputFileCallbackPolicy::LoadTemporary, _fileCallbackUserData);
if(!data) {
Error() << "Text::AbstractFont::openFile(): cannot open file" << filename;
return isOpened();
}
metrics = doOpenData(*data, size);
_fileCallback(filename, InputFileCallbackPolicy::Close, _fileCallbackUserData);
/* Shouldn't get here, the assert is fired already in setFileCallback() */
} else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
_size = metrics.size;
_ascent = metrics.ascent;
_descent = metrics.descent;
@ -120,16 +156,32 @@ bool AbstractFont::openFile(const std::string& filename, const Float size) {
}
auto AbstractFont::doOpenFile(const std::string& filename, const Float size) -> Metrics {
CORRADE_ASSERT(features() & Feature::OpenData && !(features() & Feature::MultiFile),
"Text::AbstractFont::openFile(): not implemented", {});
/* Open file */
if(!Utility::Directory::exists(filename)) {
Error() << "Text::AbstractFont::openFile(): cannot open file" << filename;
return {};
CORRADE_ASSERT(features() & Feature::OpenData, "Text::AbstractFont::openFile(): not implemented", {});
Metrics metrics;
/* 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<Containers::ArrayView<const char>> data = _fileCallback(filename, InputFileCallbackPolicy::LoadTemporary, _fileCallbackUserData);
if(!data) {
Error() << "Text::AbstractFont::openFile(): cannot open file" << filename;
return {};
}
metrics = doOpenData(*data, size);
_fileCallback(filename, InputFileCallbackPolicy::Close, _fileCallbackUserData);
/* Otherwise open the file directly */
} else {
if(!Utility::Directory::exists(filename)) {
Error() << "Text::AbstractFont::openFile(): cannot open file" << filename;
return {};
}
metrics = doOpenData(Utility::Directory::read(filename), size);
}
return doOpenSingleData(Utility::Directory::read(filename), size);
return metrics;
}
void AbstractFont::close() {

277
src/Magnum/Text/AbstractFont.h

@ -48,37 +48,76 @@ namespace Magnum { namespace Text {
@brief Base for font plugins
Provides interface for opening fonts, filling glyph cache and layouting the
glyphs. See @ref plugins for more information and `*Font` classes in @ref Text
namespace for available font plugins.
glyphs.
@section Text-AbstractFont-usage Usage
First step is to open the font using @ref openData(), @ref openSingleData() or
@ref openFile(). Next step is to prerender all the glyphs which will be used in
text rendering later, see @ref GlyphCache for more information. See
@ref Renderer for information about text rendering.
Fonts are most commonly implemented as plugins. For example, loading a font
from the filesystem using the @ref StbTrueTypeFont plugin and prerendering all
needed glyphs can be done like this, completely with all error handling:
@snippet MagnumText.cpp AbstractFont-usage
See @ref plugins for more information about general plugin usage and `*Font`
classes in the @ref Text namespace for available font plugins. See
@ref GlyphCache for more information about glyph caches and @ref Renderer for
information about actual text rendering.
@subsection Text-AbstractFont-usage-callbacks Loading data from memory, using file callbacks
Besides loading data directly from the filesystem using @ref openFile() like
shown above, it's possible to use @ref openData() to import data from memory.
Note that the particular importer implementation must support
@ref Feature::OpenData for this method to work.
Some font formats consist of more than one file and in that case you may want
to intercept those references and load them in a custom way as well. For font
plugins that advertise support for this with @ref Feature::FileCallback this is
done by specifying a file loading callback using @ref setFileCallback(). The
callback gets a filename, @ref InputFileCallbackPolicy and an user
pointer as parameters; returns a non-owning view on the loaded data or a
@ref Corrade::Containers::NullOpt "Containers::NullOpt" to indicate the file
loading failed. For example, loading a memory-mapped font could look like
below. Note that the file loading callback affects @ref openFile() as
well --- you don't have to load the top-level file manually and pass it to
@ref openData(), any font plugin supporting the callback feature handles that
correctly.
@snippet MagnumText.cpp AbstractFont-usage-callbacks
For importers that don't support @ref Feature::FileCallback directly, the base
@ref openFile() implementation will use the file callback to pass the loaded
data through to @ref openData(), in case the importer supports at least
@ref Feature::OpenData. If the importer supports neither @ref Feature::FileCallback
nor @ref Feature::OpenData, @ref setFileCallback() doesn't allow the callbacks
to be set.
The input file callback signature is the same for @ref Text::AbstractFont and
@ref Trade::AbstractImporter to allow code reuse.
@section Text-AbstractFont-subclassing Subclassing
Plugin implements @ref doFeatures(), @ref doClose(), @ref doLayout(), either
@ref doCreateGlyphCache() or @ref doFillGlyphCache() and one or more of
The plugin implements @ref doFeatures(), @ref doClose(), @ref doLayout(),
either @ref doCreateGlyphCache() or @ref doFillGlyphCache() and one or more of
`doOpen*()` functions. See also @ref AbstractLayouter for more information.
You don't need to do most of the redundant sanity checks, these things are
checked by the implementation:
- Functions @ref doOpenData(), @ref doOpenSingleData() and @ref doOpenFile()
are called after the previous file was closed, function @ref doClose() is
called only if there is any file opened.
- Functions @ref doOpenData() and @ref doOpenSingleData() are called only if
@ref Feature::OpenData is supported.
- Functions @ref doOpenData() and @ref doOpenFile() are called after the
previous file was closed, function @ref doClose() is called only if there
is any file opened.
- Function @ref doOpenData() is called only if @ref Feature::OpenData is
supported.
- The @ref doSetFileCallback() function is called only if
@ref Feature::FileCallback is supported and there is no file opened.
- All `do*()` implementations working on opened file are called only if
there is any file opened.
*/
class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin {
public:
/**
* @brief Features supported by this importer
* @brief Features supported by this font implementation
*
* @see @ref Features, @ref features()
*/
@ -86,11 +125,25 @@ class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin {
/** Opening fonts from raw data using @ref openData() */
OpenData = 1 << 0,
/**
* Specifying callbacks for loading additional files referenced
* from the main file using @ref setFileCallback(). If the font
* doesn't expose this feature, the format is either single-file or
* loading via callbacks is not supported.
*
* See @ref Text-AbstractFont-usage-callbacks and particular font
* plugin documentation for more information.
*/
FileCallback = 1 << 1,
#ifdef MAGNUM_BUILD_DEPRECATED
/**
* The format is multi-file, thus @ref openSingleData() convenience
* function cannot be used.
* @deprecated Obsolete, use file callbacks instead.
*/
MultiFile = 1 << 1,
MultiFile CORRADE_DEPRECATED_ENUM("obsolete, use file callbacks instead") = FileCallback,
#endif
/**
* The font contains prepared glyph cache.
@ -100,7 +153,7 @@ class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin {
PreparedGlyphCache = 1 << 2
};
/** @brief Set of features supported by this importer */
/** @brief Set of features supported by this font implementation */
typedef Containers::EnumSet<Feature> Features;
/**
@ -138,31 +191,118 @@ class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin {
/** @brief Features supported by this font */
Features features() const { return doFeatures(); }
/**
* @brief File opening callback function
*
* @see @ref Text-AbstractFont-usage-callbacks
*/
auto fileCallback() const -> Containers::Optional<Containers::ArrayView<const char>>(*)(const std::string&, InputFileCallbackPolicy, void*) { return _fileCallback; }
/**
* @brief File opening callback user data
*
* @see @ref Text-AbstractFont-usage-callbacks
*/
void* fileCallbackUserData() const { return _fileCallbackUserData; }
/**
* @brief Set file opening callback
*
* In case the font plugin supports @ref Feature::FileCallback, files
* opened through @ref openFile() will be loaded through the provided
* callback. Besides that, all external files referenced by the
* top-level file will be loaded through the callback function as well,
* usually on demand. The callback function gets a filename,
* @ref InputFileCallbackPolicy and the @p userData pointer as input
* and returns a non-owning view on the loaded data as output or a
* @ref Corrade::Containers::NullOpt if loading failed --- because
* empty files might also be valid in some circumstances, @cpp nullptr @ce
* can't be used to indicate a failure.
*
* In case the font plugin doesn't support @ref Feature::FileCallback but
* supports at least @ref Feature::OpenData, a file opened through
* @ref openFile() will be internally loaded through the provided
* callback and then passed to @ref openData(). First the file is
* loaded with @ref InputFileCallbackPolicy::LoadTemporary passed to
* the callback, then the returned memory view is passed to
* @ref openData() (sidestepping the potential @ref openFile()
* implementation of that particular font plugin) and after that the
* callback is called again with @ref InputFileCallbackPolicy::Close
* because the semantics of @ref openData() don't require the data to
* be alive after. In case you need a different behavior, use
* @ref openData() directly.
*
* In case @p callback is @cpp nullptr @ce, the current callback (if
* any) is reset. This function expects that the font plugin supports
* either @ref Feature::FileCallback or @ref Feature::OpenData. If an
* font plugin supports neither, callbacks can't be used.
*
* It's expected that this function is called *before* a file is
* opened. It's also expected that the loaded data are kept in scope
* for as long as the font plugin needs them, based on the value of
* @ref InputFileCallbackPolicy. Documentation of particular importers
* provides more information about the expected callback behavior.
*
* Following is an example of setting up a file loading callback for
* fetching compiled-in resources from @ref Corrade::Utility::Resource.
* See the overload below for a more convenient type-safe way to pass
* the user data pointer.
*
* @snippet MagnumText.cpp AbstractFont-setFileCallback
*
* @see @ref Text-AbstractFont-usage-callbacks
*/
void setFileCallback(Containers::Optional<Containers::ArrayView<const char>>(*callback)(const std::string&, InputFileCallbackPolicy, void*), void* userData = nullptr);
/**
* @brief Set file opening callback
*
* Equivalent to calling the above with a lambda wrapper that casts
* @cpp void* @ce back to @cpp T* @ce and dereferences it in order to
* pass it to @p callback. Example usage:
*
* @snippet MagnumText.cpp AbstractFont-setFileCallback-template
*
* @see @ref Text-AbstractFont-usage-callbacks
*/
#ifdef DOXYGEN_GENERATING_OUTPUT
template<class T> void setFileCallback(Containers::Optional<Containers::ArrayView<const char>>(*callback)(const std::string&, InputFileCallbackPolicy, T&), T& userData);
#else
/* Otherwise the user would be forced to use the + operator to convert
a lambda to a function pointer and (besides being weird and
annoying) it's also not portable because it doesn't work on MSVC
2015 and older versions of MSVC 2017. */
template<class Callback, class T> void setFileCallback(Callback callback, T& userData);
#endif
/** @brief Whether any file is opened */
bool isOpened() const { return doIsOpened(); }
/**
* @brief Open font from raw data
* @param data Pairs of filename and file data
* @param data File data
* @param size Font size
*
* Closes previous file, if it was opened, and tries to open given
* file. Available only if @ref Feature::OpenData is supported. Returns
* @cpp true @ce on success, @cpp false @ce otherwise.
* @see @ref features(), @ref openFile()
*/
bool openData(const std::vector<std::pair<std::string, Containers::ArrayView<const char>>>& data, Float size);
bool openData(Containers::ArrayView<const char> data, Float size);
/**
* @brief Open font from single data
* @param data File data
* @param size Font size
*
* Closes previous file, if it was opened, and tries to open given
* file. Available only if @ref Feature::OpenData is supported and the
* plugin doesn't have @ref Feature::MultiFile. Returns @cpp true @ce
* on success, @cpp false @ce otherwise.
#ifdef MAGNUM_BUILD_DEPRECATED
/** @brief @copybrief openData(Containers::ArrayView<const char>, Float)
* @deprecated Use @ref openFile() with @ref setFileCallback() for
* opening multi-file fonts instead.
*/
bool openSingleData(Containers::ArrayView<const char> data, Float size);
CORRADE_DEPRECATED("use openFile() with setFileCallback() for opening multi-file fonts instead") bool openData(const std::vector<std::pair<std::string, Containers::ArrayView<const char>>>& data, Float size);
/** @brief @copybrief openData(Containers::ArrayView<const char>, Float)
* @deprecated Use @ref openData(Containers::ArrayView<const char>, Float)
* instead.
*/
CORRADE_DEPRECATED("use openData(Containers::ArrayView<const char>, Float) instead") bool openSingleData(Containers::ArrayView<const char> data, Float size);
#endif
/**
* @brief Open font from file
@ -271,7 +411,7 @@ class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin {
/**
* @brief Font metrics
*
* @see @ref doOpenFile(), @ref doOpenData(), @ref doOpenSingleData()
* @see @ref doOpenFile(), @ref doOpenData()
*/
struct Metrics {
#ifndef DOXYGEN_GENERATING_OUTPUT
@ -305,39 +445,50 @@ class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin {
Float lineHeight;
};
private:
/** @brief Implementation for @ref features() */
virtual Features doFeatures() const = 0;
/** @brief Implementation for @ref isOpened() */
virtual bool doIsOpened() const = 0;
protected:
/**
* @brief Implementation for @ref openData()
* @brief Implementation for @ref openFile()
*
* Return metrics of opened font on successful opening, zeros
* otherwise. If the plugin doesn't have @ref Feature::MultiFile,
* default implementation calls @ref doOpenSingleData().
* otherwise. If @ref Feature::OpenData is supported, default
* implementation opens the file and calls @ref doOpenData() with its
* contents. It is allowed to call this function from your
* @ref doOpenFile() implementation --- in particular, this
* implementation will also correctly handle callbacks set through
* @ref setFileCallback().
*
* This function is not called when file callbacks are set through
* @ref setFileCallback() and @ref Feature::FileCallback is not
* supported --- instead, file is loaded though the callback and data
* passed through to @ref doOpenData().
*/
virtual Metrics doOpenData(const std::vector<std::pair<std::string, Containers::ArrayView<const char>>>& data, Float size);
virtual Metrics doOpenFile(const std::string& filename, Float size);
private:
/** @brief Implementation for @ref features() */
virtual Features doFeatures() const = 0;
/**
* @brief Implementation for @ref openSingleData()
* @brief Implementation for @ref setFileCallback()
*
* Return metrics of opened font on successful opening, zeros
* otherwise.
* Useful when the font plugin needs to modify some internal state on
* callback setup. Default implementation does nothing and this
* function doesn't need to be implemented --- the callback function
* and user data pointer are available through @ref fileCallback() and
* @ref fileCallbackUserData().
*/
virtual Metrics doOpenSingleData(Containers::ArrayView<const char> data, Float size);
virtual void doSetFileCallback(Containers::Optional<Containers::ArrayView<const char>>(*callback)(const std::string&, InputFileCallbackPolicy, void*), void* userData);
/** @brief Implementation for @ref isOpened() */
virtual bool doIsOpened() const = 0;
/**
* @brief Implementation for @ref openFile()
* @brief Implementation for @ref openData()
*
* Return metrics of opened font on successful opening, zeros
* otherwise. If @ref Feature::OpenData is supported and the plugin
* doesn't have @ref Feature::MultiFile, default implementation opens
* the file and calls @ref doOpenSingleData() with its contents.
* otherwise.
*/
virtual Metrics doOpenFile(const std::string& filename, Float size);
virtual Metrics doOpenData(Containers::ArrayView<const char> data, Float size);
/** @brief Implementation for @ref close() */
virtual void doClose() = 0;
@ -362,9 +513,16 @@ class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin {
/** @brief Implementation for @ref layout() */
virtual Containers::Pointer<AbstractLayouter> doLayout(const AbstractGlyphCache& cache, Float size, const std::string& text) = 0;
#ifdef DOXYGEN_GENERATING_OUTPUT
private:
#endif
Containers::Optional<Containers::ArrayView<const char>>(*_fileCallback)(const std::string&, InputFileCallbackPolicy, void*){};
void* _fileCallbackUserData{};
/* Used by the templated version only */
struct FileCallbackTemplate {
void(*callback)();
const void* userData;
/* GCC 4.8 complains loudly about missing initializers otherwise */
} _fileCallbackTemplate{nullptr, nullptr};
Float _size{}, _ascent{}, _descent{}, _lineHeight{};
};
@ -439,6 +597,21 @@ class MAGNUM_TEXT_EXPORT AbstractLayouter {
UnsignedInt _glyphCount;
};
#ifndef DOXYGEN_GENERATING_OUTPUT
template<class Callback, class T> void AbstractFont::setFileCallback(Callback callback, T& userData) {
/* Don't try to wrap a null function pointer. Need to cast first because
MSVC (even 2017) can't apply ! to a lambda. Ugh. */
const auto callbackPtr = static_cast<Containers::Optional<Containers::ArrayView<const char>>(*)(const std::string&, InputFileCallbackPolicy, T&)>(callback);
if(!callbackPtr) return setFileCallback(nullptr);
_fileCallbackTemplate = { reinterpret_cast<void(*)()>(callbackPtr), static_cast<const void*>(&userData) };
setFileCallback([](const std::string& filename, const InputFileCallbackPolicy flags, void* const userData) {
auto& s = *reinterpret_cast<FileCallbackTemplate*>(userData);
return reinterpret_cast<Containers::Optional<Containers::ArrayView<const char>>(*)(const std::string&, InputFileCallbackPolicy, T&)>(s.callback)(filename, flags, *static_cast<T*>(const_cast<void*>(s.userData)));
}, &_fileCallbackTemplate);
}
#endif
}}
#endif

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

@ -25,9 +25,11 @@
#include <sstream>
#include <Corrade/Containers/ArrayView.h>
#include <Corrade/Containers/Optional.h>
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/Utility/Directory.h>
#include "Magnum/FileCallback.h"
#include "Magnum/Math/Vector2.h"
#include "Magnum/Text/AbstractFont.h"
#include "Magnum/Text/AbstractGlyphCache.h"
@ -39,8 +41,7 @@ namespace Magnum { namespace Text { namespace Test { namespace {
struct AbstractFontTest: TestSuite::Tester {
explicit AbstractFontTest();
void openSingleData();
void openData();
void openFileAsData();
void openFileAsDataNotFound();
@ -48,6 +49,24 @@ struct AbstractFontTest: TestSuite::Tester {
void openDataNotSupported();
void openDataNotImplemented();
#ifdef MAGNUM_BUILD_DEPRECATED
void openSingleDataDeprecated();
void openMultiDataDeprecated();
#endif
void setFileCallback();
void setFileCallbackTemplate();
void setFileCallbackTemplateNull();
void setFileCallbackTemplateConst();
void setFileCallbackFileOpened();
void setFileCallbackNotImplemented();
void setFileCallbackNotSupported();
void setFileCallbackOpenFileDirectly();
void setFileCallbackOpenFileThroughBaseImplementation();
void setFileCallbackOpenFileThroughBaseImplementationFailed();
void setFileCallbackOpenFileAsData();
void setFileCallbackOpenFileAsDataFailed();
void glyphId();
void glyphIdNoFont();
@ -69,8 +88,7 @@ struct AbstractFontTest: TestSuite::Tester {
};
AbstractFontTest::AbstractFontTest() {
addTests({&AbstractFontTest::openSingleData,
addTests({&AbstractFontTest::openData,
&AbstractFontTest::openFileAsData,
&AbstractFontTest::openFileAsDataNotFound,
@ -78,6 +96,24 @@ AbstractFontTest::AbstractFontTest() {
&AbstractFontTest::openDataNotSupported,
&AbstractFontTest::openDataNotImplemented,
#ifdef MAGNUM_BUILD_DEPRECATED
&AbstractFontTest::openSingleDataDeprecated,
&AbstractFontTest::openMultiDataDeprecated,
#endif
&AbstractFontTest::setFileCallback,
&AbstractFontTest::setFileCallbackTemplate,
&AbstractFontTest::setFileCallbackTemplateNull,
&AbstractFontTest::setFileCallbackTemplateConst,
&AbstractFontTest::setFileCallbackFileOpened,
&AbstractFontTest::setFileCallbackNotImplemented,
&AbstractFontTest::setFileCallbackNotSupported,
&AbstractFontTest::setFileCallbackOpenFileDirectly,
&AbstractFontTest::setFileCallbackOpenFileThroughBaseImplementation,
&AbstractFontTest::setFileCallbackOpenFileThroughBaseImplementationFailed,
&AbstractFontTest::setFileCallbackOpenFileAsData,
&AbstractFontTest::setFileCallbackOpenFileAsDataFailed,
&AbstractFontTest::glyphId,
&AbstractFontTest::glyphIdNoFont,
@ -98,45 +134,64 @@ AbstractFontTest::AbstractFontTest() {
&AbstractFontTest::createGlyphCacheNoFont});
}
class SingleDataFont: public Text::AbstractFont {
public:
explicit SingleDataFont(): opened(false) {}
void AbstractFontTest::openData() {
struct: AbstractFont {
Features doFeatures() const override { return Feature::OpenData; }
bool doIsOpened() const override { return opened; }
bool doIsOpened() const override { return _opened; }
void doClose() override {}
Metrics doOpenSingleData(const Containers::ArrayView<const char> data, Float) override {
opened = (data.size() == 1 && data[0] == '\xa5');
return {};
Metrics doOpenData(const Containers::ArrayView<const char> data, Float size) override {
_opened = (data.size() == 1 && data[0] == '\xa5');
return {size, 1.0f, 2.0f, 3.0f};
}
UnsignedInt doGlyphId(char32_t) override { return 0; }
UnsignedInt doGlyphId(char32_t) override { return {}; }
Vector2 doGlyphAdvance(UnsignedInt) override { return {}; }
Containers::Pointer<AbstractLayouter> doLayout(const AbstractGlyphCache&, Float, const std::string&) override {
return nullptr;
}
bool opened;
};
bool _opened = false;
} font;
void AbstractFontTest::openSingleData() {
/* doOpenData() should call doOpenSingleData() */
SingleDataFont font;
const char data[] = {'\xa5'};
CORRADE_VERIFY(!font.isOpened());
font.openData({{{}, data}}, 3.0f);
const char a5 = '\xa5';
font.openData({&a5, 1}, 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);
}
void AbstractFontTest::openFileAsData() {
/* doOpenFile() should call doOpenSingleData() */
SingleDataFont font;
struct: AbstractFont {
Features doFeatures() const override { return Feature::OpenData; }
bool doIsOpened() const override { return _opened; }
void doClose() override {}
Metrics doOpenData(const Containers::ArrayView<const char> data, Float size) override {
_opened = (data.size() == 1 && data[0] == '\xa5');
return {size, 1.0f, 2.0f, 3.0f};
}
UnsignedInt doGlyphId(char32_t) override { return {}; }
Vector2 doGlyphAdvance(UnsignedInt) override { return {}; }
Containers::Pointer<AbstractLayouter> doLayout(const AbstractGlyphCache&, Float, const std::string&) override {
return nullptr;
}
bool _opened = false;
} font;
/* doOpenFile() should call doOpenData() */
CORRADE_VERIFY(!font.isOpened());
font.openFile(Utility::Directory::join(TEXT_TEST_DIR, "data.bin"), 3.0f);
font.openFile(Utility::Directory::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);
}
void AbstractFontTest::openFileAsDataNotFound() {
@ -194,9 +249,7 @@ void AbstractFontTest::openDataNotSupported() {
std::ostringstream out;
Error redirectError{&out};
/** @todo replace this with nullptr once multi-file support is done
properly via callbacks */
font.openData({}, 34.0f);
font.openData(nullptr, 34.0f);
CORRADE_COMPARE(out.str(), "Text::AbstractFont::openData(): feature not supported\n");
}
@ -215,10 +268,482 @@ void AbstractFontTest::openDataNotImplemented() {
std::ostringstream out;
Error redirectError{&out};
/** @todo replace this with nullptr and openSingleData() once multi-file
support is done properly via callbacks */
font.openData({{}}, 34.0f);
CORRADE_COMPARE(out.str(), "Text::AbstractFont::openSingleData(): feature advertised but not implemented\n");
font.openData(nullptr, 34.0f);
CORRADE_COMPARE(out.str(), "Text::AbstractFont::openData(): feature advertised but not implemented\n");
}
#ifdef MAGNUM_BUILD_DEPRECATED
void AbstractFontTest::openSingleDataDeprecated() {
struct: AbstractFont {
Features doFeatures() const override { return Feature::OpenData; }
bool doIsOpened() const override { return _opened; }
void doClose() override {}
Metrics doOpenData(const Containers::ArrayView<const char> data, Float size) override {
_opened = (data.size() == 1 && data[0] == '\xa5');
return {size, 1.0f, 2.0f, 3.0f};
}
UnsignedInt doGlyphId(char32_t) override { return {}; }
Vector2 doGlyphAdvance(UnsignedInt) override { return {}; }
Containers::Pointer<AbstractLayouter> doLayout(const AbstractGlyphCache&, Float, const std::string&) override {
return nullptr;
}
bool _opened = false;
} font;
CORRADE_VERIFY(!font.isOpened());
const char a5 = '\xa5';
CORRADE_IGNORE_DEPRECATED_PUSH
font.openSingleData({&a5, 1}, 13.0f);
CORRADE_IGNORE_DEPRECATED_POP
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);
}
void AbstractFontTest::openMultiDataDeprecated() {
struct: AbstractFont {
Features doFeatures() const override { return Feature::OpenData; }
bool doIsOpened() const override { return _opened; }
void doClose() override {}
Metrics doOpenData(const Containers::ArrayView<const char> data, Float size) override {
if(!fileCallback()) return {};
Containers::Optional<Containers::ArrayView<const char>> dataExt = fileCallback()("data.ext", InputFileCallbackPolicy::LoadPernament, fileCallbackUserData());
_opened = (data.size() == 1 && data[0] == '\xa5' && dataExt &&
dataExt->size() == 2 && (*dataExt)[1] == '\xee');
return {size, 1.0f, 2.0f, 3.0f};
}
UnsignedInt doGlyphId(char32_t) override { return {}; }
Vector2 doGlyphAdvance(UnsignedInt) override { return {}; }
Containers::Pointer<AbstractLayouter> doLayout(const AbstractGlyphCache&, Float, const std::string&) override {
return nullptr;
}
bool _opened = false;
} font;
CORRADE_VERIFY(!font.isOpened());
const char a5 = '\xa5';
const char ee[] = { '\xff', '\xee' };
CORRADE_IGNORE_DEPRECATED_PUSH
font.openData({{"data.bin", {&a5, 1}},
{"data.ext", ee}}, 13.0f);
CORRADE_IGNORE_DEPRECATED_POP
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);
}
#endif
void AbstractFontTest::setFileCallback() {
struct: AbstractFont {
Features doFeatures() const override { return Feature::OpenData|Feature::FileCallback; }
bool doIsOpened() const override { return false; }
void doClose() override {}
void doSetFileCallback(Containers::Optional<Containers::ArrayView<const char>>(*)(const std::string&, InputFileCallbackPolicy, void*), void* userData) override {
*static_cast<int*>(userData) = 1337;
}
UnsignedInt doGlyphId(char32_t) override { return {}; }
Vector2 doGlyphAdvance(UnsignedInt) override { return {}; }
Containers::Pointer<AbstractLayouter> doLayout(const AbstractGlyphCache&, Float, const std::string&) override {
return nullptr;
}
} font;
int a = 0;
auto lambda = [](const std::string&, InputFileCallbackPolicy, void*) {
return Containers::Optional<Containers::ArrayView<const char>>{};
};
font.setFileCallback(lambda, &a);
CORRADE_COMPARE(font.fileCallback(), lambda);
CORRADE_COMPARE(font.fileCallbackUserData(), &a);
CORRADE_COMPARE(a, 1337);
}
void AbstractFontTest::setFileCallbackTemplate() {
struct: AbstractFont {
Features doFeatures() const override { return Feature::OpenData|Feature::FileCallback; }
bool doIsOpened() const override { return false; }
void doClose() override {}
void doSetFileCallback(Containers::Optional<Containers::ArrayView<const char>>(*)(const std::string&, InputFileCallbackPolicy, void*), void*) override {
called = true;
}
UnsignedInt doGlyphId(char32_t) override { return {}; }
Vector2 doGlyphAdvance(UnsignedInt) override { return {}; }
Containers::Pointer<AbstractLayouter> doLayout(const AbstractGlyphCache&, Float, const std::string&) override {
return nullptr;
}
bool called = false;
} font;
int a = 0;
auto lambda = [](const std::string&, InputFileCallbackPolicy, int&) {
return Containers::Optional<Containers::ArrayView<const char>>{};
};
font.setFileCallback(lambda, a);
CORRADE_VERIFY(font.fileCallback());
CORRADE_VERIFY(font.fileCallbackUserData());
CORRADE_VERIFY(font.called);
/* The data pointers should be wrapped, thus not the same */
CORRADE_VERIFY(reinterpret_cast<void(*)()>(font.fileCallback()) != reinterpret_cast<void(*)()>(static_cast<Containers::Optional<Containers::ArrayView<const char>>(*)(const std::string&, InputFileCallbackPolicy, int&)>(lambda)));
CORRADE_VERIFY(font.fileCallbackUserData() != &a);
}
void AbstractFontTest::setFileCallbackTemplateNull() {
struct: AbstractFont {
Features doFeatures() const override { return Feature::OpenData|Feature::FileCallback; }
bool doIsOpened() const override { return false; }
void doClose() override {}
void doSetFileCallback(Containers::Optional<Containers::ArrayView<const char>>(*callback)(const std::string&, InputFileCallbackPolicy, void*), void* userData) override {
called = !callback && !userData;
}
UnsignedInt doGlyphId(char32_t) override { return {}; }
Vector2 doGlyphAdvance(UnsignedInt) override { return {}; }
Containers::Pointer<AbstractLayouter> doLayout(const AbstractGlyphCache&, Float, const std::string&) override {
return nullptr;
}
bool called = false;
} font;
int a = 0;
font.setFileCallback(static_cast<Containers::Optional<Containers::ArrayView<const char>>(*)(const std::string&, InputFileCallbackPolicy, int&)>(nullptr), a);
CORRADE_VERIFY(!font.fileCallback());
CORRADE_VERIFY(!font.fileCallbackUserData());
CORRADE_VERIFY(font.called);
}
void AbstractFontTest::setFileCallbackTemplateConst() {
struct: AbstractFont {
Features doFeatures() const override { return Feature::OpenData|Feature::FileCallback; }
bool doIsOpened() const override { return false; }
void doClose() override {}
void doSetFileCallback(Containers::Optional<Containers::ArrayView<const char>>(*)(const std::string&, InputFileCallbackPolicy, void*), void*) override {
called = true;
}
UnsignedInt doGlyphId(char32_t) override { return {}; }
Vector2 doGlyphAdvance(UnsignedInt) override { return {}; }
Containers::Pointer<AbstractLayouter> doLayout(const AbstractGlyphCache&, Float, const std::string&) override {
return nullptr;
}
bool called = false;
} font;
const int a = 0;
auto lambda = [](const std::string&, InputFileCallbackPolicy, const int&) {
return Containers::Optional<Containers::ArrayView<const char>>{};
};
font.setFileCallback(lambda, a);
CORRADE_VERIFY(font.fileCallback());
CORRADE_VERIFY(font.fileCallbackUserData());
CORRADE_VERIFY(font.called);
}
void AbstractFontTest::setFileCallbackFileOpened() {
struct: AbstractFont {
Features doFeatures() const override { return {}; }
bool doIsOpened() const override { return true; }
void doClose() override {}
UnsignedInt doGlyphId(char32_t) override { return {}; }
Vector2 doGlyphAdvance(UnsignedInt) override { return {}; }
Containers::Pointer<AbstractLayouter> doLayout(const AbstractGlyphCache&, Float, const std::string&) override {
return nullptr;
}
} font;
std::ostringstream out;
Error redirectError{&out};
font.setFileCallback([](const std::string&, InputFileCallbackPolicy, void*) {
return Containers::Optional<Containers::ArrayView<const char>>{};
});
CORRADE_COMPARE(out.str(), "Text::AbstractFont::setFileCallback(): can't be set while a font is opened\n");
}
void AbstractFontTest::setFileCallbackNotImplemented() {
struct: AbstractFont {
Features doFeatures() const override { return Feature::FileCallback; }
bool doIsOpened() const override { return false; }
void doClose() override {}
UnsignedInt doGlyphId(char32_t) override { return {}; }
Vector2 doGlyphAdvance(UnsignedInt) override { return {}; }
Containers::Pointer<AbstractLayouter> doLayout(const AbstractGlyphCache&, Float, const std::string&) override {
return nullptr;
}
} font;
int a;
auto lambda = [](const std::string&, InputFileCallbackPolicy, void*) {
return Containers::Optional<Containers::ArrayView<const char>>{};
};
font.setFileCallback(lambda, &a);
CORRADE_COMPARE(font.fileCallback(), lambda);
CORRADE_COMPARE(font.fileCallbackUserData(), &a);
/* Should just work, no need to implement the function */
}
void AbstractFontTest::setFileCallbackNotSupported() {
struct: AbstractFont {
Features doFeatures() const override { return {}; }
bool doIsOpened() const override { return false; }
void doClose() override {}
UnsignedInt doGlyphId(char32_t) override { return {}; }
Vector2 doGlyphAdvance(UnsignedInt) override { return {}; }
Containers::Pointer<AbstractLayouter> doLayout(const AbstractGlyphCache&, Float, const std::string&) override {
return nullptr;
}
} font;
std::ostringstream out;
Error redirectError{&out};
int a;
font.setFileCallback([](const std::string&, InputFileCallbackPolicy, void*) {
return Containers::Optional<Containers::ArrayView<const char>>{};
}, &a);
CORRADE_COMPARE(out.str(), "Text::AbstractFont::setFileCallback(): font plugin supports neither loading from data nor via callbacks, callbacks can't be used\n");
}
void AbstractFontTest::setFileCallbackOpenFileDirectly() {
struct: AbstractFont {
Features doFeatures() const override { return Feature::FileCallback|Feature::OpenData; }
bool doIsOpened() const override { return _opened; }
void doClose() override { _opened = false; }
Metrics doOpenFile(const std::string& filename, Float size) override {
/* Called because FileCallback is supported */
_opened = filename == "file.dat" && fileCallback() && fileCallbackUserData();
return {size, 1.0f, 2.0f, 3.0f};
}
Metrics doOpenData(Containers::ArrayView<const char>, Float) override {
/* Shouldn't be called because FileCallback is supported */
openDataCalledNotSureWhy = true;
return {};
}
UnsignedInt doGlyphId(char32_t) override { return {}; }
Vector2 doGlyphAdvance(UnsignedInt) override { return {}; }
Containers::Pointer<AbstractLayouter> doLayout(const AbstractGlyphCache&, Float, const std::string&) override {
return nullptr;
}
bool _opened = false;
bool openDataCalledNotSureWhy = false;
} font;
bool calledNotSureWhy = false;
font.setFileCallback([](const std::string&, InputFileCallbackPolicy, bool& calledNotSureWhy) -> Containers::Optional<Containers::ArrayView<const char>> {
calledNotSureWhy = true;
return {};
}, calledNotSureWhy);
CORRADE_VERIFY(font.openFile("file.dat", 42.0f));
CORRADE_VERIFY(!calledNotSureWhy);
CORRADE_VERIFY(!font.openDataCalledNotSureWhy);
CORRADE_COMPARE(font.size(), 42.0f);
CORRADE_COMPARE(font.ascent(), 1.0f);
CORRADE_COMPARE(font.descent(), 2.0f);
CORRADE_COMPARE(font.lineHeight(), 3.0f);
}
void AbstractFontTest::setFileCallbackOpenFileThroughBaseImplementation() {
struct: AbstractFont {
Features doFeatures() const override { return Feature::FileCallback|Feature::OpenData; }
bool doIsOpened() const override { return _opened; }
void doClose() override { _opened = false; }
Metrics doOpenFile(const std::string& filename, Float size) override {
openFileCalled = filename == "file.dat" && fileCallback() && fileCallbackUserData();
return AbstractFont::doOpenFile(filename, size);
}
Metrics doOpenData(Containers::ArrayView<const char> data, Float size) override {
_opened = (data.size() == 1 && data[0] == '\xb0');
return {size, 1.0f, 2.0f, 3.0f};
}
UnsignedInt doGlyphId(char32_t) override { return {}; }
Vector2 doGlyphAdvance(UnsignedInt) override { return {}; }
Containers::Pointer<AbstractLayouter> doLayout(const AbstractGlyphCache&, Float, const std::string&) override {
return nullptr;
}
bool _opened = false;
bool openFileCalled = false;
} font;
struct State {
const char data = '\xb0';
bool loaded = false;
bool closed = false;
bool calledNotSureWhy = false;
} state;
font.setFileCallback([](const std::string& filename, InputFileCallbackPolicy policy, State& state) -> Containers::Optional<Containers::ArrayView<const char>> {
if(filename == "file.dat" && policy == InputFileCallbackPolicy::LoadTemporary) {
state.loaded = true;
return Containers::arrayView(&state.data, 1);
}
if(filename == "file.dat" && policy == InputFileCallbackPolicy::Close) {
state.closed = true;
return {};
}
state.calledNotSureWhy = true;
return {};
}, state);
CORRADE_VERIFY(font.openFile("file.dat", 42.0f));
CORRADE_VERIFY(font.openFileCalled);
CORRADE_VERIFY(state.loaded);
CORRADE_VERIFY(state.closed);
CORRADE_VERIFY(!state.calledNotSureWhy);
CORRADE_COMPARE(font.size(), 42.0f);
CORRADE_COMPARE(font.ascent(), 1.0f);
CORRADE_COMPARE(font.descent(), 2.0f);
CORRADE_COMPARE(font.lineHeight(), 3.0f);
}
void AbstractFontTest::setFileCallbackOpenFileThroughBaseImplementationFailed() {
struct: AbstractFont {
Features doFeatures() const override { return Feature::FileCallback|Feature::OpenData; }
bool doIsOpened() const override { return false; }
void doClose() override {}
Metrics doOpenFile(const std::string& filename, Float size) override {
openFileCalled = true;
return AbstractFont::doOpenFile(filename, size);
}
UnsignedInt doGlyphId(char32_t) override { return {}; }
Vector2 doGlyphAdvance(UnsignedInt) override { return {}; }
Containers::Pointer<AbstractLayouter> doLayout(const AbstractGlyphCache&, Float, const std::string&) override {
return nullptr;
}
bool openFileCalled = false;
} font;
font.setFileCallback([](const std::string&, InputFileCallbackPolicy, void*) -> Containers::Optional<Containers::ArrayView<const char>> {
return {};
});
std::ostringstream out;
Error redirectError{&out};
CORRADE_VERIFY(!font.openFile("file.dat", 42.0f));
CORRADE_VERIFY(font.openFileCalled);
CORRADE_COMPARE(out.str(), "Text::AbstractFont::openFile(): cannot open file file.dat\n");
}
void AbstractFontTest::setFileCallbackOpenFileAsData() {
struct: AbstractFont {
Features doFeatures() const override { return Feature::OpenData; }
bool doIsOpened() const override { return _opened; }
void doClose() override { _opened = false; }
Metrics doOpenFile(const std::string&, Float) override {
openFileCalled = true;
return {};
}
Metrics doOpenData(Containers::ArrayView<const char> data, Float size) override {
_opened = (data.size() == 1 && data[0] == '\xb0');
return {size, 1.0f, 2.0f, 3.0f};
}
UnsignedInt doGlyphId(char32_t) override { return {}; }
Vector2 doGlyphAdvance(UnsignedInt) override { return {}; }
Containers::Pointer<AbstractLayouter> doLayout(const AbstractGlyphCache&, Float, const std::string&) override {
return nullptr;
}
bool _opened = false;
bool openFileCalled = false;
} font;
struct State {
const char data = '\xb0';
bool loaded = false;
bool closed = false;
bool calledNotSureWhy = false;
} state;
font.setFileCallback([](const std::string& filename, InputFileCallbackPolicy policy, State& state) -> Containers::Optional<Containers::ArrayView<const char>> {
if(filename == "file.dat" && policy == InputFileCallbackPolicy::LoadTemporary) {
state.loaded = true;
return Containers::arrayView(&state.data, 1);
}
if(filename == "file.dat" && policy == InputFileCallbackPolicy::Close) {
state.closed = true;
return {};
}
state.calledNotSureWhy = true;
return {};
}, state);
CORRADE_VERIFY(font.openFile("file.dat", 13.0f));
CORRADE_VERIFY(!font.openFileCalled);
CORRADE_VERIFY(state.loaded);
CORRADE_VERIFY(state.closed);
CORRADE_VERIFY(!state.calledNotSureWhy);
CORRADE_COMPARE(font.size(), 13.0f);
CORRADE_COMPARE(font.ascent(), 1.0f);
CORRADE_COMPARE(font.descent(), 2.0f);
CORRADE_COMPARE(font.lineHeight(), 3.0f);
}
void AbstractFontTest::setFileCallbackOpenFileAsDataFailed() {
struct: AbstractFont {
Features doFeatures() const override { return Feature::OpenData; }
bool doIsOpened() const override { return false; }
void doClose() override {}
Metrics doOpenFile(const std::string&, Float) override {
openFileCalled = true;
return {};
}
UnsignedInt doGlyphId(char32_t) override { return {}; }
Vector2 doGlyphAdvance(UnsignedInt) override { return {}; }
Containers::Pointer<AbstractLayouter> doLayout(const AbstractGlyphCache&, Float, const std::string&) override {
return nullptr;
}
bool openFileCalled = false;
} font;
font.setFileCallback([](const std::string&, InputFileCallbackPolicy, void*) {
return Containers::Optional<Containers::ArrayView<const char>>{};
});
std::ostringstream out;
Error redirectError{&out};
CORRADE_VERIFY(!font.openFile("file.dat", 132.0f));
CORRADE_VERIFY(!font.openFileCalled);
CORRADE_COMPARE(out.str(), "Text::AbstractFont::openFile(): cannot open file file.dat\n");
}
void AbstractFontTest::glyphId() {

3
src/Magnum/Trade/AbstractImporter.h

@ -97,6 +97,9 @@ data through to @ref openData(), in case the importer supports at least
nor @ref Feature::OpenData, @ref setFileCallback() doesn't allow the callbacks
to be set.
The input file callback signature is the same for @ref Trade::AbstractImporter
and @ref Text::AbstractFont to allow code reuse.
@subsection Trade-AbstractImporter-usage-state Internal importer state
Some importers, especially ones that make use of well-known external libraries,

99
src/MagnumPlugins/MagnumFont/MagnumFont.cpp

@ -39,8 +39,14 @@
namespace Magnum { namespace Text {
struct MagnumFont::Data {
/* Otherwise Clang complains about Utility::Configuration having explicit
constructor when emplace()ing the Pointer. Using = default works on
newer Clang but not older versions. */
explicit Data() {}
Utility::Configuration conf;
Trade::ImageData2D image;
Containers::Optional<Trade::ImageData2D> image;
Containers::Optional<std::string> filePath;
std::unordered_map<char32_t, UnsignedInt> glyphId;
std::vector<Vector2> glyphAdvance;
};
@ -66,23 +72,26 @@ MagnumFont::MagnumFont(PluginManager::AbstractManager& manager, const std::strin
MagnumFont::~MagnumFont() { close(); }
auto MagnumFont::doFeatures() const -> Features { return Feature::OpenData|Feature::MultiFile|Feature::PreparedGlyphCache; }
auto MagnumFont::doFeatures() const -> Features { return Feature::OpenData|Feature::FileCallback|Feature::PreparedGlyphCache; }
bool MagnumFont::doIsOpened() const { return _opened && _opened->image; }
bool MagnumFont::doIsOpened() const { return _opened; }
void MagnumFont::doClose() { _opened = nullptr; }
auto MagnumFont::doOpenData(const std::vector<std::pair<std::string, Containers::ArrayView<const char>>>& data, const Float) -> Metrics {
/* We need just the configuration file and image file */
if(data.size() != 2) {
Error() << "Text::MagnumFont::openData(): wanted two files, got" << data.size();
auto MagnumFont::doOpenData(const Containers::ArrayView<const char> data, const Float) -> Metrics {
if(!_opened) _opened.emplace();
if(!_opened->filePath && !fileCallback()) {
Error{} << "Text::MagnumFont::openData(): the font can be opened only from the filesystem or if a file callback is present";
return {};
}
/* Open the configuration file */
/* MSVC 2017 requires explicit std::string constructor. MSVC 2015 doesn't. */
std::istringstream in(std::string{data[0].second.begin(), data[0].second.size()});
std::istringstream in(std::string{data.begin(), data.size()});
Utility::Configuration conf(in, Utility::Configuration::Flag::SkipComments);
if(!conf.isValid() || conf.isEmpty()) {
Error() << "Text::MagnumFont::openData(): cannot open file" << data[0].first;
Error{} << "Text::MagnumFont::openData(): font file is not valid";
return {};
}
@ -93,62 +102,16 @@ auto MagnumFont::doOpenData(const std::vector<std::pair<std::string, Containers:
return {};
}
/* Check that we have also the image file */
if(conf.value("image") != data[1].first) {
Error() << "Text::MagnumFont::openData(): expected file"
<< conf.value("image") << "but got" << data[1].first;
return {};
}
/* Open and load image file */
Trade::TgaImporter importer;
if(!importer.openData(data[1].second)) {
Error() << "Text::MagnumFont::openData(): cannot open image file";
return {};
}
Containers::Optional<Trade::ImageData2D> image = importer.image2D(0);
if(!image) {
Error() << "Text::MagnumFont::openData(): cannot load image file";
return {};
}
return openInternal(std::move(conf), std::move(*image));
}
auto MagnumFont::doOpenFile(const std::string& filename, Float) -> Metrics {
/* Open the configuration file */
Utility::Configuration conf(filename, Utility::Configuration::Flag::ReadOnly|Utility::Configuration::Flag::SkipComments);
if(!conf.isValid() || conf.isEmpty()) {
Error() << "Text::MagnumFont::openFile(): cannot open file" << filename;
return {};
}
/* Check version */
if(conf.value<UnsignedInt>("version") != 1) {
Error() << "Text::MagnumFont::openFile(): unsupported file version, expected 1 but got"
<< conf.value<UnsignedInt>("version");
return {};
}
/* Open and load image file */
const std::string imageFilename = Utility::Directory::join(Utility::Directory::path(filename), conf.value("image"));
/* Open and load image file. Error messages should be printed by the
TgaImporter already, no need to repeat them again. */
Trade::TgaImporter importer;
if(!importer.openFile(imageFilename)) {
Error() << "Text::MagnumFont::openFile(): cannot open image file" << imageFilename;
return {};
}
Containers::Optional<Trade::ImageData2D> image = importer.image2D(0);
if(!image) {
Error() << "Text::MagnumFont::openFile(): cannot load image file";
return {};
}
return openInternal(std::move(conf), std::move(*image));
}
importer.setFileCallback(fileCallback(), fileCallbackUserData());
if(!importer.openFile(Utility::Directory::join(_opened->filePath ? *_opened->filePath : "", conf.value("image")))) return {};
_opened->image = importer.image2D(0);
if(!_opened->image) return {};
auto MagnumFont::openInternal(Utility::Configuration&& conf, Trade::ImageData2D&& image) -> Metrics {
/* Everything okay, save the data internally */
_opened = new Data{std::move(conf), std::move(image), std::unordered_map<char32_t, UnsignedInt>{}, {}};
_opened->conf = std::move(conf);
/* Glyph advances */
const std::vector<Utility::ConfigurationGroup*> glyphs = _opened->conf.groups("glyph");
@ -170,9 +133,11 @@ auto MagnumFont::openInternal(Utility::Configuration&& conf, Trade::ImageData2D&
_opened->conf.value<Float>("lineHeight")};
}
void MagnumFont::doClose() {
delete _opened;
_opened = nullptr;
auto MagnumFont::doOpenFile(const std::string& filename, Float size) -> Metrics {
_opened.emplace();
_opened->filePath = Utility::Directory::path(filename);
return AbstractFont::doOpenFile(filename, size);
}
UnsignedInt MagnumFont::doGlyphId(const char32_t character) {
@ -188,9 +153,9 @@ Containers::Pointer<AbstractGlyphCache> MagnumFont::doCreateGlyphCache() {
/* Set cache image */
Containers::Pointer<AbstractGlyphCache> cache(new Text::GlyphCache(
_opened->conf.value<Vector2i>("originalImageSize"),
_opened->image.size(),
_opened->image->size(),
_opened->conf.value<Vector2i>("padding")));
cache->setImage({}, _opened->image);
cache->setImage({}, *_opened->image);
/* Fill glyph map */
const std::vector<Utility::ConfigurationGroup*> glyphs = _opened->conf.groups("glyph");

13
src/MagnumPlugins/MagnumFont/MagnumFont.h

@ -32,9 +32,9 @@
#include "Magnum/configure.h"
#ifdef MAGNUM_TARGET_GL
#include "Magnum/Text/AbstractFont.h"
#include "Magnum/Trade/Trade.h"
#include <Corrade/Containers/Pointer.h>
#include "Magnum/Text/AbstractFont.h"
#include "MagnumPlugins/MagnumFont/configure.h"
#ifndef DOXYGEN_GENERATING_OUTPUT
@ -136,11 +136,9 @@ class MAGNUM_MAGNUMFONT_EXPORT MagnumFont: public AbstractFont {
~MagnumFont();
private:
struct Data;
MAGNUM_MAGNUMFONT_LOCAL Features doFeatures() const override;
MAGNUM_MAGNUMFONT_LOCAL bool doIsOpened() const override;
MAGNUM_MAGNUMFONT_LOCAL Metrics doOpenData(const std::vector<std::pair<std::string, Containers::ArrayView<const char>>>& data, Float) override;
MAGNUM_MAGNUMFONT_LOCAL Metrics doOpenData(Containers::ArrayView<const char> data, Float) override;
MAGNUM_MAGNUMFONT_LOCAL Metrics doOpenFile(const std::string& filename, Float) override;
MAGNUM_MAGNUMFONT_LOCAL void doClose() override;
@ -149,9 +147,8 @@ class MAGNUM_MAGNUMFONT_EXPORT MagnumFont: public AbstractFont {
MAGNUM_MAGNUMFONT_LOCAL Containers::Pointer<AbstractGlyphCache> doCreateGlyphCache() override;
MAGNUM_MAGNUMFONT_LOCAL Containers::Pointer<AbstractLayouter> doLayout(const AbstractGlyphCache& cache, Float size, const std::string& text) override;
MAGNUM_MAGNUMFONT_LOCAL Metrics openInternal(Utility::Configuration&& conf, Trade::ImageData2D&& image);
Data* _opened;
struct Data;
Containers::Pointer<Data> _opened;
};
}}

48
src/MagnumPlugins/MagnumFont/Test/MagnumFontTest.cpp

@ -24,9 +24,12 @@
*/
#include <sstream>
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/Optional.h>
#include <Corrade/Utility/Directory.h>
#include <Corrade/TestSuite/Tester.h>
#include "Magnum/FileCallback.h"
#include "Magnum/GL/OpenGLTester.h"
#include "Magnum/Text/AbstractFont.h"
#include "Magnum/Text/AbstractGlyphCache.h"
@ -43,6 +46,9 @@ struct MagnumFontTest: TestSuite::Tester {
void properties();
void layout();
void fileCallbackImage();
void fileCallbackImageNotFound();
/* Explicitly forbid system-wide plugin dependencies */
PluginManager::Manager<Trade::AbstractImporter> _importerManager{"nonexistent"};
PluginManager::Manager<AbstractFont> _fontManager{"nonexistent"};
@ -51,7 +57,10 @@ struct MagnumFontTest: TestSuite::Tester {
MagnumFontTest::MagnumFontTest() {
addTests({&MagnumFontTest::nonexistent,
&MagnumFontTest::properties,
&MagnumFontTest::layout});
&MagnumFontTest::layout,
&MagnumFontTest::fileCallbackImage,
&MagnumFontTest::fileCallbackImageNotFound});
/* Load the plugins directly from the build tree. Otherwise they're static
and already loaded. */
@ -67,7 +76,7 @@ void MagnumFontTest::nonexistent() {
std::ostringstream out;
Error redirectError{&out};
CORRADE_VERIFY(!font->openFile("nonexistent.conf", 0.0f));
CORRADE_COMPARE(out.str(), "Text::MagnumFont::openFile(): cannot open file nonexistent.conf\n");
CORRADE_COMPARE(out.str(), "Text::AbstractFont::openFile(): cannot open file nonexistent.conf\n");
}
void MagnumFontTest::properties() {
@ -130,6 +139,41 @@ void MagnumFontTest::layout() {
CORRADE_COMPARE(cursorPosition, Vector2(0.375f, 0.0f));
}
void MagnumFontTest::fileCallbackImage() {
Containers::Pointer<AbstractFont> font = _fontManager.instantiate("MagnumFont");
CORRADE_VERIFY(font->features() & AbstractFont::Feature::FileCallback);
std::unordered_map<std::string, Containers::Array<char>> files;
files["not/a/path/font.conf"] = Utility::Directory::read(Utility::Directory::join(MAGNUMFONT_TEST_DIR, "font.conf"));
files["not/a/path/font.tga"] = Utility::Directory::read(Utility::Directory::join(MAGNUMFONT_TEST_DIR, "font.tga"));
font->setFileCallback([](const std::string& filename, InputFileCallbackPolicy policy,
std::unordered_map<std::string, Containers::Array<char>>& files) {
Debug{} << "Loading" << filename << "with" << policy;
return Containers::optional(Containers::ArrayView<const char>(files.at(filename)));
}, files);
CORRADE_VERIFY(font->openFile("not/a/path/font.conf", 13.0f));
CORRADE_COMPARE(font->size(), 16.0f);
CORRADE_COMPARE(font->ascent(), 25.0f);
CORRADE_COMPARE(font->descent(), -10.0f);
CORRADE_COMPARE(font->lineHeight(), 39.7333f);
CORRADE_COMPARE(font->glyphAdvance(font->glyphId(U'W')), Vector2(23.0f, 0.0f));
}
void MagnumFontTest::fileCallbackImageNotFound() {
Containers::Pointer<AbstractFont> font = _fontManager.instantiate("MagnumFont");
CORRADE_VERIFY(font->features() & AbstractFont::Feature::FileCallback);
font->setFileCallback([](const std::string&, InputFileCallbackPolicy, void*) {
return Containers::Optional<Containers::ArrayView<const char>>{};
});
std::ostringstream out;
Error redirectError{&out};
CORRADE_VERIFY(!font->openData(Utility::Directory::read(Utility::Directory::join(MAGNUMFONT_TEST_DIR, "font.conf")), 13.0f));
CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::openFile(): cannot open file font.tga\n");
}
}}}}
CORRADE_TEST_MAIN(Magnum::Text::Test::MagnumFontTest)

Loading…
Cancel
Save