changeset 10367:d4b8b071d3e2 draft

(svn r14618) -Feature: when the chosen language isn't supported by the current font, try to find a font that does and use that instead. Thanks to glx/michi_cc for the Windows implementation.
author rubidium <rubidium@openttd.org>
date Mon, 24 Nov 2008 18:53:17 +0000
parents dfebdb4bcdaa
children 30cfe471a056
files src/fontcache.cpp src/fontcache.h src/lang/afrikaans.txt src/lang/brazilian_portuguese.txt src/lang/bulgarian.txt src/lang/catalan.txt src/lang/croatian.txt src/lang/czech.txt src/lang/danish.txt src/lang/dutch.txt src/lang/english.txt src/lang/english_US.txt src/lang/esperanto.txt src/lang/estonian.txt src/lang/finnish.txt src/lang/french.txt src/lang/galician.txt src/lang/german.txt src/lang/hungarian.txt src/lang/icelandic.txt src/lang/italian.txt src/lang/japanese.txt src/lang/korean.txt src/lang/lithuanian.txt src/lang/norwegian_bokmal.txt src/lang/norwegian_nynorsk.txt src/lang/origveh.txt src/lang/piglatin.txt src/lang/polish.txt src/lang/portuguese.txt src/lang/romanian.txt src/lang/russian.txt src/lang/simplified_chinese.txt src/lang/slovak.txt src/lang/slovenian.txt src/lang/spanish.txt src/lang/swedish.txt src/lang/traditional_chinese.txt src/lang/turkish.txt src/lang/ukrainian.txt src/lang/unfinished/frisian.txt src/lang/unfinished/greek.txt src/lang/unfinished/ido.txt src/lang/unfinished/indonesian.txt src/lang/unfinished/latvian.txt src/lang/unfinished/macedonian.txt src/lang/unfinished/persian.txt src/lang/unfinished/serbian.txt src/lang/welsh.txt src/strgen/strgen.cpp src/strgen/strgen.h src/strings.cpp
diffstat 52 files changed, 342 insertions(+), 47 deletions(-) [+]
line wrap: on
line diff
--- a/src/fontcache.cpp
+++ b/src/fontcache.cpp
@@ -154,8 +154,68 @@
 	RegCloseKey(hKey);
 	return err;
 }
-#else
-# ifdef WITH_FONTCONFIG
+
+
+struct EFCParam {
+	FreeTypeSettings *settings;
+	LOCALESIGNATURE  locale;
+};
+
+static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXTMETRICEX *metric, DWORD type, LPARAM lParam)
+{
+	EFCParam *info = (EFCParam *)lParam;
+
+	/* Only use TrueType fonts */
+	if (!(type & TRUETYPE_FONTTYPE)) return 1;
+	/* Don't use SYMBOL fonts */
+	if (logfont->elfLogFont.lfCharSet == SYMBOL_CHARSET) return 1;
+
+	/* The font has to have at least one of the supported locales to be usable. */
+	if ((metric->ntmFontSig.fsCsb[0] & info->locale.lsCsbSupported[0]) == 0 && (metric->ntmFontSig.fsCsb[1] & info->locale.lsCsbSupported[1]) == 0) {
+		/* On win9x metric->ntmFontSig seems to contain garbage. */
+		FONTSIGNATURE fs;
+		memset(&fs, 0, sizeof(fs));
+		HFONT font = CreateFontIndirect(&logfont->elfLogFont);
+		if (font != NULL) {
+			HDC dc = GetDC(NULL);
+			HGDIOBJ oldfont = SelectObject(dc, font);
+			GetTextCharsetInfo(dc, &fs, 0);
+			SelectObject(dc, oldfont);
+			ReleaseDC(NULL, dc);
+			DeleteObject(font);
+		}
+		if ((fs.fsCsb[0] & info->locale.lsCsbSupported[0]) == 0 && (fs.fsCsb[1] & info->locale.lsCsbSupported[1]) == 0) return 1;
+	}
+
+	strecpy(info->settings->small_font,  font_name, lastof(info->settings->small_font));
+	strecpy(info->settings->medium_font, font_name, lastof(info->settings->medium_font));
+	strecpy(info->settings->large_font,  font_name, lastof(info->settings->large_font));
+	return 0; // stop enumerating
+}
+
+bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid)
+{
+	EFCParam langInfo;
+	if (GetLocaleInfo(MAKELCID(winlangid, SORT_DEFAULT), LOCALE_FONTSIGNATURE, (LPWSTR)&langInfo.locale, sizeof(langInfo.locale) / sizeof(TCHAR)) == 0) {
+		/* Invalid langid or some other mysterious error, can't determine fallback font. */
+		DEBUG(freetype, 1, "Can't get locale info for fallback font (langid=0x%x)", winlangid);
+		return false;
+	}
+	langInfo.settings = settings;
+
+	LOGFONT font;
+	/* Enumerate all fonts. */
+	font.lfCharSet = DEFAULT_CHARSET;
+	font.lfFaceName[0] = '\0';
+	font.lfPitchAndFamily = 0;
+
+	HDC dc = GetDC(NULL);
+	int ret = EnumFontFamiliesEx(dc, &font, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&langInfo, 0);
+	ReleaseDC(NULL, dc);
+	return ret == 0;
+}
+
+#elif defined(WITH_FONTCONFIG)
 static FT_Error GetFontByFaceName(const char *font_name, FT_Face *face)
 {
 	FT_Error err = FT_Err_Cannot_Open_Resource;
@@ -221,11 +281,75 @@
 
 	return err;
 }
-# else
+
+bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid)
+{
+	if (!FcInit()) return false;
+
+	bool ret = false;
+
+	/* Fontconfig doesn't handle full language isocodes, only the part
+	 * before the _ of e.g. en_GB is used, so "remove" everything after
+	 * the _. */
+	char lang[16];
+	strecpy(lang, language_isocode, lastof(lang));
+	char *split = strchr(lang, '_');
+	if (split != NULL) *split = '\0';
+
+	FcPattern *pat;
+	FcPattern *match;
+	FcResult result;
+	FcChar8 *file;
+	FcFontSet *fs;
+	FcValue val;
+	val.type = FcTypeString;
+	val.u.s = (FcChar8*)lang;
+
+	/* First create a pattern to match the wanted language */
+	pat = FcPatternCreate();
+	/* And fill it with the language and other defaults */
+	if (pat == NULL ||
+			!FcPatternAdd(pat, "lang", val, false) ||
+			!FcConfigSubstitute(0, pat, FcMatchPattern)) {
+		goto error_pattern;
+	}
+
+	FcDefaultSubstitute(pat);
+
+	/* The create a font set and match that */
+	match = FcFontMatch(0, pat, &result);
+
+	if (match == NULL) {
+		goto error_pattern;
+	}
+
+	/* Find all fonts that do match */
+	fs = FcFontSetCreate();
+	FcFontSetAdd(fs, match);
+
+	/* And take the first, if it exists */
+	if (fs->nfont <= 0 || FcPatternGetString(fs->fonts[0], FC_FILE, 0, &file)) {
+		goto error_fontset;
+	}
+
+	strecpy(settings->small_font,  (const char*)file, lastof(settings->small_font));
+	strecpy(settings->medium_font, (const char*)file, lastof(settings->medium_font));
+	strecpy(settings->large_font,  (const char*)file, lastof(settings->large_font));
+
+	ret = true;
+
+error_fontset:
+	FcFontSetDestroy(fs);
+error_pattern:
+	if (pat != NULL) FcPatternDestroy(pat);
+	FcFini();
+	return ret;
+}
+
+#else /* without WITH_FONTCONFIG */
 FT_Error GetFontByFaceName(const char *font_name, FT_Face *face) {return FT_Err_Cannot_Open_Resource;}
-# endif /* WITH_FONTCONFIG */
-
-#endif
+bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode) { return false; }
+#endif /* WITH_FONTCONFIG */
 
 /**
  * Loads the freetype font.
@@ -303,6 +427,35 @@
 	if (_face_large  != NULL) FT_Set_Pixel_Sizes(_face_large,  0, _freetype.large_size);
 }
 
+static void ResetGlyphCache();
+
+/**
+ * Unload a face and set it to NULL.
+ * @param face the face to unload
+ */
+static void UnloadFace(FT_Face *face)
+{
+	if (*face == NULL) return;
+
+	FT_Done_Face(*face);
+	*face = NULL;
+}
+
+/**
+ * Free everything allocated w.r.t. fonts.
+ */
+void UninitFreeType()
+{
+	ResetGlyphCache();
+
+	UnloadFace(&_face_small);
+	UnloadFace(&_face_medium);
+	UnloadFace(&_face_large);
+
+	FT_Done_FreeType(_library);
+	_library = NULL;
+}
+
 
 static FT_Face GetFontFace(FontSize size)
 {
@@ -335,6 +488,27 @@
  */
 static GlyphEntry **_glyph_ptr[FS_END];
 
+/** Clear the complete cache */
+static void ResetGlyphCache()
+{
+	for (int i = 0; i < FS_END; i++) {
+		if (_glyph_ptr[i] == NULL) continue;
+
+		for (int j = 0; j < 256; j++) {
+			if (_glyph_ptr[i][j] == NULL) continue;
+
+			for (int k = 0; k < 256; k++) {
+				if (_glyph_ptr[i][j][k].sprite == NULL) continue;
+				free(_glyph_ptr[i][j][k].sprite);
+			}
+
+			free(_glyph_ptr[i][j]);
+		}
+
+		free(_glyph_ptr[i]);
+		_glyph_ptr[i] = NULL;
+	}
+}
 
 static GlyphEntry *GetGlyphPtr(FontSize size, WChar key)
 {
--- a/src/fontcache.h
+++ b/src/fontcache.h
@@ -19,9 +19,9 @@
 #ifdef WITH_FREETYPE
 
 struct FreeTypeSettings {
-	char small_font[260];
-	char medium_font[260];
-	char large_font[260];
+	char small_font[MAX_PATH];
+	char medium_font[MAX_PATH];
+	char large_font[MAX_PATH];
 	uint small_size;
 	uint medium_size;
 	uint large_size;
@@ -33,13 +33,26 @@
 extern FreeTypeSettings _freetype;
 
 void InitFreeType();
+void UninitFreeType();
 const struct Sprite *GetGlyph(FontSize size, uint32 key);
 uint GetGlyphWidth(FontSize size, uint32 key);
 
+/**
+ * We would like to have a fallback font as the current one
+ * doesn't contain all characters we need.
+ * This function must set all fonts of settings.
+ * @param settings the settings to overwrite the fontname of.
+ * @param language_isocode the language, e.g. en_GB.
+ * @param winlangid the language ID windows style.
+ * @return true if a font has been set, false otherwise.
+ */
+bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid);
+
 #else
 
 /* Stub for initializiation */
 static inline void InitFreeType() {}
+static inline void UninitFreeType() {}
 
 /** Get the Sprite for a glyph */
 static inline const Sprite *GetGlyph(FontSize size, uint32 key)
--- a/src/lang/afrikaans.txt
+++ b/src/lang/afrikaans.txt
@@ -1,6 +1,7 @@
 ##name Afrikaans
 ##ownname Jaybee
 ##isocode af_ZA
+##winlangid 0x0436
 ##plural 0
 ##gender male
 
--- a/src/lang/brazilian_portuguese.txt
+++ b/src/lang/brazilian_portuguese.txt
@@ -1,6 +1,7 @@
 ##name Brazilian_Portuguese
 ##ownname Português (BR)
 ##isocode pt_BR
+##winlangid 0x0416
 ##plural 2
 ##gender m f
 
--- a/src/lang/bulgarian.txt
+++ b/src/lang/bulgarian.txt
@@ -1,6 +1,7 @@
 ##name Bulgarian
 ##ownname Български
 ##isocode bg_BG
+##winlangid 0x0402
 ##plural 0
 ##case m f n p
 ##gender m f n p
--- a/src/lang/catalan.txt
+++ b/src/lang/catalan.txt
@@ -1,6 +1,7 @@
 ##name Catalan
 ##ownname Català
 ##isocode ca_ES
+##winlangid 0x0403
 ##plural 0
 
 #
--- a/src/lang/croatian.txt
+++ b/src/lang/croatian.txt
@@ -1,6 +1,7 @@
 ##name Croatian
 ##ownname Hrvatski
 ##isocode hr_HR
+##winlangid 0x041a
 ##plural 6
 ##case nom gen dat aku vok lok ins
 ##gender male female middle
--- a/src/lang/czech.txt
+++ b/src/lang/czech.txt
@@ -1,6 +1,7 @@
 ##name Czech
 ##ownname Čeština
 ##isocode cs_CZ
+##winlangid 0x0405
 ##plural 6
 ##case nom gen dat acc voc loc ins big small
 ##gender m f n
--- a/src/lang/danish.txt
+++ b/src/lang/danish.txt
@@ -1,6 +1,7 @@
 ##name Danish
 ##ownname Dansk
 ##isocode da_DA
+##winlangid 0x0406
 ##plural 0
 
 #
--- a/src/lang/dutch.txt
+++ b/src/lang/dutch.txt
@@ -1,6 +1,7 @@
 ##name Dutch
 ##ownname Nederlands
 ##isocode nl_NL
+##winlangid 0x0413
 ##plural 0
 
 #
--- a/src/lang/english.txt
+++ b/src/lang/english.txt
@@ -1,6 +1,7 @@
 ##name English (UK)
 ##ownname English (UK)
 ##isocode en_GB
+##winlangid 0x0809
 ##plural 0
 
 #
--- a/src/lang/english_US.txt
+++ b/src/lang/english_US.txt
@@ -1,6 +1,7 @@
 ##name English (US)
 ##ownname English (US)
 ##isocode en_US
+##winlangid 0x0409
 ##plural 0
 
 #
--- a/src/lang/esperanto.txt
+++ b/src/lang/esperanto.txt
@@ -1,6 +1,7 @@
 ##name Esperanto
 ##ownname Esperanto
 ##isocode eo_EO
+##winlangid 0x0000
 ##plural 0
 ##case n
 
--- a/src/lang/estonian.txt
+++ b/src/lang/estonian.txt
@@ -1,6 +1,7 @@
 ##name Estonian
 ##ownname Eesti keel
 ##isocode et_ET
+##winlangid 0x0425
 ##plural 0
 ##case g in
 
--- a/src/lang/finnish.txt
+++ b/src/lang/finnish.txt
@@ -1,6 +1,7 @@
 ##name Finnish
 ##ownname Suomi
 ##isocode fi_FI
+##winlangid 0x040b
 ##plural 0
 
 #
--- a/src/lang/french.txt
+++ b/src/lang/french.txt
@@ -1,6 +1,7 @@
 ##name French
 ##ownname Français
 ##isocode fr_FR
+##winlangid 0x040c
 ##plural 2
 ##gender m m2 f
 
--- a/src/lang/galician.txt
+++ b/src/lang/galician.txt
@@ -1,6 +1,7 @@
 ##name Galician
 ##ownname Galego
 ##isocode gl_ES
+##winlangid 0x0456
 ##plural 0
 ##gender m f n
 
--- a/src/lang/german.txt
+++ b/src/lang/german.txt
@@ -1,6 +1,7 @@
 ##name German
 ##ownname Deutsch
 ##isocode de_DE
+##winlangid 0x0407
 ##plural 0
 ##gender m w n p
 
--- a/src/lang/hungarian.txt
+++ b/src/lang/hungarian.txt
@@ -1,6 +1,7 @@
 ##name Hungarian
 ##ownname Magyar
 ##isocode hu_HU
+##winlangid 0x040e
 ##plural 1
 ##case t ba
 
--- a/src/lang/icelandic.txt
+++ b/src/lang/icelandic.txt
@@ -1,6 +1,7 @@
 ##name Icelandic
 ##ownname Íslenska
 ##isocode is_IS
+##winlangid 0x040f
 ##plural 0
 ##gender karlkyn kvenkyn hvorugkyn
 
--- a/src/lang/italian.txt
+++ b/src/lang/italian.txt
@@ -1,6 +1,7 @@
 ##name Italian
 ##ownname Italiano
 ##isocode it_IT
+##winlangid 0x0410
 ##plural 0
 ##case ms mp fs fp
 ##gender m f
--- a/src/lang/japanese.txt
+++ b/src/lang/japanese.txt
@@ -1,6 +1,7 @@
 ##name Japanese
 ##ownname 日本語
 ##isocode ja_JP
+##winlangid 0x0411
 ##plural 1
 
 #
--- a/src/lang/korean.txt
+++ b/src/lang/korean.txt
@@ -1,6 +1,7 @@
 ##name Korean
 ##ownname 한국어
 ##isocode ko_KR
+##winlangid 0x0412
 ##plural 1
 
 #
--- a/src/lang/lithuanian.txt
+++ b/src/lang/lithuanian.txt
@@ -1,6 +1,7 @@
 ##name Lithuanian
 ##ownname Lietuvių
 ##isocode lt_LT
+##winlangid 0x0427
 ##plural 5
 ##case kas ko kam ka kuo kur kreip
 ##gender vyr mot
--- a/src/lang/norwegian_bokmal.txt
+++ b/src/lang/norwegian_bokmal.txt
@@ -1,6 +1,7 @@
 ##name Norwegian
 ##ownname Norsk (bokmål)
 ##isocode nb_NO
+##winlangid 0x0414
 ##plural 0
 
 #
--- a/src/lang/norwegian_nynorsk.txt
+++ b/src/lang/norwegian_nynorsk.txt
@@ -1,6 +1,7 @@
 ##name Norwegian new
 ##ownname Norsk, Nynorsk
 ##isocode nn_NO
+##winlangid 0x0814
 ##plural 0
 ##gender masculine feminine neuter
 
--- a/src/lang/origveh.txt
+++ b/src/lang/origveh.txt
@@ -1,6 +1,7 @@
 ##name Original vehicle names (ENG)
 ##ownname Original vehicle names (ENG)
-##isocode xx
+##isocode xx_OV
+##winlangid 0x0000
 
 ##id 0x8000
 STR_8000_KIRBY_PAUL_TANK_STEAM                                  :Collett Pannier Tank (Steam)
--- a/src/lang/piglatin.txt
+++ b/src/lang/piglatin.txt
@@ -1,6 +1,7 @@
 ##name Pig latin
 ##ownname Igpay atinlay
 ##isocode xx_PL
+##winlangid 0x0000
 ##plural 0
 
 #
--- a/src/lang/polish.txt
+++ b/src/lang/polish.txt
@@ -1,6 +1,7 @@
 ##name Polish
 ##ownname Polski
 ##isocode pl_PL
+##winlangid 0x0415
 ##plural 7
 ##case d c b n m w
 ##gender m f n
--- a/src/lang/portuguese.txt
+++ b/src/lang/portuguese.txt
@@ -1,6 +1,7 @@
 ##name Portuguese
 ##ownname Português
 ##isocode pt_PT
+##winlangid 0x0816
 ##plural 0
 
 #
--- a/src/lang/romanian.txt
+++ b/src/lang/romanian.txt
@@ -1,6 +1,7 @@
 ##name Romanian
 ##ownname Românã
 ##isocode ro_RO
+##winlangid 0x0418
 ##plural 0
 
 #
--- a/src/lang/russian.txt
+++ b/src/lang/russian.txt
@@ -1,6 +1,7 @@
 ##name Russian
 ##ownname Русский
 ##isocode ru_RU
+##winlangid 0x0419
 ##plural 6
 ##case m f n p
 ##gender m f n p
--- a/src/lang/simplified_chinese.txt
+++ b/src/lang/simplified_chinese.txt
@@ -1,6 +1,7 @@
 ##name Chinese (Simplified)
 ##ownname 简体中文
 ##isocode zh_CN
+##winlangid 0x0804
 ##plural 1
 
 #
--- a/src/lang/slovak.txt
+++ b/src/lang/slovak.txt
@@ -1,6 +1,7 @@
 ##name Slovak
 ##ownname Slovensky
 ##isocode sk_SK
+##winlangid 0x041b
 ##plural 6
 ##case g
 ##gender m z s
--- a/src/lang/slovenian.txt
+++ b/src/lang/slovenian.txt
@@ -1,6 +1,7 @@
 ##name Slovenian
 ##ownname Slovenščina
 ##isocode sl_SL
+##winlangid 0x0424
 ##plural 8
 ##case r d t
 
--- a/src/lang/spanish.txt
+++ b/src/lang/spanish.txt
@@ -1,6 +1,7 @@
 ##name Spanish
 ##ownname Español (ES)
 ##isocode es_ES
+##winlangid 0x0c0a
 ##plural 0
 ##gender masculino femenino
 
--- a/src/lang/swedish.txt
+++ b/src/lang/swedish.txt
@@ -1,6 +1,7 @@
 ##name Swedish
 ##ownname Svenska
 ##isocode sv_SE
+##winlangid 0x081d
 ##plural 0
 
 #
--- a/src/lang/traditional_chinese.txt
+++ b/src/lang/traditional_chinese.txt
@@ -1,6 +1,7 @@
 ##name Chinese (Traditional)
 ##ownname 中文
 ##isocode zh_TW
+##winlangid 0x0404
 ##plural 1
 
 #
--- a/src/lang/turkish.txt
+++ b/src/lang/turkish.txt
@@ -1,6 +1,7 @@
 ##name Turkish
 ##ownname Türkçe
 ##isocode tr_TR
+##winlangid 0x041f
 ##plural 1
 
 #
--- a/src/lang/ukrainian.txt
+++ b/src/lang/ukrainian.txt
@@ -1,6 +1,7 @@
 ##name Ukrainian
 ##ownname Українська
 ##isocode uk_UA
+##winlangid 0x0422
 ##plural 6
 ##gender m f s mn
 ##case r d z
--- a/src/lang/unfinished/frisian.txt
+++ b/src/lang/unfinished/frisian.txt
@@ -1,6 +1,7 @@
 ##name Frisian
 ##ownname Frysk
 ##isocode fy_NL
+##winlangid 0x0462
 
 ##id 0x0000
 STR_NULL                                                        :
--- a/src/lang/unfinished/greek.txt
+++ b/src/lang/unfinished/greek.txt
@@ -1,6 +1,7 @@
 ##name Greek
 ##ownname Ελληνικά
 ##isocode el_GR
+##winlangid 0x0408
 ##plural 0
 ##gender m f n
 
--- a/src/lang/unfinished/ido.txt
+++ b/src/lang/unfinished/ido.txt
@@ -1,6 +1,7 @@
 ##name Ido
 ##ownname Ido
 ##isocode io_XX
+##winlangid 0x0000
 ##plural 0
 
 #
--- a/src/lang/unfinished/indonesian.txt
+++ b/src/lang/unfinished/indonesian.txt
@@ -1,6 +1,7 @@
 ##name Indonesian
 ##ownname Indonesian
 ##isocode id_ID
+##winlangid 0x0421
 ##plural 0
 
 #
--- a/src/lang/unfinished/latvian.txt
+++ b/src/lang/unfinished/latvian.txt
@@ -1,6 +1,7 @@
 ##name Latvian
 ##ownname Latviešu
 ##isocode lv_LV
+##winlangid 0x0426
 ##plural 3
 ##case kas
 ##gender m f
--- a/src/lang/unfinished/macedonian.txt
+++ b/src/lang/unfinished/macedonian.txt
@@ -1,6 +1,7 @@
 ##name Macedonian
 ##ownname Македонски
 ##isocode mk_MK
+##winlangid 0x042f
 ##plural 0
 
 #
--- a/src/lang/unfinished/persian.txt
+++ b/src/lang/unfinished/persian.txt
@@ -1,6 +1,7 @@
 ##name Persian
 ##ownname Farsi
 ##isocode fa_IR
+##winlangid 0x0429
 ##plural 0
 ##textdir rtl
 
--- a/src/lang/unfinished/serbian.txt
+++ b/src/lang/unfinished/serbian.txt
@@ -1,6 +1,7 @@
 ##name Serbian
 ##ownname Srpski
 ##isocode sr_YU
+##winlangid 0x7c1a
 ##plural 0
 ##case ih a ova ca ci ka ća va ao u om im e ke on ona to
 ##gender muški ženski srednji
--- a/src/lang/welsh.txt
+++ b/src/lang/welsh.txt
@@ -1,6 +1,7 @@
 ##name Welsh
 ##ownname Cymraeg
 ##isocode cy_GB
+##winlangid 0x0452
 ##plural 0
 
 #
--- a/src/strgen/strgen.cpp
+++ b/src/strgen/strgen.cpp
@@ -87,6 +87,7 @@
 static char _lang_name[32], _lang_ownname[32], _lang_isocode[16];
 static byte _lang_pluralform;
 static byte _lang_textdir;
+static uint16 _lang_winlangid;
 #define MAX_NUM_GENDER 8
 static char _genders[MAX_NUM_GENDER][16];
 static int _numgenders;
@@ -649,6 +650,13 @@
 		} else {
 			error("Invalid textdir %s", str + 8);
 		}
+	} else if (!memcmp(str, "winlangid ", 10)) {
+		char *buf = str + 10;
+		long langid = strtol(buf, NULL, 16);
+		if (langid > UINT16_MAX || langid < 0) {
+			error("Invalid winlangid %s", buf);
+		}
+		_lang_winlangid = (uint16)langid;
 	} else if (!memcmp(str, "gender ", 7)) {
 		char* buf = str + 7;
 
@@ -912,6 +920,7 @@
 	_numgenders = 0;
 	_lang_name[0] = _lang_ownname[0] = _lang_isocode[0] = '\0';
 	_lang_textdir = TD_LTR;
+	_lang_winlangid = 0x0000; // neutral language code
 	// TODO:!! We can't reset the cases. In case the translated strings
 	// derive some strings from english....
 
@@ -1161,6 +1170,7 @@
 	hdr.version = TO_LE32(_hash);
 	hdr.plural_form = _lang_pluralform;
 	hdr.text_dir = _lang_textdir;
+	hdr.winlangid = TO_LE16(_lang_winlangid);
 	strcpy(hdr.name, _lang_name);
 	strcpy(hdr.own_name, _lang_ownname);
 	strcpy(hdr.isocode, _lang_isocode);
--- a/src/strgen/strgen.h
+++ b/src/strgen/strgen.h
@@ -14,7 +14,16 @@
 	uint16 offsets[32]; // the offsets
 	byte plural_form;   // plural form index
 	byte text_dir;      // default direction of the text
-	byte pad[2];        // pad header to be a multiple of 4
+	/**
+	 * Windows language ID:
+	 * Windows cannot and will not convert isocodes to something it can use to
+	 * determine whether a font can be used for the language or not. As a result
+	 * of that we need to pass the language id via strgen to OpenTTD to tell
+	 * what language it is in "Windows". The ID is the 'locale identifier' on:
+	 *   http://msdn.microsoft.com/en-us/library/ms776294.aspx
+	 */
+	uint16 winlangid;   // windows language id
+	/* byte pad[0];        // pad header to be a multiple of 4 */
 };
 
 assert_compile(sizeof(LanguagePackHeader) % 4 == 0);
--- a/src/strings.cpp
+++ b/src/strings.cpp
@@ -1358,9 +1358,13 @@
 	size_t read = fread(hdr, sizeof(*hdr), 1, f);
 	fclose(f);
 
-	return read == 1 &&
+	bool ret = read == 1 &&
 			hdr->ident == TO_LE32(LANGUAGE_PACK_IDENT) &&
 			hdr->version == TO_LE32(LANGUAGE_PACK_VERSION);
+
+	/* Convert endianness for the windows language ID */
+	if (ret) hdr->winlangid = FROM_LE16(hdr->winlangid);
+	return ret;
 }
 
 /**
@@ -1478,45 +1482,83 @@
  */
 void CheckForMissingGlyphsInLoadedLanguagePack()
 {
-	const Sprite *question_mark = GetGlyph(FS_NORMAL, '?');
+#ifdef WITH_FREETYPE
+	/* Reset to the original state; switching languages might cause us to
+	 * automatically choose another font. This resets that choice. */
+	UninitFreeType();
+	InitFreeType();
+#endif
+
+	bool retry = false;
+	for (;;) {
+		const Sprite *question_mark = GetGlyph(FS_NORMAL, '?');
+
+		for (uint i = 0; i != 32; i++) {
+			for (uint j = 0; j < _langtab_num[i]; j++) {
+				const char *string = _langpack_offs[_langtab_start[i] + j];
+				WChar c;
+				while ((c = Utf8Consume(&string)) != '\0') {
+					if (c == SCC_SETX) {
+						/*
+						  * SetX is, together with SetXY as special character that
+						 * uses the next (two) characters as data points. We have
+						 * to skip those, otherwise the UTF8 reading will go
+						 * haywire.
+						 */
+						string++;
+					} else if (c == SCC_SETXY) {
+						string += 2;
+					} else if (IsPrintable(c) && c != '?' && GetGlyph(FS_NORMAL, c) == question_mark) {
+#ifdef WITH_FREETYPE
+						if (!retry) {
+							/* We found an unprintable character... lets try whether we can
+							 * find a fallback font that can print the characters in the
+							 * current language. */
+							retry = true;
+
+							FreeTypeSettings backup;
+							memcpy(&backup, &_freetype, sizeof(backup));
 
-	for (uint i = 0; i != 32; i++) {
-		for (uint j = 0; j < _langtab_num[i]; j++) {
-			const char *string = _langpack_offs[_langtab_start[i] + j];
-			WChar c;
-			while ((c = Utf8Consume(&string)) != '\0') {
-				if (c == SCC_SETX) {
-					/*
-					 * SetX is, together with SetXY as special character that
-					 * uses the next (two) characters as data points. We have
-					 * to skip those, otherwise the UTF8 reading will go
-					 * haywire.
-					 */
-					string++;
-				} else if (c == SCC_SETXY) {
-					string += 2;
-				} else if (IsPrintable(c) && c != '?' && GetGlyph(FS_NORMAL, c) == question_mark) {
-					/*
-					 * The character is printable, but not in the normal font.
-					 * This is the case we were testing for. In this case we
-					 * have to show the error. As we do not want the string to
-					 * be translated by the translators, we 'force' it into the
-					 * binary and 'load' it via a BindCString. To do this
-					 * properly we have to set the color of the string,
-					 * otherwise we end up with a lot of artefacts. The color
-					 * 'character' might change in the future, so for safety
-					 * we just Utf8 Encode it into the string, which takes
-					 * exactly three characters, so it replaces the "XXX" with
-					 * the color marker.
-					 */
-					static char *err_str = strdup("XXXThe current font is missing some of the characters used in the texts for this language. Read the readme to see how to solve this.");
-					Utf8Encode(err_str, SCC_YELLOW);
-					SetDParamStr(0, err_str);
-					ShowErrorMessage(INVALID_STRING_ID, STR_JUST_RAW_STRING, 0, 0);
-					return;
+							bool success = SetFallbackFont(&_freetype, _langpack->isocode, _langpack->winlangid);
+							if (success) {
+								UninitFreeType();
+								InitFreeType();
+							}
+
+							memcpy(&_freetype, &backup, sizeof(backup));
+
+							if (success) continue;
+						} else {
+							/* Our fallback font does miss characters too, so keep the
+							 * user chosen font as that is more likely to be any good than
+							 * the wild guess we made */
+							UninitFreeType();
+							InitFreeType();
+						}
+#endif
+						/*
+						 * The character is printable, but not in the normal font.
+						 * This is the case we were testing for. In this case we
+						 * have to show the error. As we do not want the string to
+						 * be translated by the translators, we 'force' it into the
+						 * binary and 'load' it via a BindCString. To do this
+						 * properly we have to set the color of the string,
+						 * otherwise we end up with a lot of artefacts. The color
+						 * 'character' might change in the future, so for safety
+						 * we just Utf8 Encode it into the string, which takes
+						 * exactly three characters, so it replaces the "XXX" with
+						 * the color marker.
+						 */
+						static char *err_str = strdup("XXXThe current font is missing some of the characters used in the texts for this language. Read the readme to see how to solve this.");
+						Utf8Encode(err_str, SCC_YELLOW);
+						SetDParamStr(0, err_str);
+						ShowErrorMessage(INVALID_STRING_ID, STR_JUST_RAW_STRING, 0, 0);
+						return;
+					}
 				}
 			}
 		}
+		break;
 	}
 
 #if !defined(WITH_ICU)