Rewrote font code to use multiple textures.

Will corrupt after playing a game.
This commit is contained in:
Aloshi 2014-07-27 16:44:02 -05:00
parent 23d8856773
commit 2b22e1fe0b
4 changed files with 440 additions and 234 deletions

View file

@ -163,7 +163,8 @@ void TextComponent::onTextChanged()
while(text.size() && size.x() + abbrevSize.x() > mSize.x()) while(text.size() && size.x() + abbrevSize.x() > mSize.x())
{ {
text.erase(text.size() - 1, 1); size_t newSize = Font::getPrevCursor(text, text.size());
text.erase(newSize, text.size() - newSize);
size = f->sizeText(text); size = f->sizeText(text);
} }

View file

@ -164,13 +164,7 @@ void TextEditComponent::updateCursorRepeat(int deltaTime)
void TextEditComponent::moveCursor(int amt) void TextEditComponent::moveCursor(int amt)
{ {
mCursor += amt; mCursor = Font::moveCursor(mText, mCursor, amt);
if(mCursor < 0)
mCursor = 0;
if(mCursor >= (int)mText.length())
mCursor = mText.length();
onCursorChanged(); onCursorChanged();
} }

View file

@ -13,6 +13,130 @@ int Font::getSize() const { return mSize; }
std::map< std::pair<std::string, int>, std::weak_ptr<Font> > Font::sFontMap; std::map< std::pair<std::string, int>, std::weak_ptr<Font> > Font::sFontMap;
// utf8 stuff
size_t Font::getNextCursor(const std::string& str, size_t cursor)
{
// compare to character at the cursor
const char& c = str[cursor];
size_t result = cursor;
if((c & 0x80) == 0) // 0xxxxxxx, one byte character
{
result += 1;
}
else if((c & 0xE0) == 0xC0) // 110xxxxx, two bytes left in character
{
result += 2;
}
else if((c & 0xF0) == 0xE0) // 1110xxxx, three bytes left in character
{
result += 3;
}
else if((c & 0xF8) == 0xF0) // 11110xxx, four bytes left in character
{
result += 4;
}
else
{
// error, invalid utf8 string
// if this assert is tripped, the cursor is in the middle of a utf8 code point
assert((c & 0xC0) != 0x80); // character is 10xxxxxx
// if that wasn't it, something crazy happened
assert(false);
}
if(str.length() < result || result < cursor) // don't go beyond the very end of the string, try and catch overflow
return cursor;
return result;
}
// note: will happily accept malformed utf8
size_t Font::getPrevCursor(const std::string& str, size_t cursor)
{
if(cursor == 0)
return 0;
do
{
cursor--;
} while(cursor > 0 &&
(str[cursor] & 0xC0) == 0x80); // character is 10xxxxxx
return cursor;
}
size_t Font::moveCursor(const std::string& str, size_t cursor, int amt)
{
if(amt > 0)
{
for(int i = 0; i < amt; i++)
cursor = Font::getNextCursor(str, cursor);
}
else if(amt < 0)
{
for(int i = amt; i < 0; i++)
cursor = Font::getPrevCursor(str, cursor);
}
return cursor;
}
UnicodeChar Font::readUnicodeChar(const std::string& str, size_t& cursor)
{
const char& c = str[cursor];
if((c & 0x80) == 0) // 0xxxxxxx, one byte character
{
// 0xxxxxxx
cursor++;
return (UnicodeChar)c;
}
else if((c & 0xE0) == 0xC0) // 110xxxxx, two bytes left in character
{
// 110xxxxx 10xxxxxx
UnicodeChar val = ((str[cursor] & 0x1F) << 6) |
(str[cursor + 1] & 0x3F);
cursor += 2;
return val;
}
else if((c & 0xF0) == 0xE0) // 1110xxxx, three bytes left in character
{
// 1110xxxx 10xxxxxx 10xxxxxx
UnicodeChar val = ((str[cursor] & 0x0F) << 12) |
((str[cursor + 1] & 0x3F) << 6) |
(str[cursor + 2] & 0x3F);
cursor += 3;
return val;
}
else if((c & 0xF8) == 0xF0) // 11110xxx, four bytes left in character
{
// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
UnicodeChar val = ((str[cursor] & 0x07) << 18) |
((str[cursor + 1] & 0x3F) << 12) |
((str[cursor + 2] & 0x3F) << 6) |
(str[cursor + 3] & 0x3F);
cursor += 4;
return val;
}
else
{
// error, invalid utf8 string
// if this assert is tripped, the cursor is in the middle of a utf8 code point
assert((c & 0xC0) != 0x80); // character is 10xxxxxx
// if that wasn't it, something crazy happened
assert(false);
}
// error
return 0;
}
void Font::initLibrary() void Font::initLibrary()
{ {
assert(sLibrary == NULL); assert(sLibrary == NULL);
@ -25,10 +149,11 @@ void Font::initLibrary()
size_t Font::getMemUsage() const size_t Font::getMemUsage() const
{ {
if(!mTextureID) size_t memUsage = 0;
return 0; for(auto it = mTextures.begin(); it != mTextures.end(); it++)
memUsage += it->textureSize.x() * it->textureSize.y() * 4;
return mTextureWidth * mTextureHeight * 4; return memUsage;
} }
size_t Font::getTotalMemUsage() size_t Font::getTotalMemUsage()
@ -51,24 +176,29 @@ size_t Font::getTotalMemUsage()
return total; return total;
} }
Font::Font(int size, const std::string& path) : mFontScale(1.0f), mSize(size), mPath(path), mTextureID(0) Font::Font(int size, const std::string& path) : mSize(size), mPath(path)
{ {
assert(mSize > 0);
if(!sLibrary)
initLibrary();
reload(ResourceManager::getInstance()); reload(ResourceManager::getInstance());
} }
Font::~Font() Font::~Font()
{ {
deinit(); unload(ResourceManager::getInstance());
} }
void Font::reload(std::shared_ptr<ResourceManager>& rm) void Font::reload(std::shared_ptr<ResourceManager>& rm)
{ {
init(rm->getFileData(mPath)); reloadGlyphTextures();
} }
void Font::unload(std::shared_ptr<ResourceManager>& rm) void Font::unload(std::shared_ptr<ResourceManager>& rm)
{ {
deinit(); freeTextures();
} }
std::shared_ptr<Font> Font::get(int size, const std::string& path) std::shared_ptr<Font> Font::get(int size, const std::string& path)
@ -89,47 +219,75 @@ std::shared_ptr<Font> Font::get(int size, const std::string& path)
return font; return font;
} }
void Font::init(ResourceData data) void Font::reloadGlyphTextures()
{ {
if(sLibrary == NULL) // freeTextures(); // make sure we don't leak
initLibrary();
deinit(); for(UnicodeChar i = 32; i < 128; i++)
getGlyph(i);
mMaxGlyphHeight = 0; // TODO
buildAtlas(data);
} }
void Font::deinit() void Font::freeTextures()
{ {
if(mTextureID) for(auto it = mTextures.begin(); it != mTextures.end(); it++)
{ {
glDeleteTextures(1, &mTextureID); glDeleteTextures(1, &it->textureId);
mTextureID = 0;
}
} }
void Font::buildAtlas(ResourceData data) mTextures.clear();
{
assert(mSize > 0);
FT_Face face;
if(FT_New_Memory_Face(sLibrary, data.ptr.get(), data.length, 0, &face))
{
LOG(LogError) << "Error creating font face! (mPath: " << mPath << ", data.length: " << data.length << ")";
return;
} }
FT_Set_Pixel_Sizes(face, 0, mSize); bool Font::FontTexture::findEmpty(const Eigen::Vector2i& size, Eigen::Vector2i& cursor_out)
{
if(size.x() >= textureSize.x() || size.y() >= textureSize.y())
return false;
// hardcoded texture size right now if(writePos.x() + size.x() >= textureSize.x() &&
mTextureWidth = 2048; writePos.y() + rowHeight + size.y() + 1 < textureSize.y())
mTextureHeight = 512; {
// row full, but it should fit on the next row
// move cursor to next row
writePos << 0, writePos.y() + rowHeight + 1; // leave 1px of space between glyphs
rowHeight = 0;
}
// create the texture if(writePos.x() + size.x() >= textureSize.x() ||
glGenTextures(1, &mTextureID); writePos.y() + size.y() >= textureSize.y())
glBindTexture(GL_TEXTURE_2D, mTextureID); {
// nope, still won't fit
return false;
}
cursor_out = writePos;
writePos[0] += size.x() + 1; // leave 1px of space between glyphs
if(size.y() > rowHeight)
rowHeight = size.y();
return true;
}
void Font::getTextureForNewGlyph(const Eigen::Vector2i& glyphSize, FontTexture*& tex_out, Eigen::Vector2i& cursor_out)
{
if(mTextures.size())
{
// check if the most recent texture has space
tex_out = &mTextures.back();
// will this one work?
if(tex_out->findEmpty(glyphSize, cursor_out))
return; // yes
}
// current textures are full,
// make a new one
mTextures.push_back(FontTexture());
tex_out = &mTextures.back();
glGenTextures(1, &tex_out->textureId);
glBindTexture(GL_TEXTURE_2D, tex_out->textureId);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
@ -140,77 +298,98 @@ void Font::buildAtlas(ResourceData data)
glPixelStorei(GL_PACK_ALIGNMENT, 1); glPixelStorei(GL_PACK_ALIGNMENT, 1);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mTextureWidth, mTextureHeight, 0, GL_ALPHA, GL_UNSIGNED_BYTE, NULL); glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, tex_out->textureSize.x(), tex_out->textureSize.y(), 0, GL_ALPHA, GL_UNSIGNED_BYTE, NULL);
bool ok = tex_out->findEmpty(glyphSize, cursor_out);
if(!ok)
{
LOG(LogError) << "Glyph too big to fit on a new texture (glyph size > " << tex_out->textureSize.x() << ", " << tex_out->textureSize.y() << ")!";
tex_out = NULL;
}
}
Font::Glyph* Font::getGlyph(UnicodeChar id)
{
// is it already loaded?
auto it = mGlyphMap.find(id);
if(it != mGlyphMap.end())
return &it->second;
// nope, need to make a glyph
// get the font data
ResourceData data = ResourceManager::getInstance()->getFileData(mPath);
FT_Face face;
if(FT_New_Memory_Face(sLibrary, data.ptr.get(), data.length, 0, &face))
{
LOG(LogError) << "Error creating font face! (mPath: " << mPath << ", data.length: " << data.length << ")";
return NULL;
}
FT_Set_Pixel_Sizes(face, 0, mSize);
//copy the glyphs into the texture
int x = 0;
int y = 0;
int maxHeight = 0;
FT_GlyphSlot g = face->glyph; FT_GlyphSlot g = face->glyph;
for(int i = 32; i < 128; i++)
{
if(FT_Load_Char(face, i, FT_LOAD_RENDER))
continue;
if(x + g->bitmap.width >= mTextureWidth) if(FT_Load_Char(face, id, FT_LOAD_RENDER))
{ {
x = 0; LOG(LogError) << "Could not find glyph for character " << id << " for font " << mPath << ", size " << mSize << "!";
y += maxHeight + 1; //leave one pixel of space between glyphs return NULL;
maxHeight = 0;
} }
if(g->bitmap.rows > maxHeight) Eigen::Vector2i glyphSize(g->bitmap.width, g->bitmap.rows);
maxHeight = g->bitmap.rows;
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, g->bitmap.width, g->bitmap.rows, GL_ALPHA, GL_UNSIGNED_BYTE, g->bitmap.buffer); FontTexture* tex = NULL;
Eigen::Vector2i cursor;
getTextureForNewGlyph(glyphSize, tex, cursor);
// getTextureForNewGlyph can fail if the glyph is bigger than the max texture size (absurdly large font size)
mCharData[i].texX = x; if(tex == NULL)
mCharData[i].texY = y; {
mCharData[i].texW = g->bitmap.width; LOG(LogError) << "Could not create glyph for character " << id << " for font " << mPath << ", size " << mSize << " (no suitable texture found)!";
mCharData[i].texH = g->bitmap.rows; return NULL;
mCharData[i].advX = (float)g->metrics.horiAdvance / 64.0f;
mCharData[i].advY = (float)g->metrics.vertAdvance / 64.0f;
mCharData[i].bearingX = (float)g->metrics.horiBearingX / 64.0f;
mCharData[i].bearingY = (float)g->metrics.horiBearingY / 64.0f;
if(mCharData[i].texH > mMaxGlyphHeight)
mMaxGlyphHeight = mCharData[i].texH;
x += g->bitmap.width + 1; //leave one pixel of space between glyphs
} }
// create glyph
Glyph& glyph = mGlyphMap[id];
glyph.texture = tex;
glyph.texPos << cursor.x() / (float)tex->textureSize.x(), cursor.y() / (float)tex->textureSize.y();
glyph.texSize << glyphSize.x() / (float)tex->textureSize.x(), glyphSize.y() / (float)tex->textureSize.y();
glyph.advance << (float)g->metrics.horiAdvance / 64.0f, (float)g->metrics.vertAdvance / 64.0f;
glyph.bearing << (float)g->metrics.horiBearingX / 64.0f, (float)g->metrics.horiBearingY / 64.0f;
// upload glyph bitmap to texture
glBindTexture(GL_TEXTURE_2D, tex->textureId);
glTexSubImage2D(GL_TEXTURE_2D, 0, cursor.x(), cursor.y(), glyphSize.x(), glyphSize.y(), GL_ALPHA, GL_UNSIGNED_BYTE, g->bitmap.buffer);
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
// free the FT face
FT_Done_Face(face); FT_Done_Face(face);
if((y + maxHeight) >= mTextureHeight) // update max glyph height
{ if(glyphSize.y() > mMaxGlyphHeight)
//failed to create a proper font texture mMaxGlyphHeight = glyphSize.y();
LOG(LogWarning) << "Font \"" << mPath << "\" with size " << mSize << " exceeded max texture size! Trying again...";
//try a 3/4th smaller size and redo initialization // done
mFontScale *= 1.25f; return &glyph;
mSize = (int)(mSize * (1.0f / mFontScale));
deinit();
init(data);
}
} }
void Font::renderTextCache(TextCache* cache) void Font::renderTextCache(TextCache* cache)
{ {
if(!mTextureID)
{
LOG(LogError) << "Error - tried to draw with Font that has no texture loaded!";
return;
}
if(cache == NULL) if(cache == NULL)
{ {
LOG(LogError) << "Attempted to draw NULL TextCache!"; LOG(LogError) << "Attempted to draw NULL TextCache!";
return; return;
} }
glBindTexture(GL_TEXTURE_2D, mTextureID); for(auto it = cache->vertexLists.begin(); it != cache->vertexLists.end(); it++)
{
assert(it->textureId != 0);
auto vertexList = *it;
glBindTexture(GL_TEXTURE_2D, it->textureId);
glEnable(GL_TEXTURE_2D); glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
@ -219,11 +398,11 @@ void Font::renderTextCache(TextCache* cache)
glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnableClientState(GL_COLOR_ARRAY); glEnableClientState(GL_COLOR_ARRAY);
glVertexPointer(2, GL_FLOAT, sizeof(TextCache::Vertex), &cache->verts[0].pos); glVertexPointer(2, GL_FLOAT, sizeof(TextCache::Vertex), it->verts[0].pos.data());
glTexCoordPointer(2, GL_FLOAT, sizeof(TextCache::Vertex), &cache->verts[0].tex); glTexCoordPointer(2, GL_FLOAT, sizeof(TextCache::Vertex), it->verts[0].tex.data());
glColorPointer(4, GL_UNSIGNED_BYTE, 0, cache->colors); glColorPointer(4, GL_UNSIGNED_BYTE, 0, it->colors.data());
glDrawArrays(GL_TRIANGLES, 0, cache->vertCount); glDrawArrays(GL_TRIANGLES, 0, it->verts.size());
glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY);
@ -232,31 +411,32 @@ void Font::renderTextCache(TextCache* cache)
glDisable(GL_TEXTURE_2D); glDisable(GL_TEXTURE_2D);
glDisable(GL_BLEND); glDisable(GL_BLEND);
} }
}
Eigen::Vector2f Font::sizeText(std::string text, float lineSpacing) const Eigen::Vector2f Font::sizeText(std::string text, float lineSpacing)
{ {
float lineWidth = 0.0f; float lineWidth = 0.0f;
float highestWidth = 0.0f; float highestWidth = 0.0f;
float y = getHeight(lineSpacing); const float lineHeight = getHeight(lineSpacing);
for(unsigned int i = 0; i < text.length(); i++) float y = lineHeight;
size_t i = 0;
while(i < text.length())
{ {
unsigned char letter = text[i]; UnicodeChar character = readUnicodeChar(text, i); // advances i
if(letter == '\n') if(character == (UnicodeChar)'\n')
{ {
if(lineWidth > highestWidth) if(lineWidth > highestWidth)
highestWidth = lineWidth; highestWidth = lineWidth;
lineWidth = 0.0f; lineWidth = 0.0f;
y += getHeight(lineSpacing); y += lineHeight;
} }
if(letter < 32 || letter >= 128) lineWidth += getGlyph(character)->advance.x();
letter = 127;
lineWidth += mCharData[letter].advX * mFontScale;
} }
if(lineWidth > highestWidth) if(lineWidth > highestWidth)
@ -267,17 +447,18 @@ Eigen::Vector2f Font::sizeText(std::string text, float lineSpacing) const
float Font::getHeight(float lineSpacing) const float Font::getHeight(float lineSpacing) const
{ {
return mMaxGlyphHeight * lineSpacing * mFontScale; return mMaxGlyphHeight * lineSpacing;
} }
float Font::getLetterHeight() const float Font::getLetterHeight()
{ {
return mCharData['S'].texH * mFontScale; Glyph* glyph = getGlyph((UnicodeChar)'S');
return glyph->texSize.y() * glyph->texture->textureSize.y();
} }
//the worst algorithm ever written //the worst algorithm ever written
//breaks up a normal string with newlines to make it fit xLen //breaks up a normal string with newlines to make it fit xLen
std::string Font::wrapText(std::string text, float xLen) const std::string Font::wrapText(std::string text, float xLen)
{ {
std::string out; std::string out;
@ -317,49 +498,46 @@ std::string Font::wrapText(std::string text, float xLen) const
return out; return out;
} }
Eigen::Vector2f Font::sizeWrappedText(std::string text, float xLen, float lineSpacing) const Eigen::Vector2f Font::sizeWrappedText(std::string text, float xLen, float lineSpacing)
{ {
text = wrapText(text, xLen); text = wrapText(text, xLen);
return sizeText(text, lineSpacing); return sizeText(text, lineSpacing);
} }
Eigen::Vector2f Font::getWrappedTextCursorOffset(std::string text, float xLen, int cursor, float lineSpacing) const Eigen::Vector2f Font::getWrappedTextCursorOffset(std::string text, float xLen, size_t stop, float lineSpacing)
{ {
std::string wrappedText = wrapText(text, xLen); std::string wrappedText = wrapText(text, xLen);
float lineWidth = 0.0f; float lineWidth = 0.0f;
float y = 0.0f; float y = 0.0f;
unsigned int stop = (unsigned int)cursor; size_t wrapCursor = 0;
unsigned int wrapOffset = 0; size_t cursor = 0;
for(unsigned int i = 0; i < stop; i++) while(cursor < stop)
{ {
unsigned char wrappedLetter = wrappedText[i + wrapOffset]; UnicodeChar wrappedCharacter = readUnicodeChar(wrappedText, wrapCursor);
unsigned char letter = text[i]; UnicodeChar character = readUnicodeChar(text, cursor);
if(wrappedLetter == '\n' && letter != '\n') if(wrappedCharacter == (UnicodeChar)'\n' && character != (UnicodeChar)'\n')
{ {
//this is where the wordwrap inserted a newline //this is where the wordwrap inserted a newline
//reset lineWidth and increment y, but don't consume a cursor character //reset lineWidth and increment y, but don't consume a cursor character
lineWidth = 0.0f; lineWidth = 0.0f;
y += getHeight(lineSpacing); y += getHeight(lineSpacing);
wrapOffset++; cursor = getPrevCursor(text, cursor); // unconsume
i--;
continue; continue;
} }
if(letter == '\n') if(character == (UnicodeChar)'\n')
{ {
lineWidth = 0.0f; lineWidth = 0.0f;
y += getHeight(lineSpacing); y += getHeight(lineSpacing);
wrapCursor = getPrevCursor(wrappedText, wrapCursor); // unconsume
continue; continue;
} }
if(letter < 32 || letter >= 128) lineWidth += getGlyph(character)->advance.x();
letter = 127;
lineWidth += mCharData[letter].advX * mFontScale;
} }
return Eigen::Vector2f(lineWidth, y); return Eigen::Vector2f(lineWidth, y);
@ -390,82 +568,97 @@ float Font::getNewlineStartOffset(const std::string& text, const unsigned int& c
} }
} }
TextCache* Font::buildTextCache(const std::string& text, Eigen::Vector2f offset, unsigned int color, float xLen, Alignment alignment, float lineSpacing) inline float font_round(float v)
{ {
if(!mTextureID) return round(v);
{
LOG(LogError) << "Error - tried to build TextCache with Font that has no texture loaded!";
return NULL;
} }
const unsigned int vertCount = text.length() * 2 * 3; // 2 triangles of 3 vertices per character TextCache* Font::buildTextCache(const std::string& text, Eigen::Vector2f offset, unsigned int color, float xLen, Alignment alignment, float lineSpacing)
TextCache::Vertex* vert = new TextCache::Vertex[vertCount]; {
GLubyte* colors = new GLubyte[vertCount * 4];
//texture atlas width/height
float tw = (float)mTextureWidth;
float th = (float)mTextureHeight;
float x = offset[0] + (xLen != 0 ? getNewlineStartOffset(text, 0, xLen, alignment) : 0); float x = offset[0] + (xLen != 0 ? getNewlineStartOffset(text, 0, xLen, alignment) : 0);
float yTop = mCharData['S'].bearingY * mFontScale; float yTop = getGlyph((UnicodeChar)'S')->bearing.y();
float yBot = getHeight(lineSpacing); float yBot = getHeight(lineSpacing);
float y = offset[1] + (yBot + yTop)/2.0f; float y = offset[1] + (yBot + yTop)/2.0f;
for(unsigned int i = 0, charNum = 0; i < vertCount; i += 6, charNum++) // vertices by texture
{ std::map< GLuint, std::vector<TextCache::Vertex> > vertMap;
unsigned char letter = text[charNum];
if(letter == '\n') size_t cursor = 0;
UnicodeChar character;
Glyph* glyph;
while(cursor < text.length())
{
character = readUnicodeChar(text, cursor); // also advances cursor
// invalid character
if(character == 0)
continue;
if(character == (UnicodeChar)'\n')
{ {
y += getHeight(lineSpacing); y += getHeight(lineSpacing);
x = offset[0] + (xLen != 0 ? getNewlineStartOffset(text, charNum+1, xLen, alignment) : 0); x = offset[0] + (xLen != 0 ? getNewlineStartOffset(text, cursor /* cursor is already advanced */, xLen, alignment) : 0);
memset(&vert[i], 0, 6 * sizeof(TextCache::Vertex));
continue; continue;
} }
if(letter < 32 || letter >= 128) glyph = getGlyph(character);
letter = 127; //print [X] if character is not standard ASCII if(glyph == NULL)
continue;
//the glyph might not start at the cursor position, but needs to be shifted a bit std::vector<TextCache::Vertex>& verts = vertMap[glyph->texture->textureId];
const float glyphStartX = x + mCharData[letter].bearingX * mFontScale; size_t oldVertSize = verts.size();
//order is bottom left, top right, top left verts.resize(oldVertSize + 6);
vert[i + 0].pos << glyphStartX, y + (mCharData[letter].texH - mCharData[letter].bearingY) * mFontScale; TextCache::Vertex* tri = verts.data() + oldVertSize;
vert[i + 1].pos << glyphStartX + mCharData[letter].texW * mFontScale, y - mCharData[letter].bearingY * mFontScale;
vert[i + 2].pos << glyphStartX, vert[i + 1].pos.y();
Eigen::Vector2i charTexCoord(mCharData[letter].texX, mCharData[letter].texY); const float glyphStartX = x + glyph->bearing.x();
Eigen::Vector2i charTexSize(mCharData[letter].texW, mCharData[letter].texH);
vert[i + 0].tex << charTexCoord.x() / tw, (charTexCoord.y() + charTexSize.y()) / th; const Eigen::Vector2i& textureSize = glyph->texture->textureSize;
vert[i + 1].tex << (charTexCoord.x() + charTexSize.x()) / tw, charTexCoord.y() / th;
vert[i + 2].tex << vert[i + 0].tex.x(), vert[i + 1].tex.y();
//next triangle (second half of the quad)
vert[i + 3].pos = vert[i + 0].pos;
vert[i + 4].pos = vert[i + 1].pos;
vert[i + 5].pos[0] = vert[i + 1].pos.x();
vert[i + 5].pos[1] = vert[i + 0].pos.y();
vert[i + 3].tex = vert[i + 0].tex;
vert[i + 4].tex = vert[i + 1].tex;
vert[i + 5].tex[0] = vert[i + 1].tex.x();
vert[i + 5].tex[1] = vert[i + 0].tex.y();
// triangle 1
// round to fix some weird "cut off" text bugs // round to fix some weird "cut off" text bugs
for(unsigned int j = i; j < i + 6; j++) tri[0].pos << font_round(glyphStartX), font_round(y + (glyph->texSize.y() * textureSize.y() - glyph->bearing.y()));
tri[1].pos << font_round(glyphStartX + glyph->texSize.x() * textureSize.x()), font_round(y - glyph->bearing.y());
tri[2].pos << tri[0].pos.x(), tri[1].pos.y();
//tri[0].tex << 0, 0;
//tri[0].tex << 1, 1;
//tri[0].tex << 0, 1;
tri[0].tex << glyph->texPos.x(), glyph->texPos.y() + glyph->texSize.y();
tri[1].tex << glyph->texPos.x() + glyph->texSize.x(), glyph->texPos.y();
tri[2].tex << tri[0].tex.x(), tri[1].tex.y();
// triangle 2
tri[3].pos = tri[0].pos;
tri[4].pos = tri[1].pos;
tri[5].pos << tri[1].pos.x(), tri[0].pos.y();
tri[3].tex = tri[0].tex;
tri[4].tex = tri[1].tex;
tri[5].tex << tri[1].tex.x(), tri[0].tex.y();
// advance
x += glyph->advance.x();
}
//TextCache::CacheMetrics metrics = { sizeText(text, lineSpacing) };
TextCache* cache = new TextCache();
cache->vertexLists.resize(vertMap.size());
cache->metrics = { sizeText(text, lineSpacing) };
unsigned int i = 0;
for(auto it = vertMap.begin(); it != vertMap.end(); it++)
{ {
vert[j].pos[0] = round(vert[j].pos[0]); TextCache::VertexList& vertList = cache->vertexLists.at(i);
vert[j].pos[1] = round(vert[j].pos[1]);
}
x += mCharData[letter].advX * mFontScale; vertList.textureId = it->first;
} vertList.verts = it->second;
TextCache::CacheMetrics metrics = { sizeText(text, lineSpacing) }; vertList.colors.resize(4 * it->second.size());
TextCache* cache = new TextCache(vertCount, vert, colors, metrics); Renderer::buildGLColorArray(vertList.colors.data(), color, it->second.size());
if(color != 0x00000000) }
cache->setColor(color);
return cache; return cache;
} }
@ -475,19 +668,10 @@ TextCache* Font::buildTextCache(const std::string& text, float offsetX, float of
return buildTextCache(text, Eigen::Vector2f(offsetX, offsetY), color, 0.0f); return buildTextCache(text, Eigen::Vector2f(offsetX, offsetY), color, 0.0f);
} }
TextCache::TextCache(int verts, Vertex* v, GLubyte* c, const CacheMetrics& m) : vertCount(verts), verts(v), colors(c), metrics(m)
{
}
TextCache::~TextCache()
{
delete[] verts;
delete[] colors;
}
void TextCache::setColor(unsigned int color) void TextCache::setColor(unsigned int color)
{ {
Renderer::buildGLColorArray(const_cast<GLubyte*>(colors), color, vertCount); for(auto it = vertexLists.begin(); it != vertexLists.end(); it++)
Renderer::buildGLColorArray(it->colors.data(), color, it->verts.size());
} }
std::shared_ptr<Font> Font::getFromTheme(const ThemeData::ThemeElement* elem, unsigned int properties, const std::shared_ptr<Font>& orig) std::shared_ptr<Font> Font::getFromTheme(const ThemeData::ThemeElement* elem, unsigned int properties, const std::shared_ptr<Font>& orig)

