From e0a0be4f7b6f5af7cc08a23acedb2744d98bdfe2 Mon Sep 17 00:00:00 2001
From: Christoph Oelckers <coelckers@users.noreply.github.com>
Date: Mon, 22 Apr 2019 09:08:43 +0200
Subject: [PATCH] - added a CanPrint function to FFont and used that to handle
 the statistics display on the automap HUD to only replace the font when
 actually needed, not based on the language.

---
 src/g_cvars.cpp                               | 15 +--
 src/g_statusbar/shared_sbar.cpp               | 12 ++-
 src/gamedata/fonts/font.cpp                   | 95 +++++++++++++++++--
 src/gamedata/fonts/v_font.cpp                 |  4 +-
 src/gamedata/fonts/v_font.h                   |  6 ++
 src/scripting/vmthunks.cpp                    | 13 +++
 wadsrc/static/zscript/base.zs                 |  1 +
 .../static/zscript/ui/statusbar/statusbar.zs  |  2 +-
 8 files changed, 119 insertions(+), 29 deletions(-)

diff --git a/src/g_cvars.cpp b/src/g_cvars.cpp
index 573fe04c91..ad3daa7694 100644
--- a/src/g_cvars.cpp
+++ b/src/g_cvars.cpp
@@ -140,20 +140,7 @@ bool CheckFontComplete(FFont *font)
 {
 	// Also check if the SmallFont contains all characters this language needs.
 	// If not, switch back to the original one.
-	const uint8_t* checkstr = (const uint8_t*)GStrings["REQUIRED_CHARACTERS"];
-	bool incomplete = false;
-
-	if (!checkstr) return true;
-	while (int c = GetCharFromString(checkstr))
-	{
-		bool redirected;
-		int cc = font->GetCharCode(c, true);
-		if (c != cc && (c != 0x1e9e || cc != 0xdf))
-		{
-			return false;
-		}
-	}
-	return true;
+	return font->CanPrint(GStrings["REQUIRED_CHARACTERS"]);
 }
 
 void UpdateGenericUI(bool cvar)
diff --git a/src/g_statusbar/shared_sbar.cpp b/src/g_statusbar/shared_sbar.cpp
index 30e8b064b4..1ec2be607b 100644
--- a/src/g_statusbar/shared_sbar.cpp
+++ b/src/g_statusbar/shared_sbar.cpp
@@ -597,6 +597,7 @@ void DBaseStatusBar::DoDrawAutomapHUD(int crdefault, int highlight)
 {
 	auto scale = GetUIScale(hud_scale);
 	auto font = generic_ui ? NewSmallFont : SmallFont;
+	auto font2 = font;
 	auto vwidth = screen->GetWidth() / scale;
 	auto vheight = screen->GetHeight() / scale;
 	auto fheight = font->GetHeight();
@@ -606,6 +607,11 @@ void DBaseStatusBar::DoDrawAutomapHUD(int crdefault, int highlight)
 	int textdist = 4;
 	int zerowidth = font->GetCharWidth('0');
 
+	if (!generic_ui)
+	{
+		if (!font->CanPrint(GStrings("AM_MONSTERS")) || !font->CanPrint(GStrings("AM_SECRETS")) || !font->CanPrint(GStrings("AM_ITEMS"))) font2 = OriginalSmallFont;
+	}
+
 	if (am_showtime)
 	{
 		sec = Tics2Seconds(primaryLevel->time);
@@ -629,14 +635,14 @@ void DBaseStatusBar::DoDrawAutomapHUD(int crdefault, int highlight)
 		if (am_showmonsters)
 		{
 			textbuffer.Format("%s\34%c %d/%d", GStrings("AM_MONSTERS"), crdefault + 65, primaryLevel->killed_monsters, primaryLevel->total_monsters);
-			screen->DrawText(font, highlight, textdist, y, textbuffer, DTA_KeepRatio, true, DTA_VirtualWidth, vwidth, DTA_VirtualHeight, vheight, TAG_DONE);
+			screen->DrawText(font2, highlight, textdist, y, textbuffer, DTA_KeepRatio, true, DTA_VirtualWidth, vwidth, DTA_VirtualHeight, vheight, TAG_DONE);
 			y += fheight;
 		}
 
 		if (am_showsecrets)
 		{
 			textbuffer.Format("%s\34%c %d/%d", GStrings("AM_SECRETS"), crdefault + 65, primaryLevel->found_secrets, primaryLevel->total_secrets);
-			screen->DrawText(font, highlight, textdist, y, textbuffer, DTA_KeepRatio, true, DTA_VirtualWidth, vwidth, DTA_VirtualHeight, vheight, TAG_DONE);
+			screen->DrawText(font2, highlight, textdist, y, textbuffer, DTA_KeepRatio, true, DTA_VirtualWidth, vwidth, DTA_VirtualHeight, vheight, TAG_DONE);
 			y += fheight;
 		}
 
@@ -644,7 +650,7 @@ void DBaseStatusBar::DoDrawAutomapHUD(int crdefault, int highlight)
 		if (am_showitems)
 		{
 			textbuffer.Format("%s\34%c %d/%d", GStrings("AM_ITEMS"), crdefault + 65, primaryLevel->found_items, primaryLevel->total_items);
-			screen->DrawText(font, highlight, textdist, y, textbuffer, DTA_KeepRatio, true, DTA_VirtualWidth, vwidth, DTA_VirtualHeight, vheight, TAG_DONE);
+			screen->DrawText(font2, highlight, textdist, y, textbuffer, DTA_KeepRatio, true, DTA_VirtualWidth, vwidth, DTA_VirtualHeight, vheight, TAG_DONE);
 			y += fheight;
 		}
 
diff --git a/src/gamedata/fonts/font.cpp b/src/gamedata/fonts/font.cpp
index 7d09f37cba..7a74daf754 100644
--- a/src/gamedata/fonts/font.cpp
+++ b/src/gamedata/fonts/font.cpp
@@ -482,6 +482,51 @@ FFont::~FFont ()
 	}
 }
 
+//==========================================================================
+//
+// FFont :: CheckCase
+//
+//==========================================================================
+
+void FFont::CheckCase()
+{
+	int lowercount = 0, uppercount = 0;
+	for (unsigned i = 0; i < Chars.Size(); i++)
+	{
+		unsigned chr = i + FirstChar;
+		if (lowerforupper[chr] == chr && upperforlower[chr] == chr)
+		{
+			continue;	// not a letter;
+		}
+		if (myislower(chr))
+		{
+			if (Chars[i].TranslatedPic != nullptr) lowercount++;
+		}
+		else
+		{
+			if (Chars[i].TranslatedPic != nullptr) uppercount++;
+		}
+	}
+	if (lowercount == 0) return;	// This is an uppercase-only font and we are done.
+
+	// The ß needs special treatment because it is far more likely to be supplied lowercase only, even in an uppercase font.
+	if (Chars[0xdf - FirstChar].TranslatedPic != nullptr)
+	{
+		if (LastChar < 0x1e9e)
+		{
+			Chars.Resize(0x1e9f - FirstChar);
+			LastChar = 0x1e9e;
+		}
+		if (Chars[0x1e9e - FirstChar].TranslatedPic == nullptr)
+		{
+			std::swap(Chars[0xdf - FirstChar], Chars[0x1e9e - FirstChar]);
+			lowercount--;
+			uppercount++;
+			if (lowercount == 0) return;
+		}
+	}
+}
+
 //==========================================================================
 //
 // FFont :: FindFont
@@ -834,15 +879,6 @@ int FFont::GetCharCode(int code, bool needpic) const
 		return code;
 	}
 	
-	// Special handling for the ß which may only exist as lowercase, so for this we need an additional upper -> lower check for all fonts aside from the generic substitution logic.
-	if (code == 0x1e9e)
-	{
-		if (LastChar <= 0xdf && (!needpic || Chars[0xdf - FirstChar].TranslatedPic != nullptr))
-		{
-			return 0xdf;
-		}
-	}
-
 	// Use different substitution logic based on the fonts content:
 	// In a font which has both upper and lower case, prefer unaccented small characters over capital ones.
 	// In a pure upper-case font, do not check for lower case replacements.
@@ -975,6 +1011,47 @@ double GetBottomAlignOffset(FFont *font, int c)
 	return offset;
 }
 
+//==========================================================================
+//
+// Checks if the font contains proper glyphs for all characters in the string
+//
+//==========================================================================
+
+bool FFont::CanPrint(const uint8_t *string) const
+{
+	while (*string)
+	{
+		auto chr = GetCharFromString(string);
+		if (!MixedCase) chr = upperforlower[chr];	// For uppercase-only fonts we shouldn't check lowercase characters.
+		if (chr == TEXTCOLOR_ESCAPE)
+		{
+			// We do not need to check for UTF-8 in here.
+			if (*string == '[')
+			{
+				while (*string != '\0' && *string != ']')
+				{
+					++string;
+				}
+			}
+			if (*string != '\0')
+			{
+				++string;
+			}
+			continue;
+		}
+		else if (chr != '\n')
+		{
+			int cc = GetCharCode(chr, true);
+			if (chr != cc)
+			{
+				return false;
+			}
+		}
+	}
+
+	return true;
+}
+
 //==========================================================================
 //
 // Find string width using this font
diff --git a/src/gamedata/fonts/v_font.cpp b/src/gamedata/fonts/v_font.cpp
index c416740488..8aef252409 100644
--- a/src/gamedata/fonts/v_font.cpp
+++ b/src/gamedata/fonts/v_font.cpp
@@ -849,7 +849,7 @@ int stripaccent(int code)
 		else if (code == 0x171) code = 0xfc;
 		else
 		{
-			static const char accentless[] = "AaAaAaCcCcCcCcDdDdEeEeEeEeEeGgGgGgGgHhHhIiIiIiIiIiIiJjKkkLlLlLlLlLlNnNnNnnNnOoOoOoOoRrRrRrSsSsSsSsTtTtTtUuUuUuUuUuUuWwYyYZzZzZz ";
+			static const char accentless[] = "AaAaAaCcCcCcCcDdDdEeEeEeEeEeGgGgGgGgHhHhIiIiIiIiIiIiJjKkkLlLlLlLlLlNnNnNnnNnOoOoOoOoRrRrRrSsSsSsSsTtTtTtUuUuUuUuUuUuWwYyYZzZzZzs";
 			return accentless[code - 0x100];
 		}
 	}
@@ -908,7 +908,7 @@ int stripaccent(int code)
 
 	}
 	
-	// skip the rest of Latin characters because none of them are relevant for modern languages.
+	// skip the rest of Latin characters because none of them are relevant for modern languages, except Vietnamese which cannot be represented with the tiny bitmap fonts anyway.
 	
 	return code;
 }
diff --git a/src/gamedata/fonts/v_font.h b/src/gamedata/fonts/v_font.h
index 5a799c1fd1..a4486cf378 100644
--- a/src/gamedata/fonts/v_font.h
+++ b/src/gamedata/fonts/v_font.h
@@ -113,6 +113,11 @@ public:
 	inline int StringWidth (const char *str) const { return StringWidth ((const uint8_t *)str); }
 	inline int StringWidth (const FString &str) const { return StringWidth ((const uint8_t *)str.GetChars()); }
 
+	// Checks if the font contains all characters to print this text.
+	bool CanPrint(const uint8_t *str) const;
+	inline bool CanPrint(const char *str) const { return CanPrint((const uint8_t *)str); }
+	inline bool CanPrint(const FString &str) const { return CanPrint((const uint8_t *)str.GetChars()); }
+
 	int GetCharCode(int code, bool needpic) const;
 	char GetCursor() const { return Cursor; }
 	void SetCursor(char c) { Cursor = c; }
@@ -120,6 +125,7 @@ public:
 	bool NoTranslate() const { return noTranslate; }
 	void RecordAllTextureColors(uint32_t *usedcolors);
 	virtual void SetDefaultTranslation(uint32_t *colors);
+	void CheckCase();
 
 protected:
 	FFont (int lump);
diff --git a/src/scripting/vmthunks.cpp b/src/scripting/vmthunks.cpp
index df5169b1a6..6046ff5851 100644
--- a/src/scripting/vmthunks.cpp
+++ b/src/scripting/vmthunks.cpp
@@ -2021,6 +2021,19 @@ DEFINE_ACTION_FUNCTION_NATIVE(FFont, StringWidth, StringWidth)
 	ACTION_RETURN_INT(StringWidth(self, str));
 }
 
+static int CanPrint(FFont *font, const FString &str)
+{
+	const char *txt = str[0] == '$' ? GStrings(&str[1]) : str.GetChars();
+	return font->CanPrint(txt);
+}
+
+DEFINE_ACTION_FUNCTION_NATIVE(FFont, CanPrint, CanPrint)
+{
+	PARAM_SELF_STRUCT_PROLOGUE(FFont);
+	PARAM_STRING(str);
+	ACTION_RETURN_INT(CanPrint(self, str));
+}
+
 static int FindFontColor(int name)
 {
 	return V_FindFontColor(ENamedName(name));
diff --git a/wadsrc/static/zscript/base.zs b/wadsrc/static/zscript/base.zs
index b52548d6de..4992b503a3 100644
--- a/wadsrc/static/zscript/base.zs
+++ b/wadsrc/static/zscript/base.zs
@@ -308,6 +308,7 @@ struct Font native
 
 	native int GetCharWidth(int code);
 	native int StringWidth(String code);
+	native bool CanPrint(String code);
 	native int GetHeight();
 	native String GetCursor();
 
diff --git a/wadsrc/static/zscript/ui/statusbar/statusbar.zs b/wadsrc/static/zscript/ui/statusbar/statusbar.zs
index 18f04af7f2..70415e8984 100644
--- a/wadsrc/static/zscript/ui/statusbar/statusbar.zs
+++ b/wadsrc/static/zscript/ui/statusbar/statusbar.zs
@@ -828,7 +828,7 @@ class BaseStatusBar native ui
 	// automap HUD common drawer
 	// This is not called directly to give a status bar the opportunity to
 	// change the text colors. If you want to do something different,
-	// override DrawAutomap directly.
+	// override DrawAutomapHUD directly.
 	//
 	//============================================================================