diff --git a/include/eepp/graphics/font.hpp b/include/eepp/graphics/font.hpp index 30eacd4cb..319ca802b 100755 --- a/include/eepp/graphics/font.hpp +++ b/include/eepp/graphics/font.hpp @@ -22,12 +22,6 @@ class EE_API Font { /** @return The recommended line spacing */ Int32 getLineSkip() const; - /** @return The font highest ascent (height above base) */ - Int32 getFontAscent() const; - - /** @return The font lowest descent (height below base) */ - Int32 getFontDescent() const; - /** Shrink the String to a max width * @param Str The string to shrink * @param MaxWidth The Max Width posible @@ -67,7 +61,7 @@ class EE_API Font { /** @return The cursor position inside the string */ Vector2i getCursorPos( const String& Text, const Uint32& Pos ); - const Glyph& getGlyph( const Uint32& index ); + const GlyphData& getGlyph( const Uint32& index ); const TextureCoords& getTextureCoords( const Uint32& index ); @@ -83,7 +77,7 @@ class EE_API Font { Int32 mAscent; Int32 mDescent; - std::vector mGlyphs; + std::vector mGlyphs; std::vector mTexCoords; TextCache mTextCache; diff --git a/include/eepp/graphics/fonthelper.hpp b/include/eepp/graphics/fonthelper.hpp index 653bcfa24..c87aef340 100644 --- a/include/eepp/graphics/fonthelper.hpp +++ b/include/eepp/graphics/fonthelper.hpp @@ -36,7 +36,7 @@ inline Uint32 fontVAlignGet( Uint32 Flags ) { #define FONT_DRAW_ALIGN_MASK ( FONT_DRAW_VALIGN_MASK | FONT_DRAW_HALIGN_MASK ) /** Basic Glyph structure used by the engine */ -struct Glyph { +struct GlyphData { Int32 MinX, MaxX, MinY, MaxY, Advance; Uint16 CurX, CurY, CurW, CurH, GlyphH; }; @@ -46,6 +46,11 @@ struct TextureCoords { Float Vertex[8]; }; +struct VertexCoords { + Float TexCoords[2]; + Float Vertex[2]; +}; + typedef struct sFntHdrS { Uint32 Magic; Uint32 FirstChar; diff --git a/include/eepp/graphics/fonttruetype.hpp b/include/eepp/graphics/fonttruetype.hpp new file mode 100644 index 000000000..2cf92ea54 --- /dev/null +++ b/include/eepp/graphics/fonttruetype.hpp @@ -0,0 +1,133 @@ +#ifndef EE_GRAPHICS_FONTTRUETYPE_HPP +#define EE_GRAPHICS_FONTTRUETYPE_HPP + +#include +#include +#include +#include +#include + +namespace EE { namespace System { +class IOStream; +}} + +namespace EE { namespace Graphics { + +class EE_API Glyph +{ + public: + Glyph() : advance(0) {} + + Float advance; ///< Offset to move horizontally to the next character + Rectf bounds; ///< Bounding rectangle of the glyph, in coordinates relative to the baseline + Recti textureRect; ///< Texture coordinates of the glyph inside the font's texture +}; + +class EE_API FontTrueType +{ + public: + /** Shrink the String to a max width + * @param Str The string to shrink + * @param MaxWidth The Max Width posible + */ + void shrinkText( String& Str, const Uint32& characterSize, bool bold, Float outlineThickness, const Uint32& MaxWidth ); + + /** Shrink the string to a max width + * @param Str The string to shrink + * @param MaxWidth The Max Width posible + */ + void shrinkText( std::string& Str, const Uint32& characterSize, bool bold, Float outlineThickness, const Uint32& MaxWidth ); + + /** Cache the with of the current text */ + void cacheWidth( const String& Text, const Uint32& characterSize, bool bold, Float outlineThickness, std::vector& LinesWidth, Float& CachedWidth, int& NumLines, int& LargestLineCharCount ); + + /** Finds the closest cursor position to the point position */ + Int32 findClosestCursorPosFromPoint( const String& Text, const Uint32& characterSize, bool bold, Float outlineThickness, const Vector2i& pos ); + + /** Simulates a selection request and return the initial and end cursor position when the selection worked. Otherwise both parameters will be -1. */ + void selectSubStringFromCursor( const String& Text, const Uint32& characterSize, bool bold, Float outlineThickness, const Int32& CurPos, Int32& InitCur, Int32& EndCur ); + + /** @return The cursor position inside the string */ + Vector2i getCursorPos( const String& Text, const Uint32& characterSize, bool bold, Float outlineThickness, const Uint32& Pos ); + + public: + struct Info + { + std::string family; ///< The font family + }; + + FontTrueType(); + + FontTrueType(const FontTrueType& copy); + + ~FontTrueType(); + + bool loadFromFile(const std::string& filename); + + bool loadFromMemory(const void* data, std::size_t sizeInBytes); + + bool loadFromStream(IOStream& stream); + + const 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; + + Float getUnderlinePosition(unsigned int characterSize) const; + + Float getUnderlineThickness(unsigned int characterSize) const; + + Texture * getTexture(unsigned int characterSize) const; + + FontTrueType& operator =(const FontTrueType& right); + + private: + struct Row + { + Row(unsigned int rowTop, unsigned int rowHeight) : width(0), top(rowTop), height(rowHeight) {} + + unsigned int width; ///< Current width of the row + unsigned int top; ///< Y position of the row into the texture + unsigned int height; ///< Height of the row + }; + + typedef std::map GlyphTable; ///< Table mapping a codepoint to its glyph + + struct Page + { + Page(); + + ~Page(); + + GlyphTable glyphs; ///< Table mapping code points to their corresponding glyph + Texture * texture; ///< Texture containing the pixels of the glyphs + unsigned int nextRow; ///< Y position of the next new row in the texture + std::vector rows; ///< List containing the position of all the existing rows + }; + + void cleanup(); + + Glyph loadGlyph(Uint32 codePoint, unsigned int characterSize, bool bold, Float outlineThickness) const; + + Recti findGlyphRect(Page& page, unsigned int width, unsigned int height) const; + + bool setCurrentSize(unsigned int characterSize) const; + + typedef std::map PageTable; ///< Table mapping a character size to its page (texture) + + void* mLibrary; ///< Pointer to the internal library interface (it is typeless to avoid exposing implementation details) + void* mFace; ///< Pointer to the internal font face (it is typeless to avoid exposing implementation details) + void* mStreamRec; ///< Pointer to the stream rec instance (it is typeless to avoid exposing implementation details) + void* mStroker; ///< Pointer to the stroker (it is typeless to avoid exposing implementation details) + int* mRefCount; ///< Reference counter used by implicit sharing + Info mInfo; ///< Information about the font + mutable PageTable mPages; ///< Table containing the glyphs pages by character size + mutable std::vector mPixelBuffer; ///< Pixel buffer holding a glyph's pixels before being written to the texture +}; + +}} + +#endif diff --git a/include/eepp/graphics/text.hpp b/include/eepp/graphics/text.hpp new file mode 100644 index 000000000..8ea9b0300 --- /dev/null +++ b/include/eepp/graphics/text.hpp @@ -0,0 +1,126 @@ +#ifndef EE_GRAPHICS_TEXT_HPP +#define EE_GRAPHICS_TEXT_HPP + +#include +#include +#include +#include + +namespace EE { namespace Graphics { + +class EE_API Text { + public: + enum Style + { + Regular = 0, ///< Regular characters, no style + Bold = 1 << 0, ///< Bold characters + Italic = 1 << 1, ///< Italic characters + Underlined = 1 << 2, ///< Underlined characters + StrikeThrough = 1 << 3 ///< Strike through characters + }; + + Text(); + + Text(const String& string, FontTrueType * font, unsigned int characterSize = 30); + + void setText(const String& string); + + void setFontTrueType(FontTrueType * font); + + void setCharacterSize(unsigned int size); + + void setStyle(Uint32 style); + + void setColor(const ColorA& color); + + void setFillColor(const ColorA& color); + + void setOutlineColor(const ColorA& color); + + void setOutlineThickness(Float thickness); + + const String& getText() const; + + const FontTrueType* getFontTrueType() const; + + unsigned int getCharacterSize() const; + + Uint32 getStyle() const; + + /** @see Set the alpha of each individual character. + ** This doesn't break any custom color per-character setted. */ + void setAlpha( const Uint8& alpha ); + + const ColorA& getFillColor() const; + + const ColorA& getOutlineColor() const; + + Float getOutlineThickness() const; + + Vector2f findCharacterPos(std::size_t index) const; + + Rectf getLocalBounds(); + + /** @return The cached text width */ + Float getTextWidth(); + + /** @return The cached text height */ + Float getTextHeight(); + + /** Draw the cached text on screen */ + void draw( const Float& X, const Float& Y, const Vector2f& Scale = Vector2f::One, const Float& Angle = 0, EE_BLEND_MODE Effect = ALPHA_NORMAL ); + + /** @return The Shadow Font Color */ + const ColorA& getShadowColor() const; + + /** Set the shadow color of the string rendered */ + void setShadowColor(const ColorA& color); + + /** @return Every cached text line width */ + const std::vector& getLinesWidth(); + + /** Set the font draw flags */ + void setFlags( const Uint32& flags ); + + /** @return The font draw flags */ + const Uint32& getFlags() const; + + /** @return The number of lines that the cached text contains */ + const int& getNumLines() const; + + private: + void ensureGeometryUpdate(); + + String mString; ///< String to display + FontTrueType * mFont; ///< FontTrueType used to display the string + unsigned int mCharacterSize; ///< Base size of characters, in pixels + Uint32 mStyle; ///< Text style (see Style enum) + ColorA mFillColor; ///< Text fill color + ColorA mOutlineColor; ///< Text outline color + Float mOutlineThickness; ///< Thickness of the text's outline + Sizei mTextureSize; + + mutable Rectf mBounds; ///< Bounding rectangle of the text (in local coordinates) + mutable bool mGeometryNeedUpdate; ///< Does the geometry need to be recomputed? + + Float mCachedWidth; + int mNumLines; + int mLargestLineCharCount; + ColorA mFontShadowColor; + Uint32 mFlags; + + std::vector mVertices; + std::vector mColors; + + std::vector mOutlineVertices; + std::vector mOutlineColors; + std::vector mLinesWidth; + + /** Force to cache the width of the current text */ + void cacheWidth(); +}; + +}} + + +#endif diff --git a/include/eepp/graphics/textcache.hpp b/include/eepp/graphics/textcache.hpp index bc7b78a1c..c8a4ad975 100644 --- a/include/eepp/graphics/textcache.hpp +++ b/include/eepp/graphics/textcache.hpp @@ -83,11 +83,6 @@ class EE_API TextCache { /** Force to cache the width of the current text */ void cacheWidth(); protected: - struct VertexCoords { - Float TexCoords[2]; - Float Vertex[2]; - }; - friend class Font; String mText; diff --git a/projects/linux/ee.files b/projects/linux/ee.files index 24811ef61..069851b88 100644 --- a/projects/linux/ee.files +++ b/projects/linux/ee.files @@ -1,4 +1,6 @@ ../../include/eepp/graphics/font.hpp +../../include/eepp/graphics/fonttruetype.hpp +../../include/eepp/graphics/text.hpp ../../include/eepp/math/interpolation1d.hpp ../../include/eepp/math/interpolation2d.hpp ../../include/eepp/ui/uidragablecontrol.hpp @@ -13,9 +15,11 @@ ../../include/eepp/ui/uithemedefault.hpp ../../include/eepp/ui/uiwidget.hpp ../../src/eepp/gaming/mapobjectlayer.cpp +../../src/eepp/graphics/fonttruetype.cpp ../../src/eepp/graphics/globalbatchrenderer.cpp ../../src/eepp/graphics/pixeldensity.cpp ../../src/eepp/graphics/pixelperfect.cpp +../../src/eepp/graphics/text.cpp ../../src/eepp/math/interpolation1d.cpp ../../src/eepp/math/interpolation2d.cpp ../../src/eepp/ui/uidragablecontrol.cpp diff --git a/src/eepp/graphics/font.cpp b/src/eepp/graphics/font.cpp index 06e3a050a..ee8b295ba 100644 --- a/src/eepp/graphics/font.cpp +++ b/src/eepp/graphics/font.cpp @@ -38,14 +38,6 @@ Int32 Font::getLineSkip() const { return mLineSkip; } -Int32 Font::getFontAscent() const { - return mAscent; -} - -Int32 Font::getFontDescent() const { - return mDescent; -} - void Font::cacheWidth( const String& Text, std::vector& LinesWidth, Float& CachedWidth, int& NumLines , int& LargestLineCharCount ) { LinesWidth.clear(); @@ -177,7 +169,7 @@ Vector2i Font::getCursorPos( const String& Text, const Uint32& Pos ) { return Vector2i( Width, Height ); } -const Glyph& Font::getGlyph(const Uint32 & index) { +const GlyphData& Font::getGlyph(const Uint32 & index) { eeASSERT( index < mGlyphs.size() ); return mGlyphs[ index ]; } @@ -243,7 +235,7 @@ void Font::shrinkText( std::string& Str, const Uint32& MaxWidth ) { while ( *tChar ) { if ( (Uint32)( *tChar ) < tGlyphSize ) { - Glyph * pChar = &mGlyphs[ ( *tChar ) ]; + GlyphData * pChar = &mGlyphs[ ( *tChar ) ]; Float fCharWidth = (Float)pChar->Advance; if ( ( *tChar ) == '\t' ) @@ -299,7 +291,7 @@ void Font::shrinkText( String& Str, const Uint32& MaxWidth ) { while ( *tChar ) { if ( (String::StringBaseType)( *tChar ) < mGlyphs.size() ) { - Glyph * pChar = &mGlyphs[ ( *tChar ) ]; + GlyphData * pChar = &mGlyphs[ ( *tChar ) ]; Float fCharWidth = (Float)pChar->Advance; if ( ( *tChar ) == '\t' ) diff --git a/src/eepp/graphics/fonttruetype.cpp b/src/eepp/graphics/fonttruetype.cpp new file mode 100644 index 000000000..1c1c39983 --- /dev/null +++ b/src/eepp/graphics/fonttruetype.cpp @@ -0,0 +1,894 @@ +#include +#include +#include + +#include +#include FT_FREETYPE_H +#include FT_GLYPH_H +#include FT_OUTLINE_H +#include FT_BITMAP_H +#include FT_STROKER_H +#include +#include + +namespace { + // FreeType callbacks that operate on a IOStream + unsigned long read(FT_Stream rec, unsigned long offset, unsigned char* buffer, unsigned long count) { + IOStream* stream = static_cast(rec->descriptor.pointer); + if (static_cast(stream->seek(offset)) == offset) + { + if (count > 0) + return static_cast(stream->read(reinterpret_cast(buffer), count)); + else + return 0; + } + else + return count > 0 ? 0 : 1; // error code is 0 if we're reading, or nonzero if we're seeking + } + void close(FT_Stream) { + } +} + +namespace EE { namespace Graphics { + +FontTrueType::FontTrueType() : + mLibrary (NULL), + mFace (NULL), + mStreamRec(NULL), + mStroker (NULL), + mRefCount (NULL), + mInfo () +{ +} + +FontTrueType::FontTrueType(const FontTrueType& copy) : + mLibrary (copy.mLibrary), + mFace (copy.mFace), + mStreamRec (copy.mStreamRec), + mStroker (copy.mStroker), + mRefCount (copy.mRefCount), + mInfo (copy.mInfo), + mPages (copy.mPages), + mPixelBuffer(copy.mPixelBuffer) +{ + if (mRefCount) + (*mRefCount)++; +} + +FontTrueType::~FontTrueType() { + cleanup(); +} + +bool FontTrueType::loadFromFile(const std::string& filename) { + // Cleanup the previous resources + cleanup(); + mRefCount = new int(1); + + // Initialize FreeType + FT_Library library; + if (FT_Init_FreeType(&library) != 0) { + std::cout << "Failed to load font \"" << filename << "\" (failed to initialize FreeType)" << std::endl; + return false; + } + mLibrary = library; + + // Load the new font face from the specified file + FT_Face face; + if (FT_New_Face(static_cast(mLibrary), filename.c_str(), 0, &face) != 0) { + std::cout << "Failed to load font \"" << filename << "\" (failed to create the font face)" << std::endl; + return false; + } + + // Load the stroker that will be used to outline the font + FT_Stroker stroker; + if (FT_Stroker_New(static_cast(mLibrary), &stroker) != 0) { + std::cout << "Failed to load font \"" << filename << "\" (failed to create the stroker)" << std::endl; + return false; + } + mStroker = stroker; + + // Select the unicode character map + if (FT_Select_Charmap(face, FT_ENCODING_UNICODE) != 0) { + std::cout << "Failed to load font \"" << filename << "\" (failed to set the Unicode character set)" << std::endl; + FT_Done_Face(face); + return false; + } + + // Store the loaded font in our ugly void* :) + mFace = face; + + // Store the font information + mInfo.family = face->family_name ? face->family_name : std::string(); + + return true; +} + +bool FontTrueType::loadFromMemory(const void* data, std::size_t sizeInBytes) { + // Cleanup the previous resources + cleanup(); + mRefCount = new int(1); + + // Initialize FreeType + FT_Library library; + if (FT_Init_FreeType(&library) != 0) { + std::cout << "Failed to load font from memory (failed to initialize FreeType)" << std::endl; + return false; + } + mLibrary = library; + + // Load the new font face from the specified file + FT_Face face; + if (FT_New_Memory_Face(static_cast(mLibrary), reinterpret_cast(data), static_cast(sizeInBytes), 0, &face) != 0) { + std::cout << "Failed to load font from memory (failed to create the font face)" << std::endl; + return false; + } + + // Load the stroker that will be used to outline the font + FT_Stroker stroker; + if (FT_Stroker_New(static_cast(mLibrary), &stroker) != 0) { + std::cout << "Failed to load font from memory (failed to create the stroker)" << std::endl; + return false; + } + mStroker = stroker; + + // Select the Unicode character map + if (FT_Select_Charmap(face, FT_ENCODING_UNICODE) != 0) { + std::cout << "Failed to load font from memory (failed to set the Unicode character set)" << std::endl; + FT_Done_Face(face); + return false; + } + + // Store the loaded font in our ugly void* :) + mFace = face; + + // Store the font information + mInfo.family = face->family_name ? face->family_name : std::string(); + + return true; +} + +bool FontTrueType::loadFromStream(IOStream& stream) { + // Cleanup the previous resources + cleanup(); + mRefCount = new int(1); + + // Initialize FreeType + FT_Library library; + if (FT_Init_FreeType(&library) != 0) { + std::cout << "Failed to load font from stream (failed to initialize FreeType)" << std::endl; + return false; + } + mLibrary = library; + + // Make sure that the stream's reading position is at the beginning + stream.seek(0); + + // Prepare a wrapper for our stream, that we'll pass to FreeType callbacks + FT_StreamRec* rec = new FT_StreamRec; + std::memset(rec, 0, sizeof(*rec)); + rec->base = NULL; + rec->size = static_cast(stream.getSize()); + rec->pos = 0; + rec->descriptor.pointer = &stream; + rec->read = &read; + rec->close = &close; + + // Setup the FreeType callbacks that will read our stream + FT_Open_Args args; + args.flags = FT_OPEN_STREAM; + args.stream = rec; + args.driver = 0; + + // Load the new font face from the specified stream + FT_Face face; + if (FT_Open_Face(static_cast(mLibrary), &args, 0, &face) != 0) { + std::cout << "Failed to load font from stream (failed to create the font face)" << std::endl; + delete rec; + return false; + } + + // Load the stroker that will be used to outline the font + FT_Stroker stroker; + if (FT_Stroker_New(static_cast(mLibrary), &stroker) != 0) { + std::cout << "Failed to load font from stream (failed to create the stroker)" << std::endl; + return false; + } + mStroker = stroker; + + // Select the Unicode character map + if (FT_Select_Charmap(face, FT_ENCODING_UNICODE) != 0) { + std::cout << "Failed to load font from stream (failed to set the Unicode character set)" << std::endl; + FT_Done_Face(face); + delete rec; + return false; + } + + // Store the loaded font in our ugly void* :) + mFace = face; + mStreamRec = rec; + + // Store the font information + mInfo.family = face->family_name ? face->family_name : std::string(); + + return true; +} + +const FontTrueType::Info& FontTrueType::getInfo() const { + return mInfo; +} + +const Glyph& FontTrueType::getGlyph(Uint32 codePoint, unsigned int characterSize, bool bold, Float outlineThickness) const { + // Get the page corresponding to the character size + GlyphTable& glyphs = mPages[characterSize].glyphs; + + // Build the key by combining the code point, bold flag, and outline thickness + Uint64 key = (static_cast(*reinterpret_cast(&outlineThickness)) << 32) + | (static_cast(bold ? 1 : 0) << 31) + | static_cast(codePoint); + + // Search the glyph into the cache + GlyphTable::const_iterator it = glyphs.find(key); + if (it != glyphs.end()) { + // Found: just return it + return it->second; + } else { + // Not found: we have to load it + Glyph glyph = loadGlyph(codePoint, characterSize, bold, outlineThickness); + return glyphs.insert(std::make_pair(key, glyph)).first->second; + } +} + +Float FontTrueType::getKerning(Uint32 first, Uint32 second, unsigned int characterSize) const { + // Special case where first or second is 0 (null character) + if (first == 0 || second == 0) + return 0.f; + + FT_Face face = static_cast(mFace); + + if (face && FT_HAS_KERNING(face) && setCurrentSize(characterSize)) { + // Convert the characters to indices + FT_UInt index1 = FT_Get_Char_Index(face, first); + FT_UInt index2 = FT_Get_Char_Index(face, second); + + // Get the kerning vector + FT_Vector kerning; + FT_Get_Kerning(face, index1, index2, FT_KERNING_DEFAULT, &kerning); + + // X advance is already in pixels for bitmap fonts + if (!FT_IS_SCALABLE(face)) + return static_cast(kerning.x); + + // Return the X advance + return static_cast(kerning.x) / static_cast(1 << 6); + } else { + // Invalid font, or no kerning + return 0.f; + } +} + +Float FontTrueType::getLineSpacing(unsigned int characterSize) const { + FT_Face face = static_cast(mFace); + + if (face && setCurrentSize(characterSize)) { + return static_cast(face->size->metrics.height) / static_cast(1 << 6); + } else { + return 0.f; + } +} + +Float FontTrueType::getUnderlinePosition(unsigned int characterSize) const { + FT_Face face = static_cast(mFace); + + if (face && setCurrentSize(characterSize)) { + // Return a fixed position if font is a bitmap font + if (!FT_IS_SCALABLE(face)) + return characterSize / 10.f; + + return -static_cast(FT_MulFix(face->underline_position, face->size->metrics.y_scale)) / static_cast(1 << 6); + } else { + return 0.f; + } +} + +Float FontTrueType::getUnderlineThickness(unsigned int characterSize) const { + FT_Face face = static_cast(mFace); + + if (face && setCurrentSize(characterSize)) { + // Return a fixed thickness if font is a bitmap font + if (!FT_IS_SCALABLE(face)) + return characterSize / 14.f; + + return static_cast(FT_MulFix(face->underline_thickness, face->size->metrics.y_scale)) / static_cast(1 << 6); + } + else { + return 0.f; + } +} + +Texture* FontTrueType::getTexture(unsigned int characterSize) const { + return mPages[characterSize].texture; +} + +FontTrueType& FontTrueType::operator =(const FontTrueType& right) { + FontTrueType temp(right); + + std::swap(mLibrary, temp.mLibrary); + std::swap(mFace, temp.mFace); + std::swap(mStreamRec, temp.mStreamRec); + std::swap(mStroker, temp.mStroker); + std::swap(mRefCount, temp.mRefCount); + std::swap(mInfo, temp.mInfo); + std::swap(mPages, temp.mPages); + std::swap(mPixelBuffer, temp.mPixelBuffer); + return *this; +} + +void FontTrueType::cleanup() { + // Check if we must destroy the FreeType pointers + if (mRefCount) { + // Decrease the reference counter + (*mRefCount)--; + + // Free the resources only if we are the last owner + if (*mRefCount == 0) + { + // Delete the reference counter + delete mRefCount; + + // Destroy the stroker + if (mStroker) + FT_Stroker_Done(static_cast(mStroker)); + + // Destroy the font face + if (mFace) + FT_Done_Face(static_cast(mFace)); + + // Destroy the stream rec instance, if any (must be done after FT_Done_Face!) + if (mStreamRec) + delete static_cast(mStreamRec); + + // Close the library + if (mLibrary) + FT_Done_FreeType(static_cast(mLibrary)); + } + } + + // Reset members + mLibrary = NULL; + mFace = NULL; + mStroker = NULL; + mStreamRec = NULL; + mRefCount = NULL; + mPages.clear(); + mPixelBuffer.clear(); +} + +Glyph FontTrueType::loadGlyph(Uint32 codePoint, unsigned int characterSize, bool bold, Float outlineThickness) const { + // The glyph to return + Glyph glyph; + + // First, transform our ugly void* to a FT_Face + FT_Face face = static_cast(mFace); + if (!face) + return glyph; + + // Set the character size + if (!setCurrentSize(characterSize)) + return glyph; + + // Load the glyph corresponding to the code point + FT_Int32 flags = FT_LOAD_TARGET_NORMAL | FT_LOAD_FORCE_AUTOHINT; + if (outlineThickness != 0) + flags |= FT_LOAD_NO_BITMAP; + if (FT_Load_Char(face, codePoint, flags) != 0) + return glyph; + + // Retrieve the glyph + FT_Glyph glyphDesc; + if (FT_Get_Glyph(face->glyph, &glyphDesc) != 0) + return glyph; + + // Apply bold and outline (there is no fallback for outline) if necessary -- first technique using outline (highest quality) + FT_Pos weight = 1 << 6; + bool outline = (glyphDesc->format == FT_GLYPH_FORMAT_OUTLINE); + if (outline) { + if (bold) + { + FT_OutlineGlyph outlineGlyph = (FT_OutlineGlyph)glyphDesc; + FT_Outline_Embolden(&outlineGlyph->outline, weight); + } + + if (outlineThickness != 0) + { + FT_Stroker stroker = static_cast(mStroker); + + FT_Stroker_Set(stroker, static_cast(outlineThickness * static_cast(1 << 6)), FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0); + FT_Glyph_Stroke(&glyphDesc, stroker, false); + } + } + + // Convert the glyph to a bitmap (i.e. rasterize it) + FT_Glyph_To_Bitmap(&glyphDesc, FT_RENDER_MODE_NORMAL, 0, 1); + FT_Bitmap& bitmap = reinterpret_cast(glyphDesc)->bitmap; + + // Apply bold if necessary -- fallback technique using bitmap (lower quality) + if (!outline) { + if (bold) + FT_Bitmap_Embolden(static_cast(mLibrary), &bitmap, weight, weight); + + if (outlineThickness != 0) + std::cout << "Failed to outline glyph (no fallback available)" << std::endl; + } + + // Compute the glyph's advance offset + glyph.advance = static_cast(face->glyph->metrics.horiAdvance) / static_cast(1 << 6); + if (bold) + glyph.advance += static_cast(weight) / static_cast(1 << 6); + + int width = bitmap.width; + int height = bitmap.rows; + + if ((width > 0) && (height > 0)) { + // Leave a small padding around characters, so that filtering doesn't + // pollute them with pixels from neighbors + const unsigned int padding = 1; + + // Get the glyphs page corresponding to the character size + Page& page = mPages[characterSize]; + + // Find a good position for the new glyph into the texture + glyph.textureRect = findGlyphRect(page, width + 2 * padding, height + 2 * padding); + + // Make sure the texture data is positioned in the center + // of the allocated texture rectangle + glyph.textureRect.Left += padding; + glyph.textureRect.Top += padding; + glyph.textureRect.Right -= 2 * padding; + glyph.textureRect.Bottom -= 2 * padding; + + // Compute the glyph's bounding box + glyph.bounds.Left = static_cast(face->glyph->metrics.horiBearingX) / static_cast(1 << 6); + glyph.bounds.Top = -static_cast(face->glyph->metrics.horiBearingY) / static_cast(1 << 6); + glyph.bounds.Right = static_cast(face->glyph->metrics.width) / static_cast(1 << 6) + outlineThickness * 2; + glyph.bounds.Bottom = static_cast(face->glyph->metrics.height) / static_cast(1 << 6) + outlineThickness * 2; + + // Extract the glyph's pixels from the bitmap + mPixelBuffer.resize(width * height * 4, 255); + const Uint8* pixels = bitmap.buffer; + if (bitmap.pixel_mode == FT_PIXEL_MODE_MONO) + { + // Pixels are 1 bit monochrome values + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + // The color channels remain white, just fill the alpha channel + std::size_t index = (x + y * width) * 4 + 3; + mPixelBuffer[index] = ((pixels[x / 8]) & (1 << (7 - (x % 8)))) ? 255 : 0; + } + pixels += bitmap.pitch; + } + } + else + { + // Pixels are 8 bits gray levels + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + // The color channels remain white, just fill the alpha channel + std::size_t index = (x + y * width) * 4 + 3; + mPixelBuffer[index] = pixels[x]; + } + pixels += bitmap.pitch; + } + } + + // Write the pixels to the texture + unsigned int x = glyph.textureRect.Left; + unsigned int y = glyph.textureRect.Top; + unsigned int w = glyph.textureRect.Right; + unsigned int h = glyph.textureRect.Bottom; + page.texture->update(&mPixelBuffer[0], w, h, x, y); + } + + // Delete the FT glyph + FT_Done_Glyph(glyphDesc); + + // Done :) + return glyph; +} + +Recti FontTrueType::findGlyphRect(Page& page, unsigned int width, unsigned int height) const { + // Find the line that fits well the glyph + Row* row = NULL; + Float bestRatio = 0; + for (std::vector::iterator it = page.rows.begin(); it != page.rows.end() && !row; ++it) { + Float ratio = static_cast(height) / it->height; + + // Ignore rows that are either too small or too high + if ((ratio < 0.7f) || (ratio > 1.f)) + continue; + + // Check if there's enough horizontal space left in the row + if (width > page.texture->getSize().x - it->width) + continue; + + // Make sure that this new row is the best found so far + if (ratio < bestRatio) + continue; + + // The current row passed all the tests: we can select it + row = &*it; + bestRatio = ratio; + } + + // If we didn't find a matching row, create a new one (10% taller than the glyph) + if (!row) { + int rowHeight = height + height / 10; + while ((page.nextRow + rowHeight >= (Uint32)page.texture->getSize().y) || (width >= (Uint32)page.texture->getSize().x)) + { + // Not enough space: resize the texture if possible + unsigned int textureWidth = page.texture->getSize().x; + unsigned int textureHeight = page.texture->getSize().y; + if ( ( textureWidth * 2 <= Texture::getMaximumSize()) && (textureHeight * 2 <= Texture::getMaximumSize() ) ) { + // Make the texture 2 times bigger + + //page.texture->lock(); + Image newImage; + newImage.create(textureWidth * 2, textureHeight * 2, 4); + newImage.copyImage(page.texture, 0, 0); + //page.texture->unlock(); + + page.texture->replace(&newImage); } else { + // Oops, we've reached the maximum texture size... + std::cout << "Failed to add a new character to the font: the maximum texture size has been reached" << std::endl; + return Recti(0, 0, 2, 2); + } + } + + // We can now create the new row + page.rows.push_back(Row(page.nextRow, rowHeight)); + page.nextRow += rowHeight; + row = &page.rows.back(); + } + + // Find the glyph's rectangle on the selected row + Recti rect(row->width, row->top, width, height); + + // Update the row informations + row->width += width; + + return rect; +} + +bool FontTrueType::setCurrentSize(unsigned int characterSize) const { + // FT_Set_Pixel_Sizes is an expensive function, so we must call it + // only when necessary to avoid killing performances + + FT_Face face = static_cast(mFace); + FT_UShort currentSize = face->size->metrics.x_ppem; + + if (currentSize != characterSize) { + FT_Error result = FT_Set_Pixel_Sizes(face, 0, characterSize); + + if (result == FT_Err_Invalid_Pixel_Size) + { + // In the case of bitmap fonts, resizing can + // fail if the requested size is not available + if (!FT_IS_SCALABLE(face)) + { + std::cout << "Failed to set bitmap font size to " << characterSize << std::endl; + std::cout << "Available sizes are: "; + for (int i = 0; i < face->num_fixed_sizes; ++i) + std::cout << face->available_sizes[i].height << " "; + std::cout << std::endl; + } + } + + return result == FT_Err_Ok; + } else { + return true; + } +} + +FontTrueType::Page::Page() : + texture(NULL), + nextRow(3) +{ + // Make sure that the texture is initialized by default + Image image; + image.create(128, 128, 4); + + // Reserve a 2x2 white square for texturing underlines + for (int x = 0; x < 2; ++x) + for (int y = 0; y < 2; ++y) + image.setPixel(x, y, ColorA(255, 255, 255, 255)); + + // Create the texture + Uint32 texId = TextureFactory::instance()->loadFromPixels( image.getPixelsPtr(), image.getWidth(), image.getHeight(), image.getChannels(), false, CLAMP_TO_EDGE, false, true ); + texture = TextureFactory::instance()->getTexture( texId ); +} + +FontTrueType::Page::~Page() { + if ( NULL != texture ) + TextureFactory::instance()->remove( texture->getId() ); +} + +void FontTrueType::cacheWidth( const String& Text, const Uint32& characterSize, bool bold, Float outlineThickness, std::vector& LinesWidth, Float& CachedWidth, int& NumLines , int& LargestLineCharCount ) { + LinesWidth.clear(); + + Float Width = 0, MaxWidth = 0; + Int32 CharID; + Int32 Lines = 1; + Int32 CharCount = 0; + LargestLineCharCount = 0; + + for (std::size_t i = 0; i < Text.size(); ++i) { + CharID = static_cast( Text.at(i) ); + Glyph glyph = getGlyph( CharID, characterSize, bold, outlineThickness ); + + Width += glyph.advance; + + CharCount++; + + if ( CharID == '\t' ) + Width += glyph.advance * 3; + + if ( CharID == '\n' ) { + Lines++; + + Float lWidth = ( CharID == '\t' ) ? glyph.advance * 4.f : glyph.advance; + + LinesWidth.push_back( Width - lWidth ); + + Width = 0; + + CharCount = 0; + } else { + if ( CharCount > LargestLineCharCount ) + LargestLineCharCount = CharCount; + } + + if ( Width > MaxWidth ) + MaxWidth = Width; + } + + if ( Text.size() && Text.at( Text.size() - 1 ) != '\n' ) { + LinesWidth.push_back( Width ); + } + + CachedWidth = MaxWidth; + NumLines = Lines; +} + +Int32 FontTrueType::findClosestCursorPosFromPoint( const String& Text, const Uint32& characterSize, bool bold, Float outlineThickness, const Vector2i& pos ) { + Float Width = 0, lWidth = 0, Height = getLineSpacing(characterSize), lHeight = 0; + Int32 CharID; + std::size_t tSize = Text.size(); + + for (std::size_t i = 0; i < tSize; ++i) { + CharID = static_cast( Text.at(i) ); + Glyph glyph = getGlyph( CharID, characterSize, bold, outlineThickness ); + + lWidth = Width; + + Width += glyph.advance; + + if ( CharID == '\t' ) { + Width += glyph.advance * 3; + } + + if ( CharID == '\n' ) { + lWidth = 0; + Width = 0; + } + + if ( pos.x <= Width && pos.x >= lWidth && pos.y <= Height && pos.y >= lHeight ) { + if ( i + 1 < tSize ) { + Int32 curDist = eeabs( pos.x - lWidth ); + Int32 nextDist = eeabs( pos.x - ( lWidth + glyph.advance ) ); + + if ( nextDist < curDist ) { + return i + 1; + } + } + + return i; + } + + if ( CharID == '\n' ) { + lHeight = Height; + Height += getLineSpacing(characterSize); + + if ( pos.x > Width && pos.y <= lHeight ) { + return i; + } + } + } + + if ( pos.x >= Width ) { + return tSize; + } + + return -1; +} + +Vector2i FontTrueType::getCursorPos( const String& Text, const Uint32& characterSize, bool bold, Float outlineThickness, const Uint32& Pos ) { + Float Width = 0, Height = getLineSpacing(characterSize); + Int32 CharID; + std::size_t tSize = ( Pos < Text.size() ) ? Pos : Text.size(); + + for (std::size_t i = 0; i < tSize; ++i) { + CharID = static_cast( Text.at(i) ); + Glyph glyph = getGlyph( CharID, characterSize, bold, outlineThickness ); + + Width += glyph.advance; + + if ( CharID == '\t' ) { + Width += glyph.advance * 3; + } + + if ( CharID == '\n' ) { + Width = 0; + Height += getLineSpacing(characterSize); + } + } + + return Vector2i( Width, Height ); +} + +static bool isStopSelChar( Uint32 c ) { + return ( !String::isCharacter( c ) && !String::isNumber( c ) ) || + ' ' == c || + '.' == c || + ',' == c || + ';' == c || + ':' == c || + '\n' == c || + '"' == c || + '\'' == c; +} + +void FontTrueType::selectSubStringFromCursor( const String& Text, const Uint32& characterSize, bool bold, Float outlineThickness, const Int32& CurPos, Int32& InitCur, Int32& EndCur ) { + InitCur = 0; + EndCur = Text.size(); + + for ( std::size_t i = CurPos; i < Text.size(); i++ ) { + if ( isStopSelChar( Text[i] ) ) { + EndCur = i; + break; + } + } + + if ( 0 == CurPos ) { + InitCur = 0; + } + + for ( Int32 i = CurPos; i >= 0; i-- ) { + if ( isStopSelChar( Text[i] ) ) { + InitCur = i + 1; + break; + } + } + + if ( InitCur == EndCur ) { + InitCur = EndCur = -1; + } +} + +void FontTrueType::shrinkText( std::string& Str, const Uint32& characterSize, bool bold, Float outlineThickness, const Uint32& MaxWidth ) { + if ( !Str.size() ) + return; + + Float tCurWidth = 0.f; + Float tWordWidth = 0.f; + Float tMaxWidth = (Float) MaxWidth; + char * tChar = &Str[0]; + char * tLastSpace = NULL; + + while ( *tChar ) { + Glyph pChar = getGlyph( *tChar, characterSize, bold, outlineThickness ); + Float fCharWidth = (Float)pChar.advance; + + if ( ( *tChar ) == '\t' ) + fCharWidth += pChar.advance * 3; + + tWordWidth += fCharWidth; + + if ( ' ' == *tChar || '\0' == *( tChar + 1 ) ) { + if ( tCurWidth + tWordWidth < tMaxWidth ) { + tCurWidth += tWordWidth; + tLastSpace = tChar; + + tChar++; + } else { + if ( NULL != tLastSpace ) { + *tLastSpace = '\n'; + tChar = tLastSpace + 1; + } else { + *tChar = '\n'; + } + + if ( '\0' == *( tChar + 1 ) ) + tChar++; + + tLastSpace = NULL; + tCurWidth = 0.f; + } + + tWordWidth = 0.f; + } else if ( '\n' == *tChar ) { + tWordWidth = 0.f; + tCurWidth = 0.f; + tLastSpace = NULL; + tChar++; + } else { + tChar++; + } + } +} + +void FontTrueType::shrinkText( String& Str, const Uint32& characterSize, bool bold, Float outlineThickness, const Uint32& MaxWidth ) { + if ( !Str.size() ) + return; + + Float tCurWidth = 0.f; + Float tWordWidth = 0.f; + Float tMaxWidth = (Float) MaxWidth; + String::StringBaseType * tChar = &Str[0]; + String::StringBaseType * tLastSpace = NULL; + + while ( *tChar ) { + Glyph pChar = getGlyph( *tChar, characterSize, bold, outlineThickness ); + + Float fCharWidth = (Float)pChar.advance; + + if ( ( *tChar ) == '\t' ) + fCharWidth += pChar.advance * 3; + + // Add the new char width to the current word width + tWordWidth += fCharWidth; + + if ( ' ' == *tChar || '\0' == *( tChar + 1 ) ) { + + // If current width plus word width is minor to the max width, continue adding + if ( tCurWidth + tWordWidth < tMaxWidth ) { + tCurWidth += tWordWidth; + tLastSpace = tChar; + + tChar++; + } else { + // If it was an space before, replace that space for an new line + // Start counting from the new line first character + if ( NULL != tLastSpace ) { + *tLastSpace = '\n'; + tChar = tLastSpace + 1; + } else { // The word is larger than the current possible width + *tChar = '\n'; + } + + if ( '\0' == *( tChar + 1 ) ) + tChar++; + + // Set the last spaces as null, because is a new line + tLastSpace = NULL; + + // New line, new current width + tCurWidth = 0.f; + } + + // New word, so we reset the current word width + tWordWidth = 0.f; + } else if ( '\n' == *tChar ) { + tWordWidth = 0.f; + tCurWidth = 0.f; + tLastSpace = NULL; + tChar++; + } else { + tChar++; + } + } +} + +}} diff --git a/src/eepp/graphics/text.cpp b/src/eepp/graphics/text.cpp new file mode 100644 index 000000000..13bf067b7 --- /dev/null +++ b/src/eepp/graphics/text.cpp @@ -0,0 +1,618 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace EE { namespace Graphics { + // Add an underline or strikethrough line to the vertex array + static void addLine(std::vector& vertices, std::vector& colors, Float lineLength, Float lineTop, const EE::System::ColorA& color, Float offset, Float thickness, Float outlineThickness, Sizei textureSize, Int32 centerDiffX) { + Float top = std::floor(lineTop + offset - (thickness / 2) + 0.5f); + Float bottom = top + std::floor(thickness + 0.5f); + Float u1 = 0; + Float v1 = 0; + Float u2 = 1 / (Float)textureSize.getWidth(); + Float v2 = 1 / (Float)textureSize.getHeight(); + + if ( GLi->quadsSupported() ) { + VertexCoords vc; + + vc.TexCoords[0] = u1; + vc.TexCoords[1] = v1; + vc.Vertex[0] = centerDiffX + -outlineThickness; + vc.Vertex[1] = top - outlineThickness; + colors.push_back( color ); + vertices.push_back( vc ); + + vc.TexCoords[0] = u1; + vc.TexCoords[1] = v2; + vc.Vertex[0] = centerDiffX + -outlineThickness; + vc.Vertex[1] = bottom + outlineThickness; + colors.push_back( color ); + vertices.push_back( vc ); + + vc.TexCoords[0] = u2; + vc.TexCoords[1] = v2; + vc.Vertex[0] = centerDiffX + lineLength + outlineThickness; + vc.Vertex[1] = bottom + outlineThickness; + colors.push_back( color ); + vertices.push_back( vc ); + + vc.TexCoords[0] = u2; + vc.TexCoords[1] = v1; + vc.Vertex[0] = centerDiffX + lineLength + outlineThickness; + vc.Vertex[1] = top - outlineThickness; + colors.push_back( color ); + vertices.push_back( vc ); + } else { + + } + } + + // Add a glyph quad to the vertex array + static void addGlyphQuad(std::vector& vertices, std::vector& colors, Vector2f position, const EE::System::ColorA& color, const EE::Graphics::Glyph& glyph, Float italic, Float outlineThickness, Sizei textureSize, Int32 centerDiffX) { + Float left = glyph.bounds.Left; + Float top = glyph.bounds.Top; + Float right = glyph.bounds.Left + glyph.bounds.Right; + Float bottom = glyph.bounds.Top + glyph.bounds.Bottom; + + Float u1 = static_cast(glyph.textureRect.Left) / (Float)textureSize.getWidth(); + Float v1 = static_cast(glyph.textureRect.Top) / (Float)textureSize.getHeight(); + Float u2 = static_cast(glyph.textureRect.Left + glyph.textureRect.Right) / (Float)textureSize.getWidth(); + Float v2 = static_cast(glyph.textureRect.Top + glyph.textureRect.Bottom) / (Float)textureSize.getHeight(); + + if ( GLi->quadsSupported() ) { + VertexCoords vc; + + vc.TexCoords[0] = u1; + vc.TexCoords[1] = v1; + vc.Vertex[0] = centerDiffX + position.x + left - italic * top - outlineThickness; + vc.Vertex[1] = position.y + top - outlineThickness; + colors.push_back( color ); + vertices.push_back( vc ); + + vc.TexCoords[0] = u1; + vc.TexCoords[1] = v2; + vc.Vertex[0] = centerDiffX + position.x + left - italic * bottom - outlineThickness; + vc.Vertex[1] = position.y + bottom - outlineThickness; + colors.push_back( color ); + vertices.push_back( vc ); + + vc.TexCoords[0] = u2; + vc.TexCoords[1] = v2; + vc.Vertex[0] = centerDiffX + position.x + right - italic * bottom - outlineThickness; + vc.Vertex[1] = position.y + bottom - outlineThickness; + colors.push_back( color ); + vertices.push_back( vc ); + + vc.TexCoords[0] = u2; + vc.TexCoords[1] = v1; + vc.Vertex[0] = centerDiffX + position.x + right - italic * top - outlineThickness; + vc.Vertex[1] = position.y + top - outlineThickness; + colors.push_back( color ); + vertices.push_back( vc ); + } else { + + } + } +}} + +namespace EE { namespace Graphics { + +Text::Text() : + mString(), + mFont(NULL), + mCharacterSize(30), + mStyle(Regular), + mFillColor(255, 255, 255, 255), + mOutlineColor(0, 0, 0, 255), + mOutlineThickness (0), + mGeometryNeedUpdate(false), + mCachedWidth(0), + mNumLines(0), + mLargestLineCharCount(0), + mFontShadowColor( ColorA( 0, 0, 0, 255 ) ), + mFlags(0) +{ +} + +Text::Text(const String& string, FontTrueType * font, unsigned int characterSize) : + mString(string), + mFont(font), + mCharacterSize(characterSize), + mStyle(Regular), + mFillColor(255, 255, 255, 255), + mOutlineColor(0, 0, 0, 255), + mOutlineThickness(0), + mGeometryNeedUpdate(true), + mCachedWidth(0), + mNumLines(0), + mLargestLineCharCount(0), + mFontShadowColor( ColorA( 0, 0, 0, 255 ) ), + mFlags(0) +{ +} + +void Text::setText(const String& string) { + if (mString != string) { + mString = string; + mGeometryNeedUpdate = true; + cacheWidth(); + } +} + +void Text::setFontTrueType(FontTrueType * font) { + if (mFont != font) { + mFont = font; + mGeometryNeedUpdate = true; + cacheWidth(); + } +} + +void Text::setCharacterSize(unsigned int size) { + if (mCharacterSize != size) { + mCharacterSize = size; + mGeometryNeedUpdate = true; + cacheWidth(); + } +} + +void Text::setStyle(Uint32 style) { + if (mStyle != style) { + mStyle = style; + mGeometryNeedUpdate = true; + cacheWidth(); + } +} + +void Text::setColor(const ColorA & color) { + setFillColor(color); +} + +void Text::setFillColor(const ColorA& color) { + if (color != mFillColor) { + mFillColor = color; + + // Change vertex colors directly, no need to update whole geometry + // (if geometry is updated anyway, we can skip this step) + if (!mGeometryNeedUpdate) { + mColors.assign( mVertices.size(), mFillColor ); + } + } +} + +void Text::setOutlineColor(const ColorA& color) { + if (color != mOutlineColor) { + mOutlineColor = color; + + // Change vertex colors directly, no need to update whole geometry + // (if geometry is updated anyway, we can skip this step) + if (!mGeometryNeedUpdate) { + mOutlineColors.assign( mOutlineVertices.size(), mOutlineColor ); + } + } +} + +void Text::setOutlineThickness(Float thickness) { + if (thickness != mOutlineThickness) { + mOutlineThickness = thickness; + mGeometryNeedUpdate = true; + } +} + +const String& Text::getText() const { + return mString; +} + +const FontTrueType* Text::getFontTrueType() const { + return mFont; +} + +unsigned int Text::getCharacterSize() const { + return mCharacterSize; +} + +Uint32 Text::getStyle() const { + return mStyle; +} + +void Text::setAlpha( const Uint8& alpha ) { + std::size_t s = mColors.size(); + for ( Uint32 i = 0; i < s; i++ ) { + mColors[ i ].Alpha = alpha; + } +} + +const ColorA& Text::getFillColor() const { + return mFillColor; +} + +const ColorA& Text::getOutlineColor() const { + return mOutlineColor; +} + +Float Text::getOutlineThickness() const { + return mOutlineThickness; +} + +Vector2f Text::findCharacterPos(std::size_t index) const { + // Make sure that we have a valid font + if (!mFont) + return Vector2f(); + + // Adjust the index if it's out of range + if (index > mString.size()) + index = mString.size(); + + // Precompute the variables needed by the algorithm + bool bold = (mStyle & Bold) != 0; + Float hspace = static_cast(mFont->getGlyph(L' ', mCharacterSize, bold).advance); + Float vspace = static_cast(mFont->getLineSpacing(mCharacterSize)); + + // Compute the position + Vector2f position; + Uint32 prevChar = 0; + for (std::size_t i = 0; i < index; ++i) { + Uint32 curChar = mString[i]; + + // Apply the kerning offset + position.x += static_cast(mFont->getKerning(prevChar, curChar, mCharacterSize)); + prevChar = curChar; + + // Handle special characters + switch (curChar) + { + case ' ': position.x += hspace; continue; + case '\t': position.x += hspace * 4; continue; + case '\n': position.y += vspace; position.x = 0; continue; + } + + // For regular characters, add the advance offset of the glyph + position.x += static_cast(mFont->getGlyph(curChar, mCharacterSize, bold).advance); + } + + return position; +} + +Rectf Text::getLocalBounds() { + ensureGeometryUpdate(); + + return mBounds; +} + +Float Text::getTextWidth() { + return mCachedWidth; +} + +Float Text::getTextHeight() { + return mFont->getLineSpacing(mCharacterSize) * mNumLines; +} + +void Text::draw(const Float & X, const Float & Y, const Vector2f & Scale, const Float & Angle, EE_BLEND_MODE Effect) { + if ( NULL != mFont ) { + GlobalBatchRenderer::instance()->draw(); + TextureFactory::instance()->bind( mFont->getTexture(mCharacterSize) ); + BlendMode::setMode( Effect ); + + if ( mFlags & FONT_DRAW_SHADOW ) { + Uint32 f = mFlags; + + mFlags &= ~FONT_DRAW_SHADOW; + + ColorA Col = getFillColor(); + + if ( Col.a() != 255 ) { + ColorA ShadowColor = getShadowColor(); + + ShadowColor.Alpha = (Uint8)( (Float)ShadowColor.Alpha * ( (Float)Col.a() / (Float)255 ) ); + + setFillColor( ShadowColor ); + } else { + setFillColor( getShadowColor() ); + } + + Float pd = PixelDensity::dpToPx(1); + + draw( X + pd, Y + pd, Scale, Angle, Effect ); + + mFlags = f; + + setFillColor( Col ); + } + + unsigned int numvert = 0; + + if ( Angle != 0.0f || Scale != 1.0f ) { + Float cX = (Float) ( (Int32)X ); + Float cY = (Float) ( (Int32)Y ); + + GLi->pushMatrix(); + + Vector2f Center( cX + mCachedWidth * 0.5f, cY + getTextHeight() * 0.5f ); + GLi->translatef( Center.x , Center.y, 0.f ); + GLi->rotatef( Angle, 0.0f, 0.0f, 1.0f ); + GLi->scalef( Scale.x, Scale.y, 1.0f ); + GLi->translatef( -Center.x + X, -Center.y + Y, 0.f ); + } else { + GLi->translatef( X, Y, 0 ); + } + + ensureGeometryUpdate(); + + numvert = mVertices.size(); + + Uint32 alloc = numvert * sizeof(VertexCoords); + Uint32 allocC = numvert * GLi->quadVertexs(); + + if ( 0 != mOutlineThickness ) { + GLi->colorPointer ( 4, GL_UNSIGNED_BYTE , 0 , reinterpret_cast( &mOutlineColors[0] ) , allocC ); + GLi->texCoordPointer( 2, GL_FP , sizeof(VertexCoords), reinterpret_cast( &mOutlineVertices[0] ) , alloc ); + GLi->vertexPointer ( 2, GL_FP , sizeof(VertexCoords), reinterpret_cast( &mOutlineVertices[0] ) + sizeof(Float) * 2 , alloc ); + + if ( GLi->quadsSupported() ) { + GLi->drawArrays( GL_QUADS, 0, numvert ); + } else { + GLi->drawArrays( GL_TRIANGLES, 0, numvert ); + } + } + + GLi->colorPointer ( 4, GL_UNSIGNED_BYTE , 0 , reinterpret_cast( &mColors[0] ) , allocC ); + GLi->texCoordPointer( 2, GL_FP , sizeof(VertexCoords), reinterpret_cast( &mVertices[0] ) , alloc ); + GLi->vertexPointer ( 2, GL_FP , sizeof(VertexCoords), reinterpret_cast( &mVertices[0] ) + sizeof(Float) * 2 , alloc ); + + if ( GLi->quadsSupported() ) { + GLi->drawArrays( GL_QUADS, 0, numvert ); + } else { + GLi->drawArrays( GL_TRIANGLES, 0, numvert ); + } + + if ( Angle != 0.0f || Scale != 1.0f ) { + GLi->popMatrix(); + } else { + GLi->translatef( -X, -Y, 0 ); + } + } +} + +void Text::ensureGeometryUpdate() { + Sizei textureSize = mFont->getTexture(mCharacterSize)->getSize(); + + if ( textureSize != mTextureSize ) + mGeometryNeedUpdate = true; + + // Do nothing, if geometry has not changed + if (!mGeometryNeedUpdate) + return; + + cacheWidth(); + + // Mark geometry as updated + mGeometryNeedUpdate = false; + + // Clear the previous geometry + mVertices.clear(); + mColors.clear();; + mOutlineVertices.clear(); + mOutlineColors.clear(); + mBounds = Rectf(); + + // No font or text: nothing to draw + if (!mFont || mString.empty()) + return; + + // Compute values related to the text style + bool bold = (mStyle & Bold) != 0; + bool underlined = (mStyle & Underlined) != 0; + bool strikeThrough = (mStyle & StrikeThrough) != 0; + Float italic = (mStyle & Italic) ? 0.208f : 0.f; // 12 degrees + Float underlineOffset = mFont->getUnderlinePosition(mCharacterSize); + Float underlineThickness = mFont->getUnderlineThickness(mCharacterSize); + + // Compute the location of the strike through dynamically + // We use the center point of the lowercase 'x' glyph as the reference + // We reuse the underline thickness as the thickness of the strike through as well + Rectf xBounds = mFont->getGlyph(L'x', mCharacterSize, bold).bounds; + Float strikeThroughOffset = xBounds.Top + xBounds.Bottom / 2.f; + + // Precompute the variables needed by the algorithm + Float hspace = static_cast(mFont->getGlyph(L' ', mCharacterSize, bold).advance); + Float vspace = static_cast(mFont->getLineSpacing(mCharacterSize)); + Float x = 0.f; + Float y = static_cast(mCharacterSize); + + // Create one quad for each character + Float minX = static_cast(mCharacterSize); + Float minY = static_cast(mCharacterSize); + Float maxX = 0.f; + Float maxY = 0.f; + Uint32 prevChar = 0; + + for (std::size_t i = 0; i < mString.size(); ++i) { + Uint32 curChar = mString[i]; + + // Apply the outline + if (mOutlineThickness != 0) { + mFont->getGlyph(curChar, mCharacterSize, bold, mOutlineThickness); + } + + // Extract the current glyph's description + mFont->getGlyph(curChar, mCharacterSize, bold); + } + + Float centerDiffX = 0; + unsigned int Line = 0; + + if ( !( mFlags & FONT_DRAW_VERTICAL ) ) { + switch ( fontHAlignGet( mFlags ) ) { + case FONT_DRAW_CENTER: + centerDiffX = (Float)( (Int32)( ( mCachedWidth - mLinesWidth[ Line ] ) * 0.5f ) ); + Line++; + break; + case FONT_DRAW_RIGHT: + centerDiffX = mCachedWidth - getLinesWidth()[ Line ]; + Line++; + break; + } + } + + for (std::size_t i = 0; i < mString.size(); ++i) { + Uint32 curChar = mString[i]; + + // Apply the kerning offset + x += mFont->getKerning(prevChar, curChar, mCharacterSize); + prevChar = curChar; + + // If we're using the underlined style and there's a new line, draw a line + if (underlined && (curChar == L'\n')) { + addLine(mVertices, mColors, x, y, mFillColor, underlineOffset, underlineThickness, 0, textureSize, centerDiffX); + + if (mOutlineThickness != 0) + addLine(mOutlineVertices, mOutlineColors, x, y, mOutlineColor, underlineOffset, underlineThickness, mOutlineThickness, textureSize, centerDiffX); + } + + // If we're using the strike through style and there's a new line, draw a line across all characters + if (strikeThrough && (curChar == L'\n')) { + addLine(mVertices, mColors, x, y, mFillColor, strikeThroughOffset, underlineThickness, 0, textureSize, centerDiffX); + + if (mOutlineThickness != 0) + addLine(mOutlineVertices, mOutlineColors, x, y, mOutlineColor, strikeThroughOffset, underlineThickness, mOutlineThickness, textureSize, centerDiffX); + } + + if ( curChar == L'\n' ) { + switch ( fontHAlignGet( mFlags ) ) { + case FONT_DRAW_CENTER: + centerDiffX = (Float)( (Int32)( ( mCachedWidth - mLinesWidth[ Line ] ) * 0.5f ) ); + break; + case FONT_DRAW_RIGHT: + centerDiffX = mCachedWidth - mLinesWidth[ Line ]; + break; + } + + Line++; + } + + // Handle special characters + if ((curChar == ' ') || (curChar == '\t') || (curChar == '\n')) { + // Update the current bounds (min coordinates) + minX = std::min(minX, x); + minY = std::min(minY, y); + + switch (curChar) { + case ' ': x += hspace; break; + case '\t': x += hspace * 4; break; + case '\n': y += vspace; x = 0; break; + } + + // Update the current bounds (max coordinates) + maxX = std::max(maxX, x); + maxY = std::max(maxY, y); + + // Next glyph, no need to create a quad for whitespace + continue; + } + + + // Apply the outline + if (mOutlineThickness != 0) { + const Glyph& glyph = mFont->getGlyph(curChar, mCharacterSize, bold, mOutlineThickness); + + Float left = glyph.bounds.Left; + Float top = glyph.bounds.Top; + Float right = glyph.bounds.Left + glyph.bounds.Right; + Float bottom = glyph.bounds.Top + glyph.bounds.Bottom; + + // Add the outline glyph to the vertices + addGlyphQuad(mOutlineVertices, mOutlineColors, Vector2f(x, y), mOutlineColor, glyph, italic, mOutlineThickness, textureSize, centerDiffX); + + // Update the current bounds with the outlined glyph bounds + minX = std::min(minX, x + left - italic * bottom - mOutlineThickness); + maxX = std::max(maxX, x + right - italic * top - mOutlineThickness); + minY = std::min(minY, y + top - mOutlineThickness); + maxY = std::max(maxY, y + bottom - mOutlineThickness); + } + + // Extract the current glyph's description + const Glyph& glyph = mFont->getGlyph(curChar, mCharacterSize, bold); + + // Add the glyph to the vertices + addGlyphQuad(mVertices, mColors, Vector2f(x, y), mFillColor, glyph, italic, 0, textureSize, centerDiffX); + + // Update the current bounds with the non outlined glyph bounds + if (mOutlineThickness == 0) { + Float left = glyph.bounds.Left; + Float top = glyph.bounds.Top; + Float right = glyph.bounds.Left + glyph.bounds.Right; + Float bottom = glyph.bounds.Top + glyph.bounds.Bottom; + + minX = std::min(minX, x + left - italic * bottom); + maxX = std::max(maxX, x + right - italic * top); + minY = std::min(minY, y + top); + maxY = std::max(maxY, y + bottom); + } + + // Advance to the next character + x += glyph.advance; + } + + // If we're using the underlined style, add the last line + if (underlined && (x > 0)) { + addLine(mVertices, mColors, x, y, mFillColor, underlineOffset, underlineThickness, 0, textureSize, centerDiffX); + + if (mOutlineThickness != 0) + addLine(mOutlineVertices, mOutlineColors, x, y, mOutlineColor, underlineOffset, underlineThickness, mOutlineThickness, textureSize, centerDiffX); + } + + // If we're using the strike through style, add the last line across all characters + if (strikeThrough && (x > 0)) { + addLine(mVertices, mColors, x, y, mFillColor, strikeThroughOffset, underlineThickness, 0, textureSize, centerDiffX); + + if (mOutlineThickness != 0) + addLine(mOutlineVertices, mOutlineColors, x, y, mOutlineColor, strikeThroughOffset, underlineThickness, mOutlineThickness, textureSize, centerDiffX); + } + + // Update the bounding rectangle + mBounds.Left = minX; + mBounds.Top = minY; + mBounds.Right = maxX - minX; + mBounds.Bottom = maxY - minY; +} + +const ColorA& Text::getShadowColor() const { + return mFontShadowColor; +} + +void Text::setShadowColor(const ColorA& color) { + mFontShadowColor = color; +} + +const int& Text::getNumLines() const { + return mNumLines; +} + +const std::vector& Text::getLinesWidth() { + return mLinesWidth; +} + +void Text::setFlags( const Uint32& flags ) { + if ( mFlags != flags ) { + mFlags = flags; + mGeometryNeedUpdate = true; + } +} + +const Uint32& Text::getFlags() const { + return mFlags; +} + +void Text::cacheWidth() { + if ( NULL != mFont && mString.size() ) { + mFont->cacheWidth( mString, mCharacterSize, (mStyle & Bold), mOutlineThickness, mLinesWidth, mCachedWidth, mNumLines, mLargestLineCharCount ); + } else { + mCachedWidth = 0; + } +} + + +}} diff --git a/src/eepp/graphics/textcache.cpp b/src/eepp/graphics/textcache.cpp index 707d87c43..4c6a50acd 100644 --- a/src/eepp/graphics/textcache.cpp +++ b/src/eepp/graphics/textcache.cpp @@ -200,7 +200,7 @@ void TextCache::cacheVerts() { if ( Char >= 0 && Char < tGlyphSize ) { TextureCoords C = mFont->getTextureCoords( Char ); - Glyph Glyph = mFont->getGlyph( Char ); + GlyphData Glyph = mFont->getGlyph( Char ); switch( Char ) { case '\v': diff --git a/src/eepp/graphics/texturefont.cpp b/src/eepp/graphics/texturefont.cpp index 876ba5e8e..e01c3a901 100755 --- a/src/eepp/graphics/texturefont.cpp +++ b/src/eepp/graphics/texturefont.cpp @@ -109,7 +109,7 @@ void TextureFont::buildFromGlyphs() { TextureFactory::instance()->bind( Tex ); - Glyph tGlyph; + GlyphData tGlyph; for (unsigned int i = 0; i < mNumChars; i++) { tGlyph = mGlyphs[i]; @@ -201,7 +201,7 @@ bool TextureFont::loadFromStream( const Uint32& TexId, IOStream& IOS ) { mGlyphs.resize( mNumChars ); // Read the glyphs - IOS.read( (char*)&mGlyphs[0], sizeof(Glyph) * mNumChars ); + IOS.read( (char*)&mGlyphs[0], sizeof(GlyphData) * mNumChars ); buildFromGlyphs(); diff --git a/src/eepp/graphics/ttffont.cpp b/src/eepp/graphics/ttffont.cpp index 0368cb871..66b116f0a 100755 --- a/src/eepp/graphics/ttffont.cpp +++ b/src/eepp/graphics/ttffont.cpp @@ -169,7 +169,7 @@ bool TTFFont::iLoad( const unsigned int& Size, EE_TTF_FONT_STYLE Style, const Ui TempGlyphSurface = mFont->renderGlyph( i, fFontColor.getValue() ); //New temp glyph - Glyph TempGlyph; + GlyphData TempGlyph; //Get the glyph attributes mFont->getGlyphMetrics( i, &TempGlyph.MinX, &TempGlyph.MaxX, &TempGlyph.MinY, &TempGlyph.MaxY, &TempGlyph.Advance ); @@ -338,9 +338,7 @@ void TTFFont::rebuildFromGlyphs() { Texture * Tex = TextureFactory::instance()->getTexture( mTexId ); - TextureFactory::instance()->bind( Tex ); - - Glyph tGlyph; + GlyphData tGlyph; for (unsigned int i = 0; i < mNumChars; i++) { tGlyph = mGlyphs[i]; @@ -401,7 +399,7 @@ bool TTFFont::saveCoordinates( const std::string& Filepath ) { fs.write( reinterpret_cast( &FntHdr ), sizeof(sFntHdr) ); // Write the glyphs - fs.write( reinterpret_cast (&mGlyphs[0]), sizeof(Glyph) * mGlyphs.size() ); + fs.write( reinterpret_cast (&mGlyphs[0]), sizeof(GlyphData) * mGlyphs.size() ); rebuildFromGlyphs(); diff --git a/src/examples/fonts/fonts.cpp b/src/examples/fonts/fonts.cpp index 32179da5e..a3460db80 100644 --- a/src/examples/fonts/fonts.cpp +++ b/src/examples/fonts/fonts.cpp @@ -1,4 +1,6 @@ #include +#include +#include EE::Window::Window * win = NULL; TTFFont * TTF = NULL; @@ -12,6 +14,10 @@ TextCache TTFOCache; TextCache TexFCache; TextCache TexF2Cache; TextCache TxtCache; +FontTrueType fontTest; +Uint32 nextGliph = 0; +Clock timer; +Text text; void mainLoop() { @@ -27,6 +33,7 @@ void mainLoop() win->close(); } +/* Float YPos = 32; // Draw the text on screen @@ -45,6 +52,14 @@ void mainLoop() // Text rotated and scaled TTFCache.draw( ( win->getWidth() - TTFCache.getTextWidth() ) * 0.5f, 512 + 32, Vector2f( 0.75f, 0.75f ), 12.5f ); +*/ + /*if ( timer.getElapsedTime().asMilliseconds() > 50 ) { + fontTest.getGlyph( nextGliph, 48, false ); + nextGliph++; + timer.restart(); + }*/ + + text.draw( ( win->getWidth() - text.getTextWidth() ) * 0.5f, 0 ); // Draw frame win->display(); @@ -79,10 +94,10 @@ EE_MAIN_FUNC int main (int argc, char * argv []) // Create the exact same font than before but using the new outlining method TTFO->load( AppPath + "assets/fonts/DejaVuSansMono.ttf", 18, TTF_STYLE_NORMAL, 128, RGB(255,255,255), 3, RGB(0,0,0), true ); - TTF2->load( AppPath + "assets/fonts/DejaVuSansMono.ttf", 24, TTF_STYLE_NORMAL, 128, RGB(255,255,255), 0, RGB(0,0,0), true ); + TTF2->load( AppPath + "assets/fonts/DejaVuSansMono.ttf", 48, TTF_STYLE_NORMAL, 128, RGB(255,255,255), 0, RGB(0,0,0), true ); // Save the TTF font so then it can be loaded as a TextureFont - TTF->save( AppPath + "assets/temp/DejaVuSansMono.png", AppPath + "assets/temp/DejaVuSansMono.fnt" ); + //TTF->save( AppPath + "assets/temp/DejaVuSansMono.png", AppPath + "assets/temp/DejaVuSansMono.fnt" ); // Load the texture font, previusly generated from a True Type Font // First load the texture @@ -120,7 +135,7 @@ EE_MAIN_FUNC int main (int argc, char * argv []) String Txt( "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." ); // Make the text fit the screen width ( wrap the text ) - TTF2->shrinkText( Txt, win->getWidth() - 96 ); + //TTF2->shrinkText( Txt, win->getWidth() - 96 ); // Create a new text cache to draw on screen // The cached text will @@ -138,6 +153,17 @@ EE_MAIN_FUNC int main (int argc, char * argv []) TxtCache.setColor( ColorA(255*i/size,0,0,255), i, i+1 ); } + fontTest.loadFromFile( AppPath + "assets/fonts/DejaVuSansMono.ttf" ); + fontTest.shrinkText( Txt, 24, false, 2, win->getWidth() - 96 ); + text.setFontTrueType( &fontTest ); + text.setCharacterSize( 24 ); + text.setFillColor( 0xFFFFFFFF ); + text.setOutlineThickness( 2 ); + text.setFlags( FONT_DRAW_CENTER ); + text.setText( Txt ); + + win->setBackColor( RGB(230,230,230) ); + // Application loop win->runMainLoop( &mainLoop ); }