diff --git a/src/Text/AbstractFont.cpp b/src/Text/AbstractFont.cpp index 58a90cf2b..741d1b48c 100644 --- a/src/Text/AbstractFont.cpp +++ b/src/Text/AbstractFont.cpp @@ -22,7 +22,11 @@ DEALINGS IN THE SOFTWARE. */ -#include "Text/AbstractFont.h" +#include "AbstractFont.h" + +#include +#include +#include namespace Magnum { namespace Text { @@ -30,6 +34,96 @@ AbstractFont::AbstractFont(): _size(0.0f) {} AbstractFont::AbstractFont(PluginManager::AbstractManager* manager, std::string plugin): AbstractPlugin(manager, std::move(plugin)), _size(0.0f) {} +bool AbstractFont::openData(const std::vector>>& data, const Float size) { + CORRADE_ASSERT(features() & Feature::OpenData, + "Text::AbstractFont::openData(): feature not supported", false); + CORRADE_ASSERT(!data.empty(), + "Text::AbstractFont::openData(): no data passed", false); + + close(); + doOpenData(data, size); + return isOpened(); +} + +void AbstractFont::doOpenData(const std::vector>>& data, const Float size) { + 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", ); + + close(); + doOpenSingleData(data[0].second, size); +} + +bool AbstractFont::openSingleData(const Containers::ArrayReference 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); + + close(); + doOpenSingleData(data, size); + return isOpened(); +} + +void AbstractFont::doOpenSingleData(Containers::ArrayReference, Float) { + CORRADE_ASSERT(false, "Text::AbstractFont::openSingleData(): feature advertised but not implemented", ); +} + +bool AbstractFont::openFile(const std::string& filename, const Float size) { + close(); + doOpenFile(filename, size); + return isOpened(); +} + +void AbstractFont::doOpenFile(const std::string& filename, const Float size) { + CORRADE_ASSERT(features() & Feature::OpenData && !(features() & Feature::MultiFile), + "Text::AbstractFont::openFile(): not implemented", ); + + /* Open file */ + std::ifstream in(filename.data(), std::ios::binary); + if(!in.good()) { + Error() << "Trade::AbstractFont::openFile(): cannot open file" << filename; + return; + } + + /* Create array to hold file contents */ + in.seekg(0, std::ios::end); + Containers::Array data(in.tellg()); + + /* Read data, close */ + in.seekg(0, std::ios::beg); + in.read(reinterpret_cast(data.begin()), data.size()); + in.close(); + + doOpenSingleData(data, size); +} + +void AbstractFont::close() { + if(isOpened()) doClose(); +} + +void AbstractFont::createGlyphCache(GlyphCache* const cache, const std::string& characters) { + CORRADE_ASSERT(isOpened(), "Text::AbstractFont::createGlyphCache(): no font opened", ); + + /* Get glyph codes from characters */ + std::u32string unicodeCharacters; + unicodeCharacters.reserve(characters.size()+1); + for(std::size_t i = 0; i != characters.size(); ) { + char32_t unicode; + std::tie(unicode, i) = Utility::Unicode::nextChar(characters, i); + unicodeCharacters.push_back(unicode); + } + + doCreateGlyphCache(cache, unicodeCharacters); +} + +AbstractLayouter* AbstractFont::layout(const GlyphCache* const cache, const Float size, const std::string& text) { + CORRADE_ASSERT(isOpened(), "Text::AbstractFont::layout(): no font opened", nullptr); + + return doLayout(cache, size, text); +} + AbstractLayouter::AbstractLayouter(): _glyphCount(0) {} AbstractLayouter::~AbstractLayouter() {} diff --git a/src/Text/AbstractFont.h b/src/Text/AbstractFont.h index 765defb49..de2f4cf45 100644 --- a/src/Text/AbstractFont.h +++ b/src/Text/AbstractFont.h @@ -50,41 +50,93 @@ information. See TextRenderer for information about text rendering. @section AbstractFont-subclassing Subclassing -Plugin implements functions open(), close(), createGlyphCache() and layout(). +Plugin implements doFeatures(), doClose(), doCreateGlyphCache(), doLayout() and +one or more of `doOpen*()` functions. + +You don't need to do most of the redundant sanity checks, these things are +checked by the implementation: + +- Functions doOpenData(), doOpenSingleData() and doOpenFile() are called + after the previous file was closed, function doClose() is called only if + there is any file opened. +- Functions doOpenData() and doOpenSingleData() are called only if + @ref Feature "Feature::OpenData" is supported. +- All `do*()` implementations working on opened file are called only if + there is any file opened. */ class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin { - CORRADE_PLUGIN_INTERFACE("cz.mosra.magnum.Text.AbstractFont/0.1") + CORRADE_PLUGIN_INTERFACE("cz.mosra.magnum.Text.AbstractFont/0.2") public: + /** + * @brief Features supported by this importer + * + * @see Features, features() + */ + enum class Feature: UnsignedByte { + /** Opening fonts from raw data using openData() */ + OpenData = 1 << 0, + + /** + * The format is multi-file, thus openSingleData() convenience + * function cannot be used. + */ + MultiFile = 1 << 1 + }; + + /** @brief Set of features supported by this importer */ + typedef Containers::EnumSet Features; + /** @brief Default constructor */ explicit AbstractFont(); /** @brief Plugin manager constructor */ explicit AbstractFont(PluginManager::AbstractManager* manager, std::string plugin); + /** @brief Features supported by this font */ + Features features() const { return doFeatures(); } + + /** @brief Whether any file is opened */ + bool isOpened() const { return doIsOpened(); } + /** - * @brief Open font from file - * @param filename Font file + * @brief Open font from raw data + * @param data Pairs of filename and file data + * @param size Font size + * + * Closes previous file, if it was opened, and tries to open given + * file. Available only if @ref Feature "Feature::OpenData" is + * supported. Returns `true` on success, `false` otherwise. + */ + bool openData(const std::vector>>& 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. Returns `true` on success, `false` otherwise. + * file. Available only if @ref Feature "Feature::OpenData" is + * supported and the plugin doesn't have @ref Feature "Feature::MultiFile". + * Returns `true` on success, `false` otherwise. */ - virtual bool open(const std::string& filename, Float size) = 0; + bool openSingleData(Containers::ArrayReference data, Float size); /** - * @brief Open font from memory - * @param data Font data - * @param dataSize Font data size + * @brief Open font from file + * @param filename Font file * @param size Font size * * Closes previous file, if it was opened, and tries to open given - * file. Returns `true` on success, `false` otherwise. + * file. If the plugin has @ref Feature "Feature::MultiFile", the + * function will use additional files in given path, all sharing common + * basename derived from @p filename. Returns `true` on success, + * `false` otherwise. */ - virtual bool open(const unsigned char* data, std::size_t dataSize, Float size) = 0; + bool openFile(const std::string& filename, Float size); /** @brief Close font */ - virtual void close() = 0; + void close(); /** @brief Font size */ Float size() const { return _size; } @@ -96,17 +148,17 @@ class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin { * * Fills the cache with given characters. */ - virtual void createGlyphCache(GlyphCache* cache, const std::string& characters) = 0; + void createGlyphCache(GlyphCache* cache, const std::string& characters); /** - * @brief Layout the text using font own layouter + * @brief Layout the text using font's own layouter * @param cache Glyph cache * @param size Font size * @param text %Text to layout * * @see createGlyphCache() */ - virtual AbstractLayouter* layout(const GlyphCache* cache, Float size, const std::string& text) = 0; + AbstractLayouter* layout(const GlyphCache* cache, Float size, const std::string& text); #ifdef DOXYGEN_GENERATING_OUTPUT private: @@ -114,6 +166,52 @@ class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin { protected: #endif Float _size; + + #ifdef DOXYGEN_GENERATING_OUTPUT + protected: + #else + private: + #endif + /** @brief Implementation for features() */ + virtual Features doFeatures() const = 0; + + /** @brief Implementation for isOpened() */ + virtual bool doIsOpened() const = 0; + + /** + * @brief Implementation for openData() + * + * If the plugin doesn't have @ref Feature "Feature::MultiFile", + * default implementation calls doOpenSingleData(). + */ + virtual void doOpenData(const std::vector>>& data, Float size); + + /** @brief Implementation for openSingleData() */ + virtual void doOpenSingleData(Containers::ArrayReference data, Float size); + + /** + * @brief Implementation for openFile() + * + * If @ref Feature "Feature::OpenData" is supported and the plugin + * doesn't have @ref Feature "Feature::MultiFile", default + * implementation opens the file and calls doOpenSingleData() with its + * contents. + */ + virtual void doOpenFile(const std::string& filename, Float size); + + /** @brief Implementation for close() */ + virtual void doClose() = 0; + + /** + * @brief Implementation for createGlyphCache() + * + * The string is converted from UTF-8 to UTF-32, unique characters are + * *not* removed. + */ + virtual void doCreateGlyphCache(GlyphCache* cache, const std::u32string& characters) = 0; + + /** @brief Implementation for layout() */ + virtual AbstractLayouter* doLayout(const GlyphCache* cache, Float size, const std::string& text) = 0; }; /** diff --git a/src/Text/CMakeLists.txt b/src/Text/CMakeLists.txt index fa28db8d1..b7e7f30a2 100644 --- a/src/Text/CMakeLists.txt +++ b/src/Text/CMakeLists.txt @@ -45,3 +45,7 @@ target_link_libraries(MagnumText Magnum MagnumTextureTools) install(TARGETS MagnumText DESTINATION ${MAGNUM_LIBRARY_INSTALL_DIR}) install(FILES ${MagnumText_HEADERS} DESTINATION ${MAGNUM_INCLUDE_INSTALL_DIR}/Text) + +if(BUILD_TESTS) + add_subdirectory(Test) +endif() diff --git a/src/Text/Test/AbstractFontTest.cpp b/src/Text/Test/AbstractFontTest.cpp new file mode 100644 index 000000000..4447b9aaa --- /dev/null +++ b/src/Text/Test/AbstractFontTest.cpp @@ -0,0 +1,118 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include + +#include "Text/AbstractFont.h" + +#include "testConfigure.h" + +namespace Magnum { namespace Text { namespace Test { + +class AbstractFontTest: public TestSuite::Tester { + public: + explicit AbstractFontTest(); + + void openSingleData(); + void openFile(); + + void createGlyphCache(); +}; + +AbstractFontTest::AbstractFontTest() { + addTests({&AbstractFontTest::openSingleData, + &AbstractFontTest::openFile, + + &AbstractFontTest::createGlyphCache}); +} + +namespace { + +class SingleDataFont: public Text::AbstractFont { + public: + Features doFeatures() const override { return Feature::OpenData; } + bool doIsOpened() const override { return opened; } + void doClose() override {} + + void doOpenSingleData(const Containers::ArrayReference data, Float) override { + opened = (data.size() == 1 && data[0] == 0xa5); + } + + void doCreateGlyphCache(GlyphCache*, const std::u32string&) override {} + + AbstractLayouter* doLayout(const GlyphCache*, Float, const std::string&) { + return nullptr; + } + + bool opened = false; +}; + +} + +void AbstractFontTest::openSingleData() { + /* doOpenData() should call doOpenSingleData() */ + SingleDataFont font; + const unsigned char data[] = {0xa5}; + CORRADE_VERIFY(!font.isOpened()); + font.openData({{{}, data}}, 3.0f); + CORRADE_VERIFY(font.isOpened()); +} + +void AbstractFontTest::openFile() { + /* doOpenFile() should call doOpenSingleData() */ + SingleDataFont font; + CORRADE_VERIFY(!font.isOpened()); + font.openFile(Utility::Directory::join(TEXT_TEST_DIR, "data.bin"), 3.0f); + CORRADE_VERIFY(font.isOpened()); +} + +void AbstractFontTest::createGlyphCache() { + class CachingFont: public Text::AbstractFont { + public: + std::u32string cacheCharacters; + + private: + Features doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + void doCreateGlyphCache(GlyphCache*, const std::u32string& characters) override { + cacheCharacters = characters; + } + + AbstractLayouter* doLayout(const GlyphCache*, Float, const std::string&) override { + return nullptr; + } + }; + + CachingFont font; + font.createGlyphCache(nullptr, "žluťoučký kůň"); + CORRADE_COMPARE(font.cacheCharacters, U"\u017Elu\u0165ou\u010Dk\u00FD k\u016F\u0148"); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::Text::Test::AbstractFontTest) diff --git a/src/Text/Test/CMakeLists.txt b/src/Text/Test/CMakeLists.txt new file mode 100644 index 000000000..e190c7785 --- /dev/null +++ b/src/Text/Test/CMakeLists.txt @@ -0,0 +1,30 @@ +# +# This file is part of Magnum. +# +# Copyright © 2010, 2011, 2012, 2013 Vladimír Vondruš +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/testConfigure.h.cmake + ${CMAKE_CURRENT_BINARY_DIR}/testConfigure.h) + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +corrade_add_test(TextAbstractFontTest AbstractFontTest.cpp LIBRARIES Magnum MagnumText) diff --git a/src/Text/Test/data.bin b/src/Text/Test/data.bin new file mode 100644 index 000000000..dd6737691 --- /dev/null +++ b/src/Text/Test/data.bin @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Text/Test/testConfigure.h.cmake b/src/Text/Test/testConfigure.h.cmake new file mode 100644 index 000000000..0cfeafcbe --- /dev/null +++ b/src/Text/Test/testConfigure.h.cmake @@ -0,0 +1,25 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#define TEXT_TEST_DIR "${CMAKE_CURRENT_SOURCE_DIR}"