diff --git a/bin/assets/fonts/custom_alagard.png b/bin/assets/fonts/custom_alagard.png new file mode 100644 index 000000000..d63f00e6b Binary files /dev/null and b/bin/assets/fonts/custom_alagard.png differ 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 ); }