From fba9460bfd50b059c5bcc7a73d07244861cdf006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Mon, 22 Jan 2018 00:17:48 -0300 Subject: [PATCH] Added FontSprite class for font sprite support ( XNA Font Sprites ). --HG-- branch : dev-2.1 --- bin/assets/fonts/custom_alagard.png | Bin 0 -> 39906 bytes include/eepp/graphics.hpp | 1 + include/eepp/graphics/fontbmfont.hpp | 3 +- include/eepp/graphics/fonthelper.hpp | 3 +- include/eepp/graphics/fontsprite.hpp | 72 +++++++++ include/eepp/graphics/image.hpp | 7 + projects/linux/ee.files | 2 + src/eepp/graphics/fontbmfont.cpp | 5 +- src/eepp/graphics/fontsprite.cpp | 211 +++++++++++++++++++++++++++ src/eepp/graphics/image.cpp | 38 ++++- src/examples/fonts/fonts.cpp | 12 ++ 11 files changed, 349 insertions(+), 5 deletions(-) create mode 100644 bin/assets/fonts/custom_alagard.png create mode 100644 include/eepp/graphics/fontsprite.hpp create mode 100644 src/eepp/graphics/fontsprite.cpp diff --git a/bin/assets/fonts/custom_alagard.png b/bin/assets/fonts/custom_alagard.png new file mode 100644 index 0000000000000000000000000000000000000000..d63f00e6badce88267d7bf7bdd1af9854bfe282b GIT binary patch literal 39906 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 literal 0 HcmV?d00001 diff --git a/include/eepp/graphics.hpp b/include/eepp/graphics.hpp index b3930a4de..1baba58e5 100644 --- a/include/eepp/graphics.hpp +++ b/include/eepp/graphics.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include diff --git a/include/eepp/graphics/fontbmfont.hpp b/include/eepp/graphics/fontbmfont.hpp index 9cd18a192..cb0a28688 100644 --- a/include/eepp/graphics/fontbmfont.hpp +++ b/include/eepp/graphics/fontbmfont.hpp @@ -12,6 +12,7 @@ class IOStream; namespace EE { namespace Graphics { +/** @brief Implementation of AngelCode BMFont fonts. */ class EE_API FontBMFont : public Font { public: static FontBMFont * New( const std::string FontName ) ; @@ -20,7 +21,7 @@ class EE_API FontBMFont : public Font { bool loadFromFile(const std::string& filename); - bool loadFromMemory(const void* data, std::size_t sizeInBytes); + bool loadFromMemory(const void* data, std::size_t sizeInBytes, const std::string & imageFileBasePath); bool loadFromStream( IOStream& stream ); diff --git a/include/eepp/graphics/fonthelper.hpp b/include/eepp/graphics/fonthelper.hpp index 4993f10b2..c6a6a8f56 100644 --- a/include/eepp/graphics/fonthelper.hpp +++ b/include/eepp/graphics/fonthelper.hpp @@ -5,7 +5,8 @@ namespace EE { namespace Graphics { enum EE_FONT_TYPE { FONT_TYPE_TTF = 1, - FONT_TYPE_BMF = 2 + FONT_TYPE_BMF = 2, + FONT_TYPE_SPRITE = 3 }; enum EE_FONT_HALIGN { diff --git a/include/eepp/graphics/fontsprite.hpp b/include/eepp/graphics/fontsprite.hpp new file mode 100644 index 000000000..f603fe49c --- /dev/null +++ b/include/eepp/graphics/fontsprite.hpp @@ -0,0 +1,72 @@ +#ifndef EE_GRAPHICS_FONTSPRITE_HPP +#define EE_GRAPHICS_FONTSPRITE_HPP + +#include +#include +#include + +namespace EE { namespace System { +class Pack; +class IOStream; +}} + +namespace EE { namespace Graphics { + +/** @brief Implementation of XNA Font Sprites */ +class EE_API FontSprite : public Font { + public: + static FontSprite * New( const std::string FontName ) ; + + ~FontSprite(); + + bool loadFromFile(const std::string& filename, Color key = Color::Magenta, Uint32 firstChar = 32, int spacing = 0); + + bool loadFromMemory(const void* data, std::size_t sizeInBytes, Color key = Color::Magenta, Uint32 firstChar = 32, int spacing = 0); + + bool loadFromStream( IOStream& stream, Color key = Color::Magenta, Uint32 firstChar = 32, int spacing = 0 ); + + bool loadFromPack( Pack * pack, std::string filePackPath, Color key = Color::Magenta, Uint32 firstChar = 32, int spacing = 0 ); + + const Font::Info& getInfo() const; + + const Glyph& getGlyph(Uint32 codePoint, unsigned int characterSize, bool bold, Float outlineThickness = 0) const; + + Float getKerning(Uint32 first, Uint32 second, unsigned int characterSize) const; + + Float getLineSpacing(unsigned int characterSize) const; + + Uint32 getFontHeight( const Uint32& characterSize ); + + Float getUnderlinePosition(unsigned int characterSize) const; + + Float getUnderlineThickness(unsigned int characterSize) const; + + Texture * getTexture(unsigned int characterSize) const; + + FontSprite& operator =(const FontSprite& right); + protected: + FontSprite(const std::string FontName); + + typedef std::map GlyphTable; ///< Table mapping a codepoint to its glyph + + struct Page + { + GlyphTable glyphs; ///< Table mapping code points to their corresponding glyph + Texture * texture; ///< Texture containing the pixels of the glyphs + }; + + void cleanup(); + + typedef std::map PageTable; ///< Table mapping a character size to its page (texture) + + Font::Info mInfo; ///< Information about the font + mutable PageTable mPages; ///< Table containing the glyphs pages by character size + std::string mFilePath; + Uint32 mFontSize; + + Glyph loadGlyph(Uint32 codePoint, unsigned int characterSize, bool bold, Float outlineThickness) const; +}; + +}} + +#endif diff --git a/include/eepp/graphics/image.hpp b/include/eepp/graphics/image.hpp index 668d1e690..fa69b4be3 100644 --- a/include/eepp/graphics/image.hpp +++ b/include/eepp/graphics/image.hpp @@ -14,6 +14,7 @@ using namespace EE::System; namespace EE { namespace System { class Pack; +class IOStream; }} namespace EE { namespace Graphics { @@ -124,6 +125,12 @@ class EE_API Image { */ Image( Pack * Pack, std::string FilePackPath, const unsigned int& forceChannels = 0 ); + /** Load an image from stream + * @param stream The stream to read the image + * @param forceChannels Number of channels to use for the image, default 0 means that it use the default image channels. + */ + Image( IOStream& stream, const unsigned int& forceChannels = 0 ); + virtual ~Image(); /** Create an empty image data */ diff --git a/projects/linux/ee.files b/projects/linux/ee.files index be8ac95b7..c08d96b6d 100644 --- a/projects/linux/ee.files +++ b/projects/linux/ee.files @@ -6,6 +6,7 @@ ../../include/eepp/graphics/drawableresource.hpp ../../include/eepp/graphics/drawablesearcher.hpp ../../include/eepp/graphics/font.hpp +../../include/eepp/graphics/fontsprite.hpp ../../include/eepp/graphics/fontstyleconfig.hpp ../../include/eepp/graphics/fonttruetype.hpp ../../include/eepp/graphics/fontbmfont.hpp @@ -74,6 +75,7 @@ ../../src/eepp/graphics/convexshapedrawable.cpp ../../src/eepp/graphics/drawablegroup.cpp ../../src/eepp/graphics/drawableresource.cpp +../../src/eepp/graphics/fontsprite.cpp ../../src/eepp/graphics/ninepatch.cpp ../../src/eepp/graphics/ninepatchmanager.cpp ../../src/eepp/graphics/renderer/shaders/base.frag.h diff --git a/src/eepp/graphics/fontbmfont.cpp b/src/eepp/graphics/fontbmfont.cpp index bf3ae590d..1a6bf9d12 100644 --- a/src/eepp/graphics/fontbmfont.cpp +++ b/src/eepp/graphics/fontbmfont.cpp @@ -53,9 +53,9 @@ bool FontBMFont::loadFromFile( const std::string& filename ) { return false; } -bool FontBMFont::loadFromMemory( const void * data, std::size_t sizeInBytes ) { +bool FontBMFont::loadFromMemory(const void * data, std::size_t sizeInBytes , const std::string& imageFileBasePath) { cleanup(); - mFilePath = FileSystem::getCurrentWorkingDirectory(); + mFilePath = imageFileBasePath.empty() ? FileSystem::getCurrentWorkingDirectory() : imageFileBasePath; IOStreamMemory stream( (const char*)data, sizeInBytes ); return loadFromStream( stream ); } @@ -70,6 +70,7 @@ bool FontBMFont::loadFromStream( IOStream& stream ) { std::vector lines = String::split( myfile ); + /** Implementation specification taken from raylib ( LICENSE zlib/libpng ). Copyright (c) Ramon Santamaria (@raysan5) */ if ( lines.size() > 4 ) { const char *searchPoint = NULL; int fontSize; int base; int texWidth; int texHeight; diff --git a/src/eepp/graphics/fontsprite.cpp b/src/eepp/graphics/fontsprite.cpp new file mode 100644 index 000000000..89443ba13 --- /dev/null +++ b/src/eepp/graphics/fontsprite.cpp @@ -0,0 +1,211 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +namespace EE { namespace Graphics { + +FontSprite * FontSprite::New( const std::string FontName ) { + return eeNew( FontSprite, ( FontName ) ); +} + +FontSprite::FontSprite( const std::string FontName ) : + Font( FONT_TYPE_SPRITE, FontName ), + mInfo(), + mFontSize(0) +{} + +void FontSprite::cleanup() { + Texture * texture = mPages[ mFontSize ].texture; + + if ( NULL != texture && TextureFactory::existsSingleton() ) + TextureFactory::instance()->remove( texture->getId() ); + + mPages.clear(); + mFontSize = 0; + mFilePath = ""; +} + +FontSprite::~FontSprite() { + cleanup(); +} + +bool FontSprite::loadFromFile(const std::string& filename , Color key, Uint32 firstChar, int spacing) { + cleanup(); + if ( FileSystem::fileExists( filename ) ) { + mFilePath = FileSystem::fileRemoveFileName( filename ); + IOStreamFile stream( filename ); + return loadFromStream( stream, key, firstChar, spacing ); + } else if ( PackManager::instance()->isFallbackToPacksActive() ) { + std::string path( filename ); + Pack * pack = PackManager::instance()->exists( path ); + + if ( NULL != pack ) { + eePRINTL( "Loading font from pack: %s", path.c_str() ); + + return loadFromPack( pack, path, key, firstChar, spacing ); + } + } + return false; +} + +bool FontSprite::loadFromMemory(const void * data, std::size_t sizeInBytes , Color key, Uint32 firstChar, int spacing) { + cleanup(); + mFilePath = FileSystem::getCurrentWorkingDirectory(); + IOStreamMemory stream( (const char*)data, sizeInBytes ); + return loadFromStream( stream, key, firstChar ); +} + +bool FontSprite::loadFromStream( IOStream& stream, Color key, Uint32 firstChar, int spacing ) { + if ( !stream.isOpen() ) + return false; + + Image img( stream ); + + if ( img.getPixelsPtr() == NULL ) + return false; + + /** Implementation specification taken from raylib ( LICENSE zlib/libpng ). Copyright (c) Ramon Santamaria (@raysan5) */ + int x = 0; + int y = 0; + int w = img.getWidth(); + int h = img.getHeight(); + + for ( y = 0; y < h; y++ ) { + for ( x = 0; x < w; x++ ) { + if ( img.getPixel( x, y ) != key ) + break; + } + + if ( img.getPixel( x, y ) != key ) + break; + } + + int charSpacing = x; + int lineSpacing = y; + int charRowHeight = 0; + + while( img.getPixel( charSpacing, lineSpacing + charRowHeight ) != key ) + charRowHeight++; + + int charHeight = charRowHeight; + int index = 0; + + mFontSize = charHeight; + + int lineToRead = 0; + int xPosToRead = charSpacing; + + GlyphTable& glyphs = mPages[ mFontSize ].glyphs; + + while ( ( lineSpacing + lineToRead * ( charHeight + lineSpacing ) ) < h ) { + while ( xPosToRead < w && img.getPixel( xPosToRead, lineSpacing + lineToRead * ( charHeight + lineSpacing ) ) != key ) { + int charWidth = 0; + + while ( img.getPixel( xPosToRead + charWidth, lineSpacing + lineToRead * ( charHeight + lineSpacing ) ) != key ) + charWidth++; + + Glyph& glyph = glyphs[ firstChar + index ]; + + glyph.textureRect = Rect( xPosToRead, lineSpacing + lineToRead * ( charHeight + lineSpacing ), charWidth, charHeight ); + glyph.advance = charWidth + spacing; + glyph.bounds = Rectf( 0.f, -charHeight, charWidth, charHeight ); + + xPosToRead += (charWidth + charSpacing); + + index++; + } + + lineToRead++; + xPosToRead = charSpacing; + } + + img.createMaskFromColor( Color::Magenta, 0 ); + + Uint32 texId = TextureFactory::instance()->loadFromPixels( img.getPixelsPtr(), img.getWidth(), img.getHeight(), img.getChannels() ); + + mPages[ mFontSize ].texture = TextureFactory::instance()->getTexture( texId ); + + return true; +} + +bool FontSprite::loadFromPack(Pack * pack, std::string filePackPath , Color key, Uint32 firstChar, int spacing) { + cleanup(); + mFilePath = FileSystem::fileRemoveFileName( filePackPath ); + return loadFromStream( *pack->getFileStream( filePackPath ), key, firstChar, spacing ); +} + +const FontSprite::Info& FontSprite::getInfo() const { + return mInfo; +} + +const Glyph &FontSprite::getGlyph(Uint32 codePoint, unsigned int characterSize, bool bold, Float outlineThickness) const { + GlyphTable& glyphs = mPages[characterSize].glyphs; + + GlyphTable::const_iterator it = glyphs.find(codePoint); + + if (it != glyphs.end()) { + return it->second; + } else { + glyphs[ characterSize ] = loadGlyph(codePoint, characterSize, bold, outlineThickness); + return glyphs[ characterSize ]; + } +} + +Glyph FontSprite::loadGlyph(Uint32 codePoint, unsigned int characterSize, bool bold, Float outlineThickness) const { + Glyph glyph; + + GlyphTable& glyphs = mPages[mFontSize].glyphs; + GlyphTable::const_iterator it = glyphs.find(codePoint); + + if (it != glyphs.end()) { + const Glyph& oriGlyph = it->second; + + Float scale = (Float)characterSize / (Float)mFontSize; + + glyph.textureRect = oriGlyph.textureRect; + glyph.bounds = oriGlyph.bounds * scale; + glyph.advance = oriGlyph.advance * scale; + } + + return glyph; +} + +Float FontSprite::getKerning(Uint32 first, Uint32 second, unsigned int characterSize) const { + return 0; +} + +Float FontSprite::getLineSpacing(unsigned int characterSize) const { + return ((Float)characterSize / mFontSize ) * mFontSize; +} + +Uint32 FontSprite::getFontHeight(const Uint32 & characterSize) { + return (Uint32)((Float)characterSize / mFontSize ) * mFontSize; +} + +Float FontSprite::getUnderlinePosition(unsigned int characterSize) const { + return 0.f; +} + +Float FontSprite::getUnderlineThickness(unsigned int characterSize) const { + return 0.f; +} + +Texture * FontSprite::getTexture(unsigned int characterSize) const { + return mPages[ mFontSize ].texture; +} + +FontSprite& FontSprite::operator =(const FontSprite& right) { + FontSprite temp(right); + std::swap(mInfo, temp.mInfo); + std::swap(mPages, temp.mPages); + std::swap(mFilePath, temp.mFilePath); + std::swap(mFontSize, temp.mFontSize); + return *this; +} + +}} diff --git a/src/eepp/graphics/image.cpp b/src/eepp/graphics/image.cpp index cff9d798e..819d46e88 100644 --- a/src/eepp/graphics/image.cpp +++ b/src/eepp/graphics/image.cpp @@ -357,6 +357,42 @@ Image::Image( Pack * Pack, std::string FilePackPath, const unsigned int& forceCh loadFromPack( Pack, FilePackPath ); } +Image::Image( IOStream & stream, const unsigned int& forceChannels ) : + mPixels(NULL), + mWidth(0), + mHeight(0), + mChannels(forceChannels), + mSize(0), + mAvoidFree(false), + mLoadedFromStbi(false) +{ + if ( stream.isOpen() ) { + SafeDataPointer PData( stream.getSize() ); + + stream.read( (char*)PData.Data, PData.DataSize ); + + int w, h, c; + Uint8 * data = stbi_load_from_memory( PData.Data, PData.DataSize, &w, &h, &c, mChannels ); + + if ( NULL != data ) { + mPixels = data; + mWidth = (unsigned int)w; + mHeight = (unsigned int)h; + + if ( STBI_default == mChannels ) + mChannels = (unsigned int)c; + + mSize = mWidth * mHeight * mChannels; + + mLoadedFromStbi = true; + } else { + eePRINTL( "Failed to load image. Reason: %s", stbi_failure_reason() ); + } + } else { + eePRINTL( "Failed to load image from stream." ); + } +} + Image::~Image() { if ( !mAvoidFree ) clearCache(); @@ -383,7 +419,7 @@ void Image::loadFromPack( Pack * Pack, const std::string& FilePackPath ) { mLoadedFromStbi = true; } else { - eePRINTL( "Failed to load image %s. Reason: %s", stbi_failure_reason(), FilePackPath.c_str() ); + eePRINTL( "Failed to load image %s. Reason: %s", FilePackPath.c_str(), stbi_failure_reason() ); } } else { eePRINTL( "Failed to load image %s from pack.", FilePackPath.c_str() ); diff --git a/src/examples/fonts/fonts.cpp b/src/examples/fonts/fonts.cpp index 51878ea30..60c6a3a96 100644 --- a/src/examples/fonts/fonts.cpp +++ b/src/examples/fonts/fonts.cpp @@ -4,10 +4,12 @@ EE::Window::Window * win = NULL; FontTrueType * fontTest; FontTrueType * fontTest2; FontBMFont * fontBMFont; +FontSprite * fontSprite; Text text; Text text2; Text text3; Text text4; +Text text5; void mainLoop() { // Clear the screen buffer @@ -33,6 +35,8 @@ void mainLoop() { text4.draw( ( win->getWidth() - text4.getTextWidth() ) * 0.5f, 590 ); + text5.draw( ( win->getWidth() - text5.getTextWidth() ) * 0.5f, 640 ); + // Draw frame win->display(); } @@ -92,6 +96,14 @@ EE_MAIN_FUNC int main (int argc, char * argv []) { text4.setCharacterSize( 45 ); text4.setFillColor( Color::Black ); + fontSprite = FontSprite::New( "alagard" ); // Alagard - Hewett Tsoi ( https://www.dafont.com/alagard.font ) + fontSprite->loadFromFile( "assets/fonts/custom_alagard.png", Color::Magenta, 32, -4 ); + + text5.setFont( fontSprite ); + text5.setString( "Lorem ipsum dolor sit amet, consectetur adipisicing elit." ); + text5.setCharacterSize( 38 ); + //text5.setFillColor( Color::Black ); + // Application loop win->runMainLoop( &mainLoop ); }