View file

@ -18,6 +18,8 @@ class TextCache;
#define FONT_PATH_LIGHT ":/opensans_hebrew_condensed_light.ttf" #define FONT_PATH_LIGHT ":/opensans_hebrew_condensed_light.ttf"
#define FONT_PATH_REGULAR ":/opensans_hebrew_condensed_regular.ttf" #define FONT_PATH_REGULAR ":/opensans_hebrew_condensed_regular.ttf"
typedef unsigned long UnicodeChar;
enum Alignment enum Alignment
{ {
ALIGN_LEFT, ALIGN_LEFT,
@ -36,17 +38,17 @@ public:
virtual ~Font(); virtual ~Font();
Eigen::Vector2f sizeText(std::string text, float lineSpacing = 1.5f) const; // Returns the expected size of a string when rendered. Extra spacing is applied to the Y axis. Eigen::Vector2f sizeText(std::string text, float lineSpacing = 1.5f); // Returns the expected size of a string when rendered. Extra spacing is applied to the Y axis.
TextCache* buildTextCache(const std::string& text, float offsetX, float offsetY, unsigned int color); TextCache* buildTextCache(const std::string& text, float offsetX, float offsetY, unsigned int color);
TextCache* buildTextCache(const std::string& text, Eigen::Vector2f offset, unsigned int color, float xLen, Alignment alignment = ALIGN_LEFT, float lineSpacing = 1.5f); TextCache* buildTextCache(const std::string& text, Eigen::Vector2f offset, unsigned int color, float xLen, Alignment alignment = ALIGN_LEFT, float lineSpacing = 1.5f);
void renderTextCache(TextCache* cache); void renderTextCache(TextCache* cache);
std::string wrapText(std::string text, float xLen) const; // Inserts newlines into text to make it wrap properly. std::string wrapText(std::string text, float xLen); // Inserts newlines into text to make it wrap properly.
Eigen::Vector2f sizeWrappedText(std::string text, float xLen, float lineSpacing = 1.5f) const; // Returns the expected size of a string after wrapping is applied. Eigen::Vector2f sizeWrappedText(std::string text, float xLen, float lineSpacing = 1.5f); // Returns the expected size of a string after wrapping is applied.
Eigen::Vector2f getWrappedTextCursorOffset(std::string text, float xLen, int cursor, float lineSpacing = 1.5f) const; // Returns the position of of the cursor after moving "cursor" characters. Eigen::Vector2f getWrappedTextCursorOffset(std::string text, float xLen, size_t cursor, float lineSpacing = 1.5f); // Returns the position of of the cursor after moving "cursor" characters.
float getHeight(float lineSpacing = 1.5f) const; float getHeight(float lineSpacing = 1.5f) const;
float getLetterHeight() const; float getLetterHeight();
void unload(std::shared_ptr<ResourceManager>& rm) override; void unload(std::shared_ptr<ResourceManager>& rm) override;
void reload(std::shared_ptr<ResourceManager>& rm) override; void reload(std::shared_ptr<ResourceManager>& rm) override;
@ -61,42 +63,62 @@ public:
size_t getMemUsage() const; // returns an approximation of VRAM used by this font's texture (in bytes) size_t getMemUsage() const; // returns an approximation of VRAM used by this font's texture (in bytes)
static size_t getTotalMemUsage(); // returns an approximation of total VRAM used by font textures (in bytes) static size_t getTotalMemUsage(); // returns an approximation of total VRAM used by font textures (in bytes)
// utf8 stuff
static size_t getNextCursor(const std::string& str, size_t cursor);
static size_t getPrevCursor(const std::string& str, size_t cursor);
static size_t moveCursor(const std::string& str, size_t cursor, int moveAmt); // negative moveAmt = move backwards, positive = move forwards
static UnicodeChar readUnicodeChar(const std::string& str, size_t& cursor); // reads unicode character at cursor AND moves cursor to the next valid unicode char
private: private:
static FT_Library sLibrary; static FT_Library sLibrary;
static std::map< std::pair<std::string, int>, std::weak_ptr<Font> > sFontMap; static std::map< std::pair<std::string, int>, std::weak_ptr<Font> > sFontMap;
Font(int size, const std::string& path); Font(int size, const std::string& path);
void init(ResourceData data); void reloadGlyphTextures();
void deinit(); void freeTextures();
void buildAtlas(ResourceData data); //Builds a "texture atlas," one big OpenGL texture with glyphs 32 to 128. struct FontTexture
//contains sizing information for every glyph.
struct CharData
{ {
int texX; GLuint textureId;
int texY; Eigen::Vector2i textureSize;
int texW;
int texH;
float advX; //!<The horizontal distance to advance to the next character after this one Eigen::Vector2i writePos;
float advY; //!<The vertical distance to advance to the next character after this one int rowHeight;
float bearingX; //!<The horizontal distance from the cursor to the start of the character FontTexture()
float bearingY; //!<The vertical distance from the cursor to the start of the character {
textureId = 0;
textureSize << 2048, 512;
writePos = Eigen::Vector2i::Zero();
rowHeight = 0;
}
bool findEmpty(const Eigen::Vector2i& size, Eigen::Vector2i& cursor_out);
}; };
CharData mCharData[128]; std::vector<FontTexture> mTextures;
GLuint mTextureID; void getTextureForNewGlyph(const Eigen::Vector2i& glyphSize, FontTexture*& tex_out, Eigen::Vector2i& cursor_out);
struct Glyph
{
FontTexture* texture;
Eigen::Vector2f texPos;
Eigen::Vector2f texSize; // in texels!
Eigen::Vector2f advance;
Eigen::Vector2f bearing;
};
std::map<UnicodeChar, Glyph> mGlyphMap;
Glyph* getGlyph(UnicodeChar id);
int mTextureWidth; //OpenGL texture width
int mTextureHeight; //OpenGL texture height
int mMaxGlyphHeight; int mMaxGlyphHeight;
float mFontScale; //!<Font scale factor. It is > 1.0 if the font would be to big for the texture
int mSize; const int mSize;
const std::string mPath; const std::string mPath;
float getNewlineStartOffset(const std::string& text, const unsigned int& charStart, const float& xLen, const Alignment& alignment); float getNewlineStartOffset(const std::string& text, const unsigned int& charStart, const float& xLen, const Alignment& alignment);
@ -110,13 +132,23 @@ private:
// Keep in mind you still need the Font object to render a TextCache (as the Font holds the OpenGL texture), and if a Font changes your TextCache may become invalid. // Keep in mind you still need the Font object to render a TextCache (as the Font holds the OpenGL texture), and if a Font changes your TextCache may become invalid.
class TextCache class TextCache
{ {
public: protected:
struct Vertex struct Vertex
{ {
Eigen::Vector2f pos; Eigen::Vector2f pos;
Eigen::Vector2f tex; Eigen::Vector2f tex;
}; };
struct VertexList
{
GLuint textureId;
std::vector<Vertex> verts;
std::vector<GLubyte> colors;
};
std::vector<VertexList> vertexLists;
public:
struct CacheMetrics struct CacheMetrics
{ {
Eigen::Vector2f size; Eigen::Vector2f size;
@ -124,10 +156,5 @@ public:
void setColor(unsigned int color); void setColor(unsigned int color);
TextCache(int verts, Vertex* v, GLubyte* c, const CacheMetrics& m); friend Font;
~TextCache();
int vertCount;
Vertex* verts;
GLubyte* colors;
}; };