Added fallback font support. Untested on Linux (but should fail gracefully).

This commit is contained in:
Aloshi 2014-08-30 15:36:56 -05:00
parent 9f040f4c71
commit c4d0e0229e
2 changed files with 127 additions and 26 deletions
es-core/src/resources

View file

@ -137,6 +137,20 @@ UnicodeChar Font::readUnicodeChar(const std::string& str, size_t& cursor)
} }
Font::FontFace::FontFace(ResourceData&& d, int size) : data(d)
{
int err = FT_New_Memory_Face(sLibrary, data.ptr.get(), data.length, 0, &face);
assert(!err);
FT_Set_Pixel_Sizes(face, 0, size);
}
Font::FontFace::~FontFace()
{
if(face)
FT_Done_Face(face);
}
void Font::initLibrary() void Font::initLibrary()
{ {
assert(sLibrary == NULL); assert(sLibrary == NULL);
@ -153,6 +167,9 @@ size_t Font::getMemUsage() const
for(auto it = mTextures.begin(); it != mTextures.end(); it++) for(auto it = mTextures.begin(); it != mTextures.end(); it++)
memUsage += it->textureSize.x() * it->textureSize.y() * 4; memUsage += it->textureSize.x() * it->textureSize.y() * 4;
for(auto it = mFaceCache.begin(); it != mFaceCache.end(); it++)
memUsage += it->second->data.length;
return memUsage; return memUsage;
} }
@ -186,6 +203,8 @@ Font::Font(int size, const std::string& path) : mSize(size), mPath(path)
// always initialize ASCII characters // always initialize ASCII characters
for(UnicodeChar i = 32; i < 128; i++) for(UnicodeChar i = 32; i < 128; i++)
getGlyph(i); getGlyph(i);
clearFaceCache();
} }
Font::~Font() Font::~Font()
@ -326,6 +345,93 @@ void Font::getTextureForNewGlyph(const Eigen::Vector2i& glyphSize, FontTexture*&
} }
} }
std::vector<std::string> getFallbackFontPaths()
{
#ifdef WIN32
// Windows
// get this system's equivalent of "C:\Windows" (might be on a different drive or in a different folder)
// so we can check the Fonts subdirectory for fallback fonts
TCHAR winDir[MAX_PATH];
GetWindowsDirectory(winDir, MAX_PATH);
std::string fontDir = winDir;
fontDir += "\\Fonts\\";
const char* fontNames[] = {
"meiryo.ttc", // japanese
"simhei.ttf", // chinese
"arial.ttf" // latin
};
//prepend to font file names
std::vector<std::string> fontPaths;
fontPaths.reserve(sizeof(fontNames) / sizeof(fontNames[0]));
for(unsigned int i = 0; i < sizeof(fontNames) / sizeof(fontNames[0]); i++)
{
std::string path = fontDir + fontNames[i];
if(ResourceManager::getInstance()->fileExists(path))
fontPaths.push_back(path);
}
fontPaths.shrink_to_fit();
return fontPaths;
#else
// Linux
// TODO
const char* paths[] = {
"/usr/share/fonts/truetype/ttf-dejavu/DejaVuSerif.ttf",
"/usr/share/fonts/TTF/DejaVuSerif.ttf",
"/usr/share/fonts/dejavu/DejaVuSerif.ttf"
};
std::vector<std::string> fontPaths;
for(unsigned int i = 0; i < sizeof(paths) / sizeof(paths[0]); i++)
{
if(ResourceManager::getInstance()->fileExists(paths[i]))
fontPaths.push_back(paths[i]);
}
fontPaths.shrink_to_fit();
return fonts;
#endif
}
FT_Face Font::getFaceForChar(UnicodeChar id)
{
static const std::vector<std::string> fallbackFonts = getFallbackFontPaths();
// look through our current font + fallback fonts to see if any have the glyph we're looking for
for(unsigned int i = 0; i < fallbackFonts.size() + 1; i++)
{
auto fit = mFaceCache.find(i);
if(fit == mFaceCache.end()) // doesn't exist yet
{
// i == 0 -> mPath
// otherwise, take from fallbackFonts
const std::string& path = (i == 0 ? mPath : fallbackFonts.at(i - 1));
ResourceData data = ResourceManager::getInstance()->getFileData(path);
mFaceCache[i] = std::unique_ptr<FontFace>(new FontFace(std::move(data), mSize));
fit = mFaceCache.find(i);
}
if(FT_Get_Char_Index(fit->second->face, id) != 0)
return fit->second->face;
}
// nothing has a valid glyph - return the "real" face so we get a "missing" character
return mFaceCache.begin()->second->face;
}
void Font::clearFaceCache()
{
mFaceCache.clear();
}
Font::Glyph* Font::getGlyph(UnicodeChar id) Font::Glyph* Font::getGlyph(UnicodeChar id)
{ {
// is it already loaded? // is it already loaded?
@ -334,18 +440,13 @@ Font::Glyph* Font::getGlyph(UnicodeChar id)
return &it->second; return &it->second;
// nope, need to make a glyph // nope, need to make a glyph
// get the font data FT_Face face = getFaceForChar(id);
ResourceData data = ResourceManager::getInstance()->getFileData(mPath); if(!face)
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 << ")"; LOG(LogError) << "Could not find appropriate font face for character " << id << " for font " << mPath;
return NULL; return NULL;
} }
FT_Set_Pixel_Sizes(face, 0, mSize);
FT_GlyphSlot g = face->glyph; FT_GlyphSlot g = face->glyph;
if(FT_Load_Char(face, id, FT_LOAD_RENDER)) if(FT_Load_Char(face, id, FT_LOAD_RENDER))
@ -382,9 +483,6 @@ Font::Glyph* Font::getGlyph(UnicodeChar id)
glTexSubImage2D(GL_TEXTURE_2D, 0, cursor.x(), cursor.y(), glyphSize.x(), glyphSize.y(), GL_ALPHA, GL_UNSIGNED_BYTE, g->bitmap.buffer); 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);
// update max glyph height // update max glyph height
if(glyphSize.y() > mMaxGlyphHeight) if(glyphSize.y() > mMaxGlyphHeight)
mMaxGlyphHeight = glyphSize.y(); mMaxGlyphHeight = glyphSize.y();
@ -402,21 +500,12 @@ void Font::rebuildTextures()
it->initTexture(); it->initTexture();
} }
// open the font file
ResourceData data = ResourceManager::getInstance()->getFileData(mPath);
// load the font with FT
FT_Face face;
FT_New_Memory_Face(sLibrary, data.ptr.get(), data.length, 0, &face);
// set the size
FT_Set_Pixel_Sizes(face, 0, mSize);
FT_GlyphSlot glyphSlot = face->glyph;
// reupload the texture data // reupload the texture data
for(auto it = mGlyphMap.begin(); it != mGlyphMap.end(); it++) for(auto it = mGlyphMap.begin(); it != mGlyphMap.end(); it++)
{ {
FT_Face face = getFaceForChar(it->first);
FT_GlyphSlot glyphSlot = face->glyph;
// load the glyph bitmap through FT // load the glyph bitmap through FT
FT_Load_Char(face, it->first, FT_LOAD_RENDER); FT_Load_Char(face, it->first, FT_LOAD_RENDER);
@ -432,9 +521,6 @@ void Font::rebuildTextures()
} }
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
// free the FT face
FT_Done_Face(face);
} }
void Font::renderTextCache(TextCache* cache) void Font::renderTextCache(TextCache* cache)
@ -722,6 +808,8 @@ TextCache* Font::buildTextCache(const std::string& text, Eigen::Vector2f offset,
Renderer::buildGLColorArray(vertList.colors.data(), color, it->second.size()); Renderer::buildGLColorArray(vertList.colors.data(), color, it->second.size());
} }
clearFaceCache();
return cache; return cache;
} }

View file

@ -92,6 +92,15 @@ private:
void deinitTexture(); // deinitializes the OpenGL texture if any exists, is automatically called in the destructor void deinitTexture(); // deinitializes the OpenGL texture if any exists, is automatically called in the destructor
}; };
struct FontFace
{
const ResourceData data;
FT_Face face;
FontFace(ResourceData&& d, int size);
virtual ~FontFace();
};
void rebuildTextures(); void rebuildTextures();
void unloadTextures(); void unloadTextures();
@ -99,6 +108,10 @@ private:
void getTextureForNewGlyph(const Eigen::Vector2i& glyphSize, FontTexture*& tex_out, Eigen::Vector2i& cursor_out); void getTextureForNewGlyph(const Eigen::Vector2i& glyphSize, FontTexture*& tex_out, Eigen::Vector2i& cursor_out);
std::map< unsigned int, std::unique_ptr<FontFace> > mFaceCache;
FT_Face getFaceForChar(UnicodeChar id);
void clearFaceCache();
struct Glyph struct Glyph
{ {
FontTexture* texture; FontTexture* texture;