Browse Source

Replace fallback font nonsense with automatic per-glyph fallback (#11084)

sfan5 3 years ago
parent
commit
8d89f5f0cc

+ 2 - 11
builtin/settingtypes.txt

@@ -859,7 +859,7 @@ font_path (Regular font path) filepath fonts/Arimo-Regular.ttf
 
 font_path_bold (Bold font path) filepath fonts/Arimo-Bold.ttf
 font_path_italic (Italic font path) filepath fonts/Arimo-Italic.ttf
-font_path_bolditalic (Bold and italic font path) filepath fonts/Arimo-BoldItalic.ttf
+font_path_bold_italic (Bold and italic font path) filepath fonts/Arimo-BoldItalic.ttf
 
 #    Font size of the monospace font in point (pt).
 mono_font_size (Monospace font size) int 15 1
@@ -872,16 +872,7 @@ mono_font_path (Monospace font path) filepath fonts/Cousine-Regular.ttf
 
 mono_font_path_bold (Bold monospace font path) filepath fonts/Cousine-Bold.ttf
 mono_font_path_italic (Italic monospace font path) filepath fonts/Cousine-Italic.ttf
-mono_font_path_bolditalic (Bold and italic monospace font path) filepath fonts/Cousine-BoldItalic.ttf
-
-#    Font size of the fallback font in point (pt).
-fallback_font_size (Fallback font size) int 15 1
-
-#    Shadow offset (in pixels) of the fallback font. If 0, then shadow will not be drawn.
-fallback_font_shadow (Fallback font shadow) int 1
-
-#    Opaqueness (alpha) of the shadow behind the fallback font, between 0 and 255.
-fallback_font_shadow_alpha (Fallback font shadow alpha) int 128 0 255
+mono_font_path_bold_italic (Bold and italic monospace font path) filepath fonts/Cousine-BoldItalic.ttf
 
 #    Path of the fallback font.
 #    If “freetype” setting is enabled: Must be a TrueType font.

+ 0 - 12
po/minetest.pot

@@ -1085,18 +1085,6 @@ msgstr ""
 msgid "Invalid gamespec."
 msgstr ""
 
-#. ~ DO NOT TRANSLATE THIS LITERALLY!
-#. This is a special string. Put either "no" or "yes"
-#. into the translation field (literally).
-#. Choose "yes" if the language requires use of the fallback
-#. font, "no" otherwise.
-#. The fallback font is (normally) required for languages with
-#. non-Latin script, like Chinese.
-#. When in doubt, test your translation.
-#: src/client/fontengine.cpp
-msgid "needs_fallback_font"
-msgstr ""
-
 #: src/client/game.cpp
 msgid "Shutting down..."
 msgstr ""

+ 3 - 0
src/CMakeLists.txt

@@ -668,7 +668,10 @@ endif(BUILD_SERVER)
 # see issue #4638
 set(GETTEXT_BLACKLISTED_LOCALES
 	ar
+	dv
 	he
+	hi
+	kn
 	ky
 	ms_Arab
 	th

+ 44 - 51
src/client/fontengine.cpp

@@ -56,7 +56,7 @@ FontEngine::FontEngine(gui::IGUIEnvironment* env) :
 
 	readSettings();
 
-	if (m_currentMode == FM_Standard) {
+	if (m_currentMode != FM_Simple) {
 		g_settings->registerChangedCallback("font_size", font_setting_changed, NULL);
 		g_settings->registerChangedCallback("font_bold", font_setting_changed, NULL);
 		g_settings->registerChangedCallback("font_italic", font_setting_changed, NULL);
@@ -66,12 +66,7 @@ FontEngine::FontEngine(gui::IGUIEnvironment* env) :
 		g_settings->registerChangedCallback("font_path_bolditalic", font_setting_changed, NULL);
 		g_settings->registerChangedCallback("font_shadow", font_setting_changed, NULL);
 		g_settings->registerChangedCallback("font_shadow_alpha", font_setting_changed, NULL);
-	}
-	else if (m_currentMode == FM_Fallback) {
-		g_settings->registerChangedCallback("fallback_font_size", font_setting_changed, NULL);
 		g_settings->registerChangedCallback("fallback_font_path", font_setting_changed, NULL);
-		g_settings->registerChangedCallback("fallback_font_shadow", font_setting_changed, NULL);
-		g_settings->registerChangedCallback("fallback_font_shadow_alpha", font_setting_changed, NULL);
 	}
 
 	g_settings->registerChangedCallback("mono_font_path", font_setting_changed, NULL);
@@ -101,6 +96,11 @@ void FontEngine::cleanCache()
 
 /******************************************************************************/
 irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec)
+{
+	return getFont(spec, false);
+}
+
+irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec, bool may_fail)
 {
 	if (spec.mode == FM_Unspecified) {
 		spec.mode = m_currentMode;
@@ -112,6 +112,10 @@ irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec)
 		// Support for those could be added, but who cares?
 		spec.bold = false;
 		spec.italic = false;
+	} else if (spec.mode == _FM_Fallback) {
+		// Fallback font doesn't support these either
+		spec.bold = false;
+		spec.italic = false;
 	}
 
 	// Fallback to default size
@@ -130,6 +134,13 @@ irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec)
 	else
 		font = initFont(spec);
 
