From d655352485f6817dedcc9693230f88f671ef8fa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 21 Jun 2013 17:54:23 +0200 Subject: [PATCH] Text: reworked AbstractFont plugin interface. Implementation is moved into `do*()` private virtual functions, public interface is doing additional sanity checks around them. Opening files is by default done in base implementation, which loads the files into memory and then calls function for opening raw data. Added interface for opening multi-file fonts from raw data, default implementation just calls single-file implementation. Function for creating glyph cache converts the text from UTF-8 to UTF-32 and then calls the implementation, removing the burden from reimplementing this in each plugin. Added unit test for file and single data opening, bumped plugin version to 0.2. --- src/Text/AbstractFont.cpp | 96 ++++++++++++++++++++- src/Text/AbstractFont.h | 128 ++++++++++++++++++++++++---- src/Text/CMakeLists.txt | 4 + src/Text/Test/AbstractFontTest.cpp | 118 +++++++++++++++++++++++++ src/Text/Test/CMakeLists.txt | 30 +++++++ src/Text/Test/data.bin | 1 + src/Text/Test/testConfigure.h.cmake | 25 ++++++ 7 files changed, 386 insertions(+), 16 deletions(-) create mode 100644 src/Text/Test/AbstractFontTest.cpp create mode 100644 src/Text/Test/CMakeLists.txt create mode 100644 src/Text/Test/data.bin create mode 100644 src/Text/Test/testConfigure.h.cmake 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}"