From c4d0e0229ea114c0d424a96321340594fc1599df Mon Sep 17 00:00:00 2001
From: Aloshi <allofquist@yahoo.com>
Date: Sat, 30 Aug 2014 15:36:56 -0500
Subject: [PATCH] Added fallback font support. Untested on Linux (but should
 fail gracefully).

---
 es-core/src/resources/Font.cpp | 140 +++++++++++++++++++++++++++------
 es-core/src/resources/Font.h   |  13 +++
 2 files changed, 127 insertions(+), 26 deletions(-)

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;