(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.

pull/155/head
rubidium 16 years ago
parent 0f6583216b
commit dccd7886a8

@ -154,8 +154,68 @@ registry_no_font_found:
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 @@ static FT_Error GetFontByFaceName(const char *font_name, FT_Face *face)
return err;
}
# else
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, 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;}
bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode) { return false; }
#endif /* WITH_FONTCONFIG */
/**
* Loads the freetype font.
@ -303,6 +427,35 @@ void InitFreeType()
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 @@ struct GlyphEntry {
*/
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)
{

@ -19,9 +19,9 @@ void InitializeUnicodeGlyphMap();
#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 @@ struct FreeTypeSettings {
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)

@ -1,6 +1,7 @@
##name Afrikaans
##ownname Jaybee
##isocode af_ZA
##winlangid 0x0436
##plural 0
##gender male

@ -1,6 +1,7 @@
##name Brazilian_Portuguese
##ownname Português (BR)
##isocode pt_BR
##winlangid 0x0416
##plural 2
##gender m f

@ -1,6 +1,7 @@
##name Bulgarian
##ownname Български
##isocode bg_BG
##winlangid 0x0402
##plural 0
##case m f n p
##gender m f n p

@ -1,6 +1,7 @@
##name Catalan
##ownname Català
##isocode ca_ES
##winlangid 0x0403
##plural 0
#

@ -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

@ -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

@ -1,6 +1,7 @@
##name Danish
##ownname Dansk
##isocode da_DA
##winlangid 0x0406
##plural 0
#

@ -1,6 +1,7 @@
##name Dutch
##ownname Nederlands
##isocode nl_NL
##winlangid 0x0413
##plural 0
#

@ -1,6 +1,7 @@
##name English (UK)
##ownname English (UK)
##isocode en_GB
##winlangid 0x0809
##plural 0
#

@ -1,6 +1,7 @@
##name English (US)
##ownname English (US)
##isocode en_US
##winlangid 0x0409
##plural 0
#

@ -1,6 +1,7 @@
##name Esperanto
##ownname Esperanto
##isocode eo_EO
##winlangid 0x0000
##plural 0
##case n

@ -1,6 +1,7 @@
##name Estonian
##ownname Eesti keel
##isocode et_ET
##winlangid 0x0425
##plural 0
##case g in

@ -1,6 +1,7 @@
##name Finnish
##ownname Suomi
##isocode fi_FI
##winlangid 0x040b
##plural 0
#

@ -1,6 +1,7 @@
##name French
##ownname Français
##isocode fr_FR
##winlangid 0x040c
##plural 2
##gender m m2 f

@ -1,6 +1,7 @@
##name Galician
##ownname Galego
##isocode gl_ES
##winlangid 0x0456
##plural 0
##gender m f n

@ -1,6 +1,7 @@
##name German
##ownname Deutsch
##isocode de_DE
##winlangid 0x0407
##plural 0
##gender m w n p

@ -1,6 +1,7 @@
##name Hungarian
##ownname Magyar
##isocode hu_HU
##winlangid 0x040e
##plural 1
##case t ba

@ -1,6 +1,7 @@
##name Icelandic
##ownname Íslenska
##isocode is_IS
##winlangid 0x040f
##plural 0
##gender karlkyn kvenkyn hvorugkyn

@ -1,6 +1,7 @@
##name Italian
##ownname Italiano
##isocode it_IT
##winlangid 0x0410
##plural 0
##case ms mp fs fp
##gender m f

@ -1,6 +1,7 @@
##name Japanese
##ownname 日本語
##isocode ja_JP
##winlangid 0x0411
##plural 1
#

@ -1,6 +1,7 @@
##name Korean
##ownname 한국어
##isocode ko_KR
##winlangid 0x0412
##plural 1
#

@ -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

@ -1,6 +1,7 @@
##name Norwegian
##ownname Norsk (bokmål)
##isocode nb_NO
##winlangid 0x0414
##plural 0
#