+	if (!font && !may_fail) {
+		errorstream << "Minetest cannot continue without a valid font. "
+			"Please correct the 'font_path' setting or install the font "
+			"file in the proper location." << std::endl;
+		abort();
+	}
+
 	m_font_cache[spec.getHash()][spec.size] = font;
 
 	return font;
@@ -204,20 +215,9 @@ unsigned int FontEngine::getFontSize(FontMode mode)
 void FontEngine::readSettings()
 {
 	if (USE_FREETYPE && g_settings->getBool("freetype")) {
-		m_default_size[FM_Standard] = g_settings->getU16("font_size");
-		m_default_size[FM_Fallback] = g_settings->getU16("fallback_font_size");
-		m_default_size[FM_Mono]     = g_settings->getU16("mono_font_size");
-
-		/*~ DO NOT TRANSLATE THIS LITERALLY!
-		This is a special string. Put either "no" or "yes"
-		into the translation field (literally).
-		Choose "yes" if the language requires use of the fallback
-		font, "no" otherwise.
-		The fallback font is (normally) required for languages with
-		non-Latin script, like Chinese.
-		When in doubt, test your translation. */
-		m_currentMode = is_yes(gettext("needs_fallback_font")) ?
-				FM_Fallback : FM_Standard;
+		m_default_size[FM_Standard]  = g_settings->getU16("font_size");
+		m_default_size[_FM_Fallback] = g_settings->getU16("font_size");
+		m_default_size[FM_Mono]      = g_settings->getU16("mono_font_size");
 
 		m_default_bold = g_settings->getBool("font_bold");
 		m_default_italic = g_settings->getBool("font_italic");
@@ -271,18 +271,8 @@ gui::IGUIFont *FontEngine::initFont(const FontSpec &spec)
 	assert(spec.size != FONT_SIZE_UNSPECIFIED);
 
 	std::string setting_prefix = "";
-
-	switch (spec.mode) {
-		case FM_Fallback:
-			setting_prefix = "fallback_";
-			break;
-		case FM_Mono:
-		case FM_SimpleMono:
-			setting_prefix = "mono_";
-			break;
-		default:
-			break;
-	}
+	if (spec.mode == FM_Mono)
+		setting_prefix = "mono_";
 
 	std::string setting_suffix = "";
 	if (spec.bold)
@@ -305,38 +295,41 @@ gui::IGUIFont *FontEngine::initFont(const FontSpec &spec)
 	g_settings->getU16NoEx(setting_prefix + "font_shadow_alpha",
 			font_shadow_alpha);
 
-	std::string wanted_font_path;
-	wanted_font_path = g_settings->get(setting_prefix + "font_path" + setting_suffix);
+	std::string path_setting;
+	if (spec.mode == _FM_Fallback)
+		path_setting = "fallback_font_path";
+	else
+		path_setting = setting_prefix + "font_path" + setting_suffix;
 
 	std::string fallback_settings[] = {
-		wanted_font_path,
-		g_settings->get("fallback_font_path"),
-		Settings::getLayer(SL_DEFAULTS)->get(setting_prefix + "font_path")
+		g_settings->get(path_setting),
+		Settings::getLayer(SL_DEFAULTS)->get(path_setting)
 	};
 
 #if USE_FREETYPE
 	for (const std::string &font_path : fallback_settings) {
-		irr::gui::IGUIFont *font = gui::CGUITTFont::createTTFont(m_env,
+		gui::CGUITTFont *font = gui::CGUITTFont::createTTFont(m_env,
 				font_path.c_str(), size, true, true, font_shadow,
 				font_shadow_alpha);
 
-		if (font)
-			return font;
-
-		errorstream << "FontEngine: Cannot load '" << font_path <<
+		if (!font) {
+			errorstream << "FontEngine: Cannot load '" << font_path <<
 				"'. Trying to fall back to another path." << std::endl;
-	}
-
+			continue;
+		}
 
-	// give up
-	errorstream << "minetest can not continue without a valid font. "
-			"Please correct the 'font_path' setting or install the font "
-			"file in the proper location" << std::endl;
+		if (spec.mode != _FM_Fallback) {
+			FontSpec spec2(spec);
+			spec2.mode = _FM_Fallback;
+			font->setFallback(getFont(spec2, true));
+		}
+		return font;
+	}
 #else
-	errorstream << "FontEngine: Tried to load freetype fonts but Minetest was"
-			" not compiled with that library." << std::endl;
+	errorstream << "FontEngine: Tried to load TTF font but Minetest was"
+			" compiled without Freetype." << std::endl;
 #endif
-	abort();
+	return nullptr;
 }
 
 /** initialize a font without freetype */

+ 5 - 3
src/client/fontengine.h

@@ -33,7 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 enum FontMode : u8 {
 	FM_Standard = 0,
 	FM_Mono,
-	FM_Fallback,
+	_FM_Fallback, // do not use directly
 	FM_Simple,
 	FM_SimpleMono,
 	FM_MaxMode,
@@ -47,7 +47,7 @@ struct FontSpec {
 		bold(bold),
 		italic(italic) {}
 
-	u16 getHash()
+	u16 getHash() const
 	{
 		return (mode << 2) | (static_cast<u8>(bold) << 1) | static_cast<u8>(italic);
 	}
@@ -132,10 +132,12 @@ public:
 	void readSettings();
 
 private:
+	irr::gui::IGUIFont *getFont(FontSpec spec, bool may_fail);
+
 	/** update content of font cache in case of a setting change made it invalid */
 	void updateFontCache();
 
-	/** initialize a new font */
+	/** initialize a new TTF font */
 	gui::IGUIFont *initFont(const FontSpec &spec);
 
 	/** initialize a font without freetype */

+ 0 - 5
src/defaultsettings.cpp

@@ -304,12 +304,7 @@ void set_default_settings()
 	settings->setDefault("mono_font_path_bold_italic", porting::getDataPath("fonts" DIR_DELIM "Cousine-BoldItalic.ttf"));
 	settings->setDefault("fallback_font_path", porting::getDataPath("fonts" DIR_DELIM "DroidSansFallbackFull.ttf"));
 
-	settings->setDefault("fallback_font_shadow", "1");
-	settings->setDefault("fallback_font_shadow_alpha", "128");
-
 	std::string font_size_str = std::to_string(TTF_DEFAULT_FONT_SIZE);
-
-	settings->setDefault("fallback_font_size", font_size_str);
 #else
 	settings->setDefault("freetype", "false");
 	settings->setDefault("font_path", porting::getDataPath("fonts" DIR_DELIM "mono_dejavu_sans"));

+ 59 - 5
src/irrlicht_changes/CGUITTFont.cpp

@@ -275,7 +275,8 @@ CGUITTFont* CGUITTFont::create(IrrlichtDevice *device, const io::path& filename,
 //! Constructor.
 CGUITTFont::CGUITTFont(IGUIEnvironment *env)
 : use_monochrome(false), use_transparency(true), use_hinting(true), use_auto_hinting(true),
-batch_load_size(1), Device(0), Environment(env), Driver(0), GlobalKerningWidth(0), GlobalKerningHeight(0)
+batch_load_size(1), Device(0), Environment(env), Driver(0), GlobalKerningWidth(0), GlobalKerningHeight(0),
+shadow_offset(0), shadow_alpha(0), fallback(0)
 {
 	#ifdef _DEBUG
 	setDebugName("CGUITTFont");
@@ -640,7 +641,30 @@ void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& positio
 			if (current_color < colors.size())
 				applied_colors.push_back(colors[current_color]);
 		}
-		offset.X += getWidthFromCharacter(currentChar);
+		if (n > 0)
+		{
+			offset.X += getWidthFromCharacter(currentChar);
+		}
+		else if (fallback != 0)
+		{
+			// Let the fallback font draw it, this isn't super efficient but hopefully that doesn't matter
+			wchar_t l1[] = { (wchar_t) currentChar, 0 }, l2 = (wchar_t) previousChar;
+
+			if (visible)
+			{
+				// Apply kerning.
+				offset.X += fallback->getKerningWidth(l1, &l2);
+				offset.Y += fallback->getKerningHeight();
+
+				u32 current_color = iter.getPos();
+				fallback->draw(core::stringw(l1),
+					core::rect<s32>({offset.X-1, offset.Y-1}, position.LowerRightCorner), // ???
+					current_color < colors.size() ? colors[current_color] : video::SColor(255, 255, 255, 255),
+					false, false, clip);
+			}
+
+			offset.X += fallback->getDimension(l1).Width;
+		}
 
 		previousChar = currentChar;
 		++iter;
@@ -766,6 +790,12 @@ inline u32 CGUITTFont::getWidthFromCharacter(uchar32_t c) const
 		int w = Glyphs[n-1].advance.x / 64;
 		return w;
 	}
+	if (fallback != 0)
+	{
+		wchar_t s[] = { (wchar_t) c, 0 };
+		return fallback->getDimension(s).Width;
+	}
+
 	if (c >= 0x2000)
 		return (font_metrics.ascender / 64);
 	else return (font_metrics.ascender / 64) / 2;
@@ -789,6 +819,12 @@ inline u32 CGUITTFont::getHeightFromCharacter(uchar32_t c) const
 		s32 height = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y + Glyphs[n-1].source_rect.getHeight();
 		return height;
 	}
+	if (fallback != 0)
+	{
+		wchar_t s[] = { (wchar_t) c, 0 };
+		return fallback->getDimension(s).Height;
+	}
+
 	if (c >= 0x2000)
 		return (font_metrics.ascender / 64);
 	else return (font_metrics.ascender / 64) / 2;
@@ -804,9 +840,9 @@ u32 CGUITTFont::getGlyphIndexByChar(uchar32_t c) const
 	// Get the glyph.
 	u32 glyph = FT_Get_Char_Index(tt_face, c);
 
-	// Check for a valid glyph.  If it is invalid, attempt to use the replacement character.
+	// Check for a valid glyph.
 	if (glyph == 0)
-		glyph = FT_Get_Char_Index(tt_face, core::unicode::UTF_REPLACEMENT_CHARACTER);
+		return 0;
 
 	// If our glyph is already loaded, don't bother doing any batch loading code.
 	if (glyph != 0 && Glyphs[glyph - 1].isLoaded)
@@ -922,13 +958,26 @@ core::vector2di CGUITTFont::getKerning(const uchar32_t thisLetter, const uchar32
 
 	core::vector2di ret(GlobalKerningWidth, GlobalKerningHeight);
 
+	u32 n = getGlyphIndexByChar(thisLetter);
+
+	// If we don't have this glyph, ask fallback font
+	if (n == 0)
+	{
+		if (fallback != 0) {
+			wchar_t l1 = (wchar_t) thisLetter, l2 = (wchar_t) previousLetter;
+			ret.X = fallback->getKerningWidth(&l1, &l2);
+			ret.Y = fallback->getKerningHeight();
+		}
+		return ret;
+	}
+
 	// If we don't have kerning, no point in continuing.
 	if (!FT_HAS_KERNING(tt_face))
 		return ret;
 
 	// Get the kerning information.
 	FT_Vector v;
-	FT_Get_Kerning(tt_face, getGlyphIndexByChar(previousLetter), getGlyphIndexByChar(thisLetter), FT_KERNING_DEFAULT, &v);
+	FT_Get_Kerning(tt_face, getGlyphIndexByChar(previousLetter), n, FT_KERNING_DEFAULT, &v);
 
 	// If we have a scalable font, the return value will be in font points.
 	if (FT_IS_SCALABLE(tt_face))
@@ -960,6 +1009,9 @@ void CGUITTFont::setInvisibleCharacters(const core::ustring& s)
 video::IImage* CGUITTFont::createTextureFromChar(const uchar32_t& ch)
 {
 	u32 n = getGlyphIndexByChar(ch);
+	if (n == 0)
+		n = getGlyphIndexByChar((uchar32_t) core::unicode::UTF_REPLACEMENT_CHARACTER);
+
 	const SGUITTGlyph& glyph = Glyphs[n-1];
 	CGUITTGlyphPage* page = Glyph_Pages[glyph.glyph_page];
 
@@ -1147,6 +1199,8 @@ core::array<scene::ISceneNode*> CGUITTFont::addTextSceneNode(const wchar_t* text
 				container.push_back(current_node);
 			}
 			offset.X += getWidthFromCharacter(current_char);
+			// Note that fallback font handling is missing here (Minetest never uses this)
+
 			previous_char = current_char;
 			++text;
 		}

+ 6 - 1
src/irrlicht_changes/CGUITTFont.h

@@ -269,7 +269,7 @@ namespace gui
 				video::SColor color, bool hcenter=false, bool vcenter=false,
 				const core::rect<s32>* clip=0);
 
-			virtual void draw(const EnrichedString& text, const core::rect<s32>& position,
+			void draw(const EnrichedString& text, const core::rect<s32>& position,
 				video::SColor color, bool hcenter=false, bool vcenter=false,
 				const core::rect<s32>* clip=0);
 
@@ -313,6 +313,9 @@ namespace gui
 			//! Get the last glyph page's index.
 			u32 getLastGlyphPageIndex() const { return Glyph_Pages.size() - 1; }
 
+			//! Set font that should be used for glyphs not present in ours
+			void setFallback(gui::IGUIFont* font) { fallback = font; }
+
 			//! Create corresponding character's software image copy from the font,
 			//! so you can use this data just like any ordinary video::IImage.
 			//! \param ch The character you need
@@ -387,6 +390,8 @@ namespace gui
 			core::ustring Invisible;
 			u32 shadow_offset;
 			u32 shadow_alpha;
+
+			gui::IGUIFont* fallback;
 	};
 
 } // end namespace gui