diff --git a/es-core/src/resources/Font.cpp b/es-core/src/resources/Font.cpp index 99a494498..91bda2be9 100644 --- a/es-core/src/resources/Font.cpp +++ b/es-core/src/resources/Font.cpp @@ -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() { assert(sLibrary == NULL); @@ -153,6 +167,9 @@ size_t Font::getMemUsage() const for(auto it = mTextures.begin(); it != mTextures.end(); it++) memUsage += it->textureSize.x() * it->textureSize.y() * 4; + for(auto it = mFaceCache.begin(); it != mFaceCache.end(); it++) + memUsage += it->second->data.length; + return memUsage; } @@ -186,6 +203,8 @@ Font::Font(int size, const std::string& path) : mSize(size), mPath(path) // always initialize ASCII characters for(UnicodeChar i = 32; i < 128; i++) getGlyph(i); + + clearFaceCache(); } 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) { // is it already loaded? @@ -334,18 +440,13 @@ Font::Glyph* Font::getGlyph(UnicodeChar id) 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)) + FT_Face face = getFaceForChar(id); + if(!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; } - FT_Set_Pixel_Sizes(face, 0, mSize); - FT_GlyphSlot g = face->glyph; 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); glBindTexture(GL_TEXTURE_2D, 0); - // free the FT face - FT_Done_Face(face); - // update max glyph height if(glyphSize.y() > mMaxGlyphHeight) mMaxGlyphHeight = glyphSize.y(); @@ -402,21 +500,12 @@ void Font::rebuildTextures() 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 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 FT_Load_Char(face, it->first, FT_LOAD_RENDER); @@ -432,9 +521,6 @@ void Font::rebuildTextures() } glBindTexture(GL_TEXTURE_2D, 0); - - // free the FT face - FT_Done_Face(face); } 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()); } + clearFaceCache(); + return cache; } diff --git a/es-core/src/resources/Font.h b/es-core/src/resources/Font.h index 4f8b10b20..daaecbead 100644 --- a/es-core/src/resources/Font.h +++ b/es-core/src/resources/Font.h @@ -92,6 +92,15 @@ private: 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 unloadTextures(); @@ -99,6 +108,10 @@ private: 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 { FontTexture* texture;