WIP new font class.

--HG--
branch : dev-font
This commit is contained in:
Martí­n Lucas Golini
2017-03-12 21:02:10 -03:00
parent cbad5e7ded
commit 786b8d67b3
13 changed files with 1821 additions and 36 deletions

View File

@@ -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<Glyph> mGlyphs;
std::vector<GlyphData> mGlyphs;
std::vector<TextureCoords> mTexCoords;
TextCache mTextCache;

View File

@@ -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;

View File

@@ -0,0 +1,133 @@
#ifndef EE_GRAPHICS_FONTTRUETYPE_HPP
#define EE_GRAPHICS_FONTTRUETYPE_HPP
#include <eepp/graphics/base.hpp>
#include <eepp/graphics/texture.hpp>
#include <map>
#include <string>
#include <vector>
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<Float>& 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<Uint64, Glyph> 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<Row> 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<unsigned int, Page> 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<Uint8> mPixelBuffer; ///< Pixel buffer holding a glyph's pixels before being written to the texture
};
}}
#endif

View File

@@ -0,0 +1,126 @@
#ifndef EE_GRAPHICS_TEXT_HPP
#define EE_GRAPHICS_TEXT_HPP
#include <eepp/graphics/fonttruetype.hpp>
#include <eepp/graphics/fonthelper.hpp>
#include <string>
#include <vector>
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<Float>& 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<VertexCoords> mVertices;
std::vector<ColorA> mColors;
std::vector<VertexCoords> mOutlineVertices;
std::vector<ColorA> mOutlineColors;
std::vector<Float> mLinesWidth;
/** Force to cache the width of the current text */
void cacheWidth();
};
}}
#endif

View File

@@ -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;

View File

@@ -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

View File

@@ -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<Float>& 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' )

View File

@@ -0,0 +1,894 @@
#include <eepp/graphics/fonttruetype.hpp>
#include <eepp/system/iostream.hpp>
#include <eepp/graphics/texturefactory.hpp>
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
#include FT_OUTLINE_H
#include FT_BITMAP_H
#include FT_STROKER_H
#include <cstdlib>
#include <cstring>
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<IOStream*>(rec->descriptor.pointer);
if (static_cast<unsigned long>(stream->seek(offset)) == offset)
{
if (count > 0)
return static_cast<unsigned long>(stream->read(reinterpret_cast<char*>(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<FT_Library>(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<FT_Library>(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<FT_Library>(mLibrary), reinterpret_cast<const FT_Byte*>(data), static_cast<FT_Long>(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<FT_Library>(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<unsigned long>(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<FT_Library>(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<FT_Library>(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<Uint64>(*reinterpret_cast<Uint32*>(&outlineThickness)) << 32)
| (static_cast<Uint64>(bold ? 1 : 0) << 31)
| static_cast<Uint64>(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<FT_Face>(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<Float>(kerning.x);
// Return the X advance
return static_cast<Float>(kerning.x) / static_cast<Float>(1 << 6);
} else {
// Invalid font, or no kerning
return 0.f;
}
}
Float FontTrueType::getLineSpacing(unsigned int characterSize) const {
FT_Face face = static_cast<FT_Face>(mFace);
if (face && setCurrentSize(characterSize)) {
return static_cast<Float>(face->size->metrics.height) / static_cast<Float>(1 << 6);
} else {
return 0.f;
}
}
Float FontTrueType::getUnderlinePosition(unsigned int characterSize) const {
FT_Face face = static_cast<FT_Face>(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<Float>(FT_MulFix(face->underline_position, face->size->metrics.y_scale)) / static_cast<Float>(1 << 6);
} else {
return 0.f;
}
}
Float FontTrueType::getUnderlineThickness(unsigned int characterSize) const {
FT_Face face = static_cast<FT_Face>(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<Float>(FT_MulFix(face->underline_thickness, face->size->metrics.y_scale)) / static_cast<Float>(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<FT_Stroker>(mStroker));
// Destroy the font face
if (mFace)
FT_Done_Face(static_cast<FT_Face>(mFace));
// Destroy the stream rec instance, if any (must be done after FT_Done_Face!)
if (mStreamRec)
delete static_cast<FT_StreamRec*>(mStreamRec);
// Close the library
if (mLibrary)
FT_Done_FreeType(static_cast<FT_Library>(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<FT_Face>(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<FT_Stroker>(mStroker);
FT_Stroker_Set(stroker, static_cast<FT_Fixed>(outlineThickness * static_cast<Float>(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<FT_BitmapGlyph>(glyphDesc)->bitmap;
// Apply bold if necessary -- fallback technique using bitmap (lower quality)
if (!outline) {
if (bold)
FT_Bitmap_Embolden(static_cast<FT_Library>(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<Float>(face->glyph->metrics.horiAdvance) / static_cast<Float>(1 << 6);
if (bold)
glyph.advance += static_cast<Float>(weight) / static_cast<Float>(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<Float>(face->glyph->metrics.horiBearingX) / static_cast<Float>(1 << 6);
glyph.bounds.Top = -static_cast<Float>(face->glyph->metrics.horiBearingY) / static_cast<Float>(1 << 6);
glyph.bounds.Right = static_cast<Float>(face->glyph->metrics.width) / static_cast<Float>(1 << 6) + outlineThickness * 2;
glyph.bounds.Bottom = static_cast<Float>(face->glyph->metrics.height) / static_cast<Float>(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<Row>::iterator it = page.rows.begin(); it != page.rows.end() && !row; ++it) {
Float ratio = static_cast<Float>(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<FT_Face>(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<Float>& 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<Int32>( 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<Int32>( 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<Int32>( 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++;
}
}
}
}}

618
src/eepp/graphics/text.cpp Normal file
View File

@@ -0,0 +1,618 @@
#include <eepp/graphics/text.hpp>
#include <eepp/graphics/texture.hpp>
#include <eepp/graphics/renderer/gl.hpp>
#include <eepp/graphics/glextensions.hpp>
#include <eepp/graphics/globalbatchrenderer.hpp>
#include <eepp/graphics/texturefactory.hpp>
#include <cmath>
namespace EE { namespace Graphics {
// Add an underline or strikethrough line to the vertex array
static void addLine(std::vector<VertexCoords>& vertices, std::vector<ColorA>& 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<VertexCoords>& vertices, std::vector<ColorA>& 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<Float>(glyph.textureRect.Left) / (Float)textureSize.getWidth();
Float v1 = static_cast<Float>(glyph.textureRect.Top) / (Float)textureSize.getHeight();
Float u2 = static_cast<Float>(glyph.textureRect.Left + glyph.textureRect.Right) / (Float)textureSize.getWidth();
Float v2 = static_cast<Float>(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<Float>(mFont->getGlyph(L' ', mCharacterSize, bold).advance);
Float vspace = static_cast<Float>(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<Float>(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<Float>(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<char*>( &mOutlineColors[0] ) , allocC );
GLi->texCoordPointer( 2, GL_FP , sizeof(VertexCoords), reinterpret_cast<char*>( &mOutlineVertices[0] ) , alloc );
GLi->vertexPointer ( 2, GL_FP , sizeof(VertexCoords), reinterpret_cast<char*>( &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<char*>( &mColors[0] ) , allocC );
GLi->texCoordPointer( 2, GL_FP , sizeof(VertexCoords), reinterpret_cast<char*>( &mVertices[0] ) , alloc );
GLi->vertexPointer ( 2, GL_FP , sizeof(VertexCoords), reinterpret_cast<char*>( &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<Float>(mFont->getGlyph(L' ', mCharacterSize, bold).advance);
Float vspace = static_cast<Float>(mFont->getLineSpacing(mCharacterSize));
Float x = 0.f;
Float y = static_cast<Float>(mCharacterSize);
// Create one quad for each character
Float minX = static_cast<Float>(mCharacterSize);
Float minY = static_cast<Float>(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<Float>& 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;
}
}
}}

View File

@@ -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':

View File

@@ -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();

View File

@@ -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<const char*>( &FntHdr ), sizeof(sFntHdr) );
// Write the glyphs
fs.write( reinterpret_cast<const char*> (&mGlyphs[0]), sizeof(Glyph) * mGlyphs.size() );
fs.write( reinterpret_cast<const char*> (&mGlyphs[0]), sizeof(GlyphData) * mGlyphs.size() );
rebuildFromGlyphs();

View File

@@ -1,4 +1,6 @@
#include <eepp/ee.hpp>
#include <eepp/graphics/fonttruetype.hpp>
#include <eepp/graphics/text.hpp>
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 );
}