@ -1,6 +1,7 @@
##name Norwegian new
##ownname Norsk, Nynorsk
##isocode nn_NO
##winlangid 0x0814
##plural 0
##gender masculine feminine neuter

@ -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)

@ -1,6 +1,7 @@
##name Pig latin
##ownname Igpay atinlay
##isocode xx_PL
##winlangid 0x0000
##plural 0
#

@ -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

@ -1,6 +1,7 @@
##name Portuguese
##ownname Português
##isocode pt_PT
##winlangid 0x0816
##plural 0
#

@ -1,6 +1,7 @@
##name Romanian
##ownname Românã
##isocode ro_RO
##winlangid 0x0418
##plural 0
#

@ -1,6 +1,7 @@
##name Russian
##ownname Русский
##isocode ru_RU
##winlangid 0x0419
##plural 6
##case m f n p
##gender m f n p

@ -1,6 +1,7 @@
##name Chinese (Simplified)
##ownname 简体中文
##isocode zh_CN
##winlangid 0x0804
##plural 1
#

@ -1,6 +1,7 @@
##name Slovak
##ownname Slovensky
##isocode sk_SK
##winlangid 0x041b
##plural 6
##case g
##gender m z s

@ -1,6 +1,7 @@
##name Slovenian
##ownname Slovenščina
##isocode sl_SL
##winlangid 0x0424
##plural 8
##case r d t

@ -1,6 +1,7 @@
##name Spanish
##ownname Español (ES)
##isocode es_ES
##winlangid 0x0c0a
##plural 0
##gender masculino femenino

@ -1,6 +1,7 @@
##name Swedish
##ownname Svenska
##isocode sv_SE
##winlangid 0x081d
##plural 0
#

@ -1,6 +1,7 @@
##name Chinese (Traditional)
##ownname 中文
##isocode zh_TW
##winlangid 0x0404
##plural 1
#

@ -1,6 +1,7 @@
##name Turkish
##ownname Türkçe
##isocode tr_TR
##winlangid 0x041f
##plural 1
#

@ -1,6 +1,7 @@
##name Ukrainian
##ownname Українська
##isocode uk_UA
##winlangid 0x0422
##plural 6
##gender m f s mn
##case r d z

@ -1,6 +1,7 @@
##name Frisian
##ownname Frysk
##isocode fy_NL
##winlangid 0x0462
##id 0x0000
STR_NULL :

@ -1,6 +1,7 @@
##name Greek
##ownname Ελληνικά
##isocode el_GR
##winlangid 0x0408
##plural 0
##gender m f n

@ -1,6 +1,7 @@
##name Ido
##ownname Ido
##isocode io_XX
##winlangid 0x0000
##plural 0
#

@ -1,6 +1,7 @@
##name Indonesian
##ownname Indonesian
##isocode id_ID
##winlangid 0x0421
##plural 0
#

@ -1,6 +1,7 @@
##name Latvian
##ownname Latviešu
##isocode lv_LV
##winlangid 0x0426
##plural 3
##case kas
##gender m f

@ -1,6 +1,7 @@
##name Macedonian
##ownname Македонски
##isocode mk_MK
##winlangid 0x042f
##plural 0
#

@ -1,6 +1,7 @@
##name Persian
##ownname Farsi
##isocode fa_IR
##winlangid 0x0429
##plural 0
##textdir rtl

@ -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

@ -1,6 +1,7 @@
##name Welsh
##ownname Cymraeg
##isocode cy_GB
##winlangid 0x0452
##plural 0
#

@ -87,6 +87,7 @@ static uint32 _hash;
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 @@ static void HandlePragma(char *str)
} 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 @@ static void ParseFile(const char *file, bool english)
_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 @@ static void WriteLangfile(const char *filename)
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);

@ -14,7 +14,16 @@ struct LanguagePackHeader {
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);

@ -1358,9 +1358,13 @@ static bool GetLanguageFileHeader(const char *file, LanguagePack *hdr)
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 InitializeLanguagePacks()
*/
void CheckForMissingGlyphsInLoadedLanguagePack()
{
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) {
/*
* 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;
#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));
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)

Loading…
Cancel
Save