# fontdetection.cpp

Go to the documentation of this file.

    00001 /* $Id: fontdetection.cpp 25502 2013-06-28 19:44:28Z rubidium $ */
    00002
    00003 /*
    00004  * This file is part of OpenTTD.
    00005  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
    00006  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    00007  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
    00008  */
    00009
    00012 #ifdef WITH_FREETYPE
    00013
    00014 #include "stdafx.h"
    00015 #include "debug.h"
    00016 #include "fontdetection.h"
    00017 #include "string_func.h"
    00018 #include "strings_func.h"
    00019
    00020 extern FT_Library _library;
    00021
    00027 /* ========================================================================================
    00028  * Windows support
    00029  * ======================================================================================== */
    00030
    00031 #ifdef WIN32
    00032 #include "core/alloc_func.hpp"
    00033 #include "core/math_func.hpp"
    00034 #include <windows.h>
    00035 #include <shlobj.h> /* SHGetFolderPath */
    00036 #include "os/windows/win32.h"
    00037
    00048 char *GetShortPath(const char *long_path)
    00049 {
    00050   static char short_path[MAX_PATH];
    00051 #ifdef UNICODE
    00052   /* The non-unicode GetShortPath doesn't support UTF-8...,
    00053    * so convert the path to wide chars, then get the short
    00054    * path and convert it back again. */
    00055   wchar_t long_path_w[MAX_PATH];
    00056   MultiByteToWideChar(CP_UTF8, 0, long_path, -1, long_path_w, MAX_PATH);
    00057
    00058   wchar_t short_path_w[MAX_PATH];
    00059   GetShortPathNameW(long_path_w, short_path_w, MAX_PATH);
    00060
    00061   WideCharToMultiByte(CP_ACP, 0, short_path_w, -1, short_path, MAX_PATH, NULL, NULL);
    00062 #else
    00063   /* Technically not needed, but do it for consistency. */
    00064   GetShortPathNameA(long_path, short_path, MAX_PATH);
    00065 #endif
    00066   return short_path;
    00067 }
    00068
    00069 /* Get the font file to be loaded into Freetype by looping the registry
    00070  * location where windows lists all installed fonts. Not very nice, will
    00071  * surely break if the registry path changes, but it works. Much better
    00072  * solution would be to use CreateFont, and extract the font data from it
    00073  * by GetFontData. The problem with this is that the font file needs to be
    00074  * kept in memory then until the font is no longer needed. This could mean
    00075  * an additional memory usage of 30MB (just for fonts!) when using an eastern
    00076  * font for all font sizes */
    00077 #define FONT_DIR_NT "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"
    00078 #define FONT_DIR_9X "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Fonts"
    00079 FT_Error GetFontByFaceName(const char *font_name, FT_Face *face)
    00080 {
    00081   FT_Error err = FT_Err_Cannot_Open_Resource;
    00082   HKEY hKey;
    00083   LONG ret;
    00084   TCHAR vbuffer[MAX_PATH], dbuffer[256];
    00085   TCHAR *font_namep;
    00086   char *font_path;
    00087   uint index;
    00088
    00089   /* On windows NT (2000, NT3.5, XP, etc.) the fonts are stored in the
    00090    * "Windows NT" key, on Windows 9x in the Windows key. To save us having
    00091    * to retrieve the windows version, we'll just query both */
    00092   ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T(FONT_DIR_NT), 0, KEY_READ, &hKey);
    00093   if (ret != ERROR_SUCCESS) ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T(FONT_DIR_9X), 0, KEY_READ, &hKey);
    00094
    00095   if (ret != ERROR_SUCCESS) {
    00096     DEBUG(freetype, 0, "Cannot open registry key HKLM\\SOFTWARE\\Microsoft\\Windows (NT)\\CurrentVersion\\Fonts");
    00097     return err;
    00098   }
    00099
    00100   /* For Unicode we need some conversion between widechar and
    00101    * normal char to match the data returned by RegEnumValue,
    00102    * otherwise just use parameter */
    00103 #if defined(UNICODE)
    00104   font_namep = MallocT<TCHAR>(MAX_PATH);
    00105   MB_TO_WIDE_BUFFER(font_name, font_namep, MAX_PATH * sizeof(TCHAR));
    00106 #else
    00107   font_namep = const_cast<char *>(font_name); // only cast because in unicode pointer is not const
    00108 #endif
    00109
    00110   for (index = 0;; index++) {
    00111     TCHAR *s;
    00112     DWORD vbuflen = lengthof(vbuffer);
    00113     DWORD dbuflen = lengthof(dbuffer);
    00114
    00115     ret = RegEnumValue(hKey, index, vbuffer, &vbuflen, NULL, NULL, (byte*)dbuffer, &dbuflen);
    00116     if (ret != ERROR_SUCCESS) goto registry_no_font_found;
    00117
    00118     /* The font names in the registry are of the following 3 forms:
    00119      * - ADMUI3.fon
    00120      * - Book Antiqua Bold (TrueType)
    00121      * - Batang & BatangChe & Gungsuh & GungsuhChe (TrueType)
    00122      * We will strip the font-type '()' if any and work with the font name
    00123      * itself, which must match exactly; if...
    00124      * TTC files, font files which contain more than one font are separated
    00125      * by '&'. Our best bet will be to do substr match for the fontname
    00126      * and then let FreeType figure out which index to load */
    00127     s = _tcschr(vbuffer, _T('('));
    00128     if (s != NULL) s[-1] = '\0';
    00129
    00130     if (_tcschr(vbuffer, _T('&')) == NULL) {
    00131       if (_tcsicmp(vbuffer, font_namep) == 0) break;
    00132     } else {
    00133       if (_tcsstr(vbuffer, font_namep) != NULL) break;
    00134     }
    00135   }
    00136
    00137   if (!SUCCEEDED(OTTDSHGetFolderPath(NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, vbuffer))) {
    00138     DEBUG(freetype, 0, "SHGetFolderPath cannot return fonts directory");
    00139     goto folder_error;
    00140   }
    00141
    00142   /* Some fonts are contained in .ttc files, TrueType Collection fonts. These
    00143    * contain multiple fonts inside this single file. GetFontData however
    00144    * returns the whole file, so we need to check each font inside to get the
    00145    * proper font.
    00146    * Also note that FreeType does not support UNICODE filenames! */
    00147 #if defined(UNICODE)
    00148   /* We need a cast here back from wide because FreeType doesn't support
    00149    * widechar filenames. Just use the buffer we allocated before for the
    00150    * font_name search */
    00151   font_path = (char*)font_namep;
    00152   WIDE_TO_MB_BUFFER(vbuffer, font_path, MAX_PATH * sizeof(TCHAR));
    00153 #else
    00154   font_path = vbuffer;
    00155 #endif
    00156
    00157   ttd_strlcat(font_path, "\\", MAX_PATH * sizeof(TCHAR));
    00158   ttd_strlcat(font_path, WIDE_TO_MB(dbuffer), MAX_PATH * sizeof(TCHAR));
    00159
    00160   /* Convert the path into something that FreeType understands */
    00161   font_path = GetShortPath(font_path);
    00162
    00163   index = 0;
    00164   do {
    00165     err = FT_New_Face(_library, font_path, index, face);
    00166     if (err != FT_Err_Ok) break;
    00167
    00168     if (strncasecmp(font_name, (*face)->family_name, strlen((*face)->family_name)) == 0) break;
    00169     /* Try english name if font name failed */
    00170     if (strncasecmp(font_name + strlen(font_name) + 1, (*face)->family_name, strlen((*face)->family_name)) == 0) break;
    00171     err = FT_Err_Cannot_Open_Resource;
    00172
    00173   } while ((FT_Long)++index != (*face)->num_faces);
    00174
    00175
    00176 folder_error:
    00177 registry_no_font_found:
    00178 #if defined(UNICODE)
    00179   free(font_namep);
    00180 #endif
    00181   RegCloseKey(hKey);
    00182   return err;
    00183 }
    00184
    00198 static const char *GetEnglishFontName(const ENUMLOGFONTEX *logfont)
    00199 {
    00200   static char font_name[MAX_PATH];
    00201   const char *ret_font_name = NULL;
    00202   uint pos = 0;
    00203   HDC dc;
    00204   HGDIOBJ oldfont;
    00205   byte *buf;
    00206   DWORD dw;
    00207   uint16 format, count, stringOffset, platformId, encodingId, languageId, nameId, length, offset;
    00208
    00209   HFONT font = CreateFontIndirect(&logfont->elfLogFont);
    00210   if (font == NULL) goto err1;
    00211
    00212   dc = GetDC(NULL);
    00213   oldfont = SelectObject(dc, font);
    00214   dw = GetFontData(dc, 'eman', 0, NULL, 0);
    00215   if (dw == GDI_ERROR) goto err2;
    00216
    00217   buf = MallocT<byte>(dw);
    00218   dw = GetFontData(dc, 'eman', 0, buf, dw);
    00219   if (dw == GDI_ERROR) goto err3;
    00220
    00221   format = buf[pos++] << 8;
    00222   format += buf[pos++];
    00223   assert(format == 0);
    00224   count = buf[pos++] << 8;
    00225   count += buf[pos++];
    00226   stringOffset = buf[pos++] << 8;
    00227   stringOffset += buf[pos++];
    00228   for (uint i = 0; i < count; i++) {
    00229     platformId = buf[pos++] << 8;
    00230     platformId += buf[pos++];
    00231     encodingId = buf[pos++] << 8;
    00232     encodingId += buf[pos++];
    00233     languageId = buf[pos++] << 8;
    00234     languageId += buf[pos++];
    00235     nameId = buf[pos++] << 8;
    00236     nameId += buf[pos++];
    00237     if (nameId != 1) {
    00238       pos += 4; // skip length and offset
    00239       continue;
    00240     }
    00241     length = buf[pos++] << 8;
    00242     length += buf[pos++];
    00243     offset = buf[pos++] << 8;
    00244     offset += buf[pos++];
    00245
    00246     /* Don't buffer overflow */
    00247     length = min(length, MAX_PATH - 1);
    00248     for (uint j = 0; j < length; j++) font_name[j] = buf[stringOffset + offset + j];
    00249     font_name[length] = '\0';
    00250
    00251     if ((platformId == 1 && languageId == 0) ||      // Macintosh English
    00252         (platformId == 3 && languageId == 0x0409)) { // Microsoft English (US)
    00253       ret_font_name = font_name;
    00254       break;
    00255     }
    00256   }
    00257
    00258 err3:
    00259   free(buf);
    00260 err2:
    00261   SelectObject(dc, oldfont);
    00262   ReleaseDC(NULL, dc);
    00263   DeleteObject(font);
    00264 err1:
    00265   return ret_font_name == NULL ? WIDE_TO_MB((const TCHAR*)logfont->elfFullName) : ret_font_name;
    00266 }
    00267
    00268 class FontList {
    00269 protected:
    00270   TCHAR **fonts;
    00271   uint items;
    00272   uint capacity;
    00273
    00274 public:
    00275   FontList() : fonts(NULL), items(0), capacity(0) { };
    00276
    00277   ~FontList() {
    00278     if (this->fonts == NULL) return;
    00279
    00280     for (uint i = 0; i < this->items; i++) {
    00281       free(this->fonts[i]);
    00282     }
    00283
    00284     free(this->fonts);
    00285   }
    00286
    00287   bool Add(const TCHAR *font) {
    00288     for (uint i = 0; i < this->items; i++) {
    00289       if (_tcscmp(this->fonts[i], font) == 0) return false;
    00290     }
    00291
    00292     if (this->items == this->capacity) {
    00293       this->capacity += 10;
    00294       this->fonts = ReallocT(this->fonts, this->capacity);
    00295     }
    00296
    00297     this->fonts[this->items++] = _tcsdup(font);
    00298
    00299     return true;
    00300   }
    00301 };
    00302
    00303 struct EFCParam {
    00304   FreeTypeSettings *settings;
    00305   LOCALESIGNATURE  locale;
    00306   MissingGlyphSearcher *callback;
    00307   FontList fonts;
    00308 };
    00309
    00310 static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXTMETRICEX *metric, DWORD type, LPARAM lParam)
    00311 {
    00312   EFCParam *info = (EFCParam *)lParam;
    00313
    00314   /* Skip duplicates */
    00315   if (!info->fonts.Add((const TCHAR*)logfont->elfFullName)) return 1;
    00316   /* Only use TrueType fonts */
    00317   if (!(type & TRUETYPE_FONTTYPE)) return 1;
    00318   /* Don't use SYMBOL fonts */
    00319   if (logfont->elfLogFont.lfCharSet == SYMBOL_CHARSET) return 1;
    00320   /* Use monospaced fonts when asked for it. */
    00321   if (info->callback->Monospace() && (logfont->elfLogFont.lfPitchAndFamily & (FF_MODERN | FIXED_PITCH)) != (FF_MODERN | FIXED_PITCH)) return 1;
    00322
    00323   /* The font has to have at least one of the supported locales to be usable. */
    00324   if ((metric->ntmFontSig.fsCsb[0] & info->locale.lsCsbSupported[0]) == 0 && (metric->ntmFontSig.fsCsb[1] & info->locale.lsCsbSupported[1]) == 0) {
    00325     /* On win9x metric->ntmFontSig seems to contain garbage. */
    00326     FONTSIGNATURE fs;
    00327     memset(&fs, 0, sizeof(fs));
    00328     HFONT font = CreateFontIndirect(&logfont->elfLogFont);
    00329     if (font != NULL) {
    00330       HDC dc = GetDC(NULL);
    00331       HGDIOBJ oldfont = SelectObject(dc, font);
    00332       GetTextCharsetInfo(dc, &fs, 0);
    00333       SelectObject(dc, oldfont);
    00334       ReleaseDC(NULL, dc);
    00335       DeleteObject(font);
    00336     }
    00337     if ((fs.fsCsb[0] & info->locale.lsCsbSupported[0]) == 0 && (fs.fsCsb[1] & info->locale.lsCsbSupported[1]) == 0) return 1;
    00338   }
    00339
    00340   char font_name[MAX_PATH];
    00341 #if defined(UNICODE)
    00342   WIDE_TO_MB_BUFFER((const TCHAR*)logfont->elfFullName, font_name, lengthof(font_name));
    00343 #else
    00344   strecpy(font_name, (const TCHAR*)logfont->elfFullName, lastof(font_name));
    00345 #endif
    00346
    00347   /* Add english name after font name */
    00348   const char *english_name = GetEnglishFontName(logfont);
    00349   strecpy(font_name + strlen(font_name) + 1, english_name, lastof(font_name));
    00350
    00351   /* Check whether we can actually load the font. */
    00352   bool ft_init = _library != NULL;
    00353   bool found = false;
    00354   FT_Face face;
    00355   /* Init FreeType if needed. */
    00356   if ((ft_init || FT_Init_FreeType(&_library) == FT_Err_Ok) && GetFontByFaceName(font_name, &face) == FT_Err_Ok) {
    00357     FT_Done_Face(face);
    00358     found = true;
    00359   }
    00360   if (!ft_init) {
    00361     /* Uninit FreeType if we did the init. */
    00362     FT_Done_FreeType(_library);
    00363     _library = NULL;
    00364   }
    00365
    00366   if (!found) return 1;
    00367
    00368   info->callback->SetFontNames(info->settings, font_name);
    00369   if (info->callback->FindMissingGlyphs(NULL)) return 1;
    00370   DEBUG(freetype, 1, "Fallback font: %s (%s)", font_name, english_name);
    00371   return 0; // stop enumerating
    00372 }
    00373
    00374 bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid, MissingGlyphSearcher *callback)
    00375 {
    00376   DEBUG(freetype, 1, "Trying fallback fonts");
    00377   EFCParam langInfo;
    00378   if (GetLocaleInfo(MAKELCID(winlangid, SORT_DEFAULT), LOCALE_FONTSIGNATURE, (LPTSTR)&langInfo.locale, sizeof(langInfo.locale) / sizeof(TCHAR)) == 0) {
    00379     /* Invalid langid or some other mysterious error, can't determine fallback font. */
    00380     DEBUG(freetype, 1, "Can't get locale info for fallback font (langid=0x%x)", winlangid);
    00381     return false;
    00382   }
    00383   langInfo.settings = settings;
    00384   langInfo.callback = callback;
    00385
    00386   LOGFONT font;
    00387   /* Enumerate all fonts. */
    00388   font.lfCharSet = DEFAULT_CHARSET;
    00389   font.lfFaceName[0] = '\0';
    00390   font.lfPitchAndFamily = 0;
    00391
    00392   HDC dc = GetDC(NULL);
    00393   int ret = EnumFontFamiliesEx(dc, &font, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&langInfo, 0);
    00394   ReleaseDC(NULL, dc);
    00395   return ret == 0;
    00396 }
    00397
    00398 #elif defined(__APPLE__) /* end ifdef Win32 */
    00399 /* ========================================================================================
    00400  * OSX support
    00401  * ======================================================================================== */
    00402
    00403 #include "os/macosx/macos.h"
    00404
    00405 FT_Error GetFontByFaceName(const char *font_name, FT_Face *face)
    00406 {
    00407   FT_Error err = FT_Err_Cannot_Open_Resource;
    00408
    00409   /* Get font reference from name. */
    00410   CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, font_name, kCFStringEncodingUTF8);
    00411   ATSFontRef font = ATSFontFindFromName(name, kATSOptionFlagsDefault);
    00412   CFRelease(name);
    00413   if (font == kInvalidFont) return err;
    00414
    00415   /* Get a file system reference for the font. */
    00416   FSRef ref;
    00417   OSStatus os_err = -1;
    00418 #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
    00419   if (MacOSVersionIsAtLeast(10, 5, 0)) {
    00420     os_err = ATSFontGetFileReference(font, &ref);
    00421   } else
    00422 #endif
    00423   {
    00424 #if (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5) && !__LP64__
    00425     /* This type was introduced with the 10.5 SDK. */
    00426 #if (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5)
    00427   #define ATSFSSpec FSSpec
    00428 #endif
    00429     FSSpec spec;
    00430     os_err = ATSFontGetFileSpecification(font, (ATSFSSpec *)&spec);
    00431     if (os_err == noErr) os_err = FSpMakeFSRef(&spec, &ref);
    00432 #endif
    00433   }
    00434
    00435   if (os_err == noErr) {
    00436     /* Get unix path for file. */
    00437     UInt8 file_path[PATH_MAX];
    00438     if (FSRefMakePath(&ref, file_path, sizeof(file_path)) == noErr) {
    00439       DEBUG(freetype, 3, "Font path for %s: %s", font_name, file_path);
    00440       err = FT_New_Face(_library, (const char *)file_path, 0, face);
    00441     }
    00442   }
    00443
    00444   return err;
    00445 }
    00446
    00447 bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid, MissingGlyphSearcher *callback)
    00448 {
    00449   const char *str;
    00450   bool result = false;
    00451
    00452   callback->FindMissingGlyphs(&str);
    00453
    00454 #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
    00455   if (MacOSVersionIsAtLeast(10, 5, 0)) {
    00456     /* Determine fallback font using CoreText. This uses the language isocode
    00457      * to find a suitable font. CoreText is available from 10.5 onwards. */
    00458     char lang[16];
    00459     if (strcmp(language_isocode, "zh_TW") == 0) {
    00460       /* Traditional Chinese */
    00461       strecpy(lang, "zh-Hant", lastof(lang));
    00462     } else if (strcmp(language_isocode, "zh_CN") == 0) {
    00463       /* Simplified Chinese */
    00464       strecpy(lang, "zh-Hans", lastof(lang));
    00465     } else if (strncmp(language_isocode, "ur", 2) == 0) {
    00466       /* The urdu alphabet is variant of persian. As OS X has no default
    00467        * font that advertises an urdu language code, search for persian
    00468        * support instead. */
    00469       strecpy(lang, "fa", lastof(lang));
    00470     } else {
    00471       /* Just copy the first part of the isocode. */
    00472       strecpy(lang, language_isocode, lastof(lang));
    00473       char *sep = strchr(lang, '_');
    00474       if (sep != NULL) *sep = '\0';
    00475     }
    00476
    00477     CFStringRef lang_code;
    00478     lang_code = CFStringCreateWithCString(kCFAllocatorDefault, lang, kCFStringEncodingUTF8);
    00479
    00480     /* Create a font iterator and iterate over all fonts that
    00481      * are available to the application. */
    00482     ATSFontIterator itr;
    00483     ATSFontRef font;
    00484     ATSFontIteratorCreate(kATSFontContextLocal, NULL, NULL, kATSOptionFlagsUnRestrictedScope, &itr);
    00485     while (!result && ATSFontIteratorNext(itr, &font) == noErr) {
    00486       /* Get CoreText font handle. */
    00487       CTFontRef font_ref = CTFontCreateWithPlatformFont(font, 0.0, NULL, NULL);
    00488       CFArrayRef langs = CTFontCopySupportedLanguages(font_ref);
    00489       if (langs != NULL) {
    00490         /* Font has a list of supported languages. */
    00491         for (CFIndex i = 0; i < CFArrayGetCount(langs); i++) {
    00492           CFStringRef lang = (CFStringRef)CFArrayGetValueAtIndex(langs, i);
    00493           if (CFStringCompare(lang, lang_code, kCFCompareAnchored) == kCFCompareEqualTo) {
    00494             /* Lang code is supported by font, get full font name. */
    00495             CFStringRef font_name = CTFontCopyFullName(font_ref);
    00496             char name[128];
    00497             CFStringGetCString(font_name, name, lengthof(name), kCFStringEncodingUTF8);
    00498             CFRelease(font_name);
    00499             /* Skip some inappropriate or ugly looking fonts that have better alternatives. */
    00500             if (strncmp(name, "Courier", 7) == 0 || strncmp(name, "Apple Symbols", 13) == 0 ||
    00501                 strncmp(name, ".Aqua", 5) == 0 || strncmp(name, "LastResort", 10) == 0 ||
    00502                 strncmp(name, "GB18030 Bitmap", 14) == 0) continue;
    00503
    00504             /* Save result. */
    00505             callback->SetFontNames(settings, name);
    00506             DEBUG(freetype, 2, "CT-Font for %s: %s", language_isocode, name);
    00507             result = true;
    00508             break;
    00509           }
    00510         }
    00511         CFRelease(langs);
    00512       }
    00513       CFRelease(font_ref);
    00514     }
    00515     ATSFontIteratorRelease(&itr);
    00516     CFRelease(lang_code);
    00517   } else
    00518 #endif
    00519   {
    00520 #if (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5) && !__LP64__
    00521     /* Determine fallback font using ATSUI. This uses a string sample with
    00522      * missing characters. This is not failure-proof, but a better way like
    00523      * using the isocode as in the CoreText code path is not available.
    00524      * ATSUI was deprecated with 10.6 and is only partially available in
    00525      * 64-bit mode. */
    00526
    00527     /* Remove all control characters in the range from SCC_CONTROL_START to
    00528      * SCC_CONTROL_END as well as all ASCII < 0x20 from the string as it will
    00529      * mess with the automatic font detection */
    00530     char buff[256]; // This length is enough to find a suitable replacement font
    00531     strecpy(buff, str, lastof(buff));
    00532     str_validate(buff, lastof(buff), SVS_ALLOW_NEWLINE);
    00533
    00534     /* Extract a UniChar representation of the sample string. */
    00535     CFStringRef cf_str = CFStringCreateWithCString(kCFAllocatorDefault, buff, kCFStringEncodingUTF8);
    00536     if (cf_str == NULL) {
    00537       /* Something went wrong. Corrupt/invalid sample string? */
    00538       return false;
    00539     }
    00540     CFIndex str_len = CFStringGetLength(cf_str);
    00541     UniChar string[str_len];
    00542     CFStringGetCharacters(cf_str, CFRangeMake(0, str_len), string);
    00543
    00544     /* Create a default text style with the default font. */
    00545     ATSUStyle style;
    00546     ATSUCreateStyle(&style);
    00547
    00548     /* Create a text layout object from the sample string using the text style. */
    00549     UniCharCount run_len = kATSUToTextEnd;
    00550     ATSUTextLayout text_layout;
    00551     ATSUCreateTextLayoutWithTextPtr(string, kATSUFromTextBeginning, kATSUToTextEnd, str_len, 1, &run_len, &style, &text_layout);
    00552
    00553     /* Try to match a font for the sample text. ATSUMatchFontsToText stops after
    00554      * it finds the first continuous character run not renderable with the currently
    00555      * selected font starting at offset. The matching needs to be repeated until
    00556      * the end of the string is reached to make sure the fallback font matches for
    00557      * all characters in the string and not only the first run. */
    00558     UniCharArrayOffset offset = kATSUFromTextBeginning;
    00559     OSStatus os_err;
    00560     do {
    00561       ATSUFontID font;
    00562       UniCharCount run_len;
    00563       os_err = ATSUMatchFontsToText(text_layout, offset, kATSUToTextEnd, &font, &offset, &run_len);
    00564       if (os_err == kATSUFontsMatched) {
    00565         /* Found a better fallback font. Update the text layout
    00566          * object with the new font. */
    00567         ATSUAttributeTag tag = kATSUFontTag;
    00568         ByteCount size = sizeof(font);
    00569         ATSUAttributeValuePtr val = &font;
    00570         ATSUSetAttributes(style, 1, &tag, &size, &val);
    00571         offset += run_len;
    00572       }
    00573       /* Exit if the end of the string is reached or some other error occurred. */
    00574     } while (os_err == kATSUFontsMatched && offset < (UniCharArrayOffset)str_len);
    00575
    00576     if (os_err == noErr || os_err == kATSUFontsMatched) {
    00577       /* ATSUMatchFontsToText exited normally. Extract font
    00578        * out of the text layout object. */
    00579       ATSUFontID font;
    00580       ByteCount act_len;
    00581       ATSUGetAttribute(style, kATSUFontTag, sizeof(font), &font, &act_len);
    00582
    00583       /* Get unique font name. The result is not a c-string, we have
    00584        * to leave space for a \0 and terminate it ourselves. */
    00585       char name[128];
    00586       ATSUFindFontName(font, kFontUniqueName, kFontNoPlatformCode, kFontNoScriptCode, kFontNoLanguageCode, 127, name, &act_len, NULL);
    00587       name[act_len > 127 ? 127 : act_len] = '\0';
    00588
    00589       /* Save Result. */
    00590       callback->SetFontNames(settings, name);
    00591       DEBUG(freetype, 2, "ATSUI-Font for %s: %s", language_isocode, name);
    00592       result = true;
    00593     }
    00594
    00595     ATSUDisposeTextLayout(text_layout);
    00596     ATSUDisposeStyle(style);
    00597     CFRelease(cf_str);
    00598 #endif
    00599   }
    00600
    00601   if (result && strncmp(settings->medium.font, "Geeza Pro", 9) == 0) {
    00602     /* The font 'Geeza Pro' is often found for arabic characters, but
    00603      * it has the 'tiny' problem of not having any latin characters.
    00604      * 'Arial Unicode MS' on the other hand has arabic and latin glyphs,
    00605      * but seems to 'forget' to inform the OS about this fact. Manually
    00606      * substitute the latter for the former if it is loadable. */
    00607     bool ft_init = _library != NULL;
    00608     FT_Face face;
    00609     /* Init FreeType if needed. */
    00610     if ((ft_init || FT_Init_FreeType(&_library) == FT_Err_Ok) && GetFontByFaceName("Arial Unicode MS", &face) == FT_Err_Ok) {
    00611       FT_Done_Face(face);
    00612       callback->SetFontNames(settings, "Arial Unicode MS");
    00613       DEBUG(freetype, 1, "Replacing font 'Geeza Pro' with 'Arial Unicode MS'");
    00614     }
    00615     if (!ft_init) {
    00616       /* Uninit FreeType if we did the init. */
    00617       FT_Done_FreeType(_library);
    00618       _library = NULL;
    00619     }
    00620    }
    00621
    00622   callback->FindMissingGlyphs(NULL);
    00623   return result;
    00624 }
    00625
    00626 #elif defined(WITH_FONTCONFIG) /* end ifdef __APPLE__ */
    00627
    00628 #include <fontconfig/fontconfig.h>
    00629
    00630 /* ========================================================================================
    00631  * FontConfig (unix) support
    00632  * ======================================================================================== */
    00633 FT_Error GetFontByFaceName(const char *font_name, FT_Face *face)
    00634 {
    00635   FT_Error err = FT_Err_Cannot_Open_Resource;
    00636
    00637   if (!FcInit()) {
    00638     ShowInfoF("Unable to load font configuration");
    00639   } else {
    00640     FcPattern *match;
    00641     FcPattern *pat;
    00642     FcFontSet *fs;
    00643     FcResult  result;
    00644     char *font_style;
    00645     char *font_family;
    00646
    00647     /* Split & strip the font's style */
    00648     font_family = strdup(font_name);
    00649     font_style = strchr(font_family, ',');
    00650     if (font_style != NULL) {
    00651       font_style[0] = '\0';
    00652       font_style++;
    00653       while (*font_style == ' ' || *font_style == '\t') font_style++;
    00654     }
    00655
    00656     /* Resolve the name and populate the information structure */
    00657     pat = FcNameParse((FcChar8*)font_family);
    00658     if (font_style != NULL) FcPatternAddString(pat, FC_STYLE, (FcChar8*)font_style);
    00659     FcConfigSubstitute(0, pat, FcMatchPattern);
    00660     FcDefaultSubstitute(pat);
    00661     fs = FcFontSetCreate();
    00662     match = FcFontMatch(0, pat, &result);
    00663
    00664     if (fs != NULL && match != NULL) {
    00665       int i;
    00666       FcChar8 *family;
    00667       FcChar8 *style;
    00668       FcChar8 *file;
    00669       FcFontSetAdd(fs, match);
    00670
    00671       for (i = 0; err != FT_Err_Ok && i < fs->nfont; i++) {
    00672         /* Try the new filename */
    00673         if (FcPatternGetString(fs->fonts[i], FC_FILE,   0, &file)   == FcResultMatch &&
    00674             FcPatternGetString(fs->fonts[i], FC_FAMILY, 0, &family) == FcResultMatch &&
    00675             FcPatternGetString(fs->fonts[i], FC_STYLE,  0, &style)  == FcResultMatch) {
    00676
    00677           /* The correct style? */
    00678           if (font_style != NULL && strcasecmp(font_style, (char*)style) != 0) continue;
    00679
    00680           /* Font config takes the best shot, which, if the family name is spelled
    00681            * wrongly a 'random' font, so check whether the family name is the
    00682            * same as the supplied name */
    00683           if (strcasecmp(font_family, (char*)family) == 0) {
    00684             err = FT_New_Face(_library, (char *)file, 0, face);
    00685           }
    00686         }
    00687       }
    00688     }
    00689
    00690     free(font_family);
    00691     FcPatternDestroy(pat);
    00692     FcFontSetDestroy(fs);
    00693     FcFini();
    00694   }
    00695
    00696   return err;
    00697 }
    00698
    00699 bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid, MissingGlyphSearcher *callback)
    00700 {
    00701   if (!FcInit()) return false;
    00702
    00703   bool ret = false;
    00704
    00705   /* Fontconfig doesn't handle full language isocodes, only the part
    00706    * before the _ of e.g. en_GB is used, so "remove" everything after
    00707    * the _. */
    00708   char lang[16];
    00709   seprintf(lang, lastof(lang), ":lang=%s", language_isocode);
    00710   char *split = strchr(lang, '_');
    00711   if (split != NULL) *split = '\0';
    00712
    00713   /* First create a pattern to match the wanted language. */
    00714   FcPattern *pat = FcNameParse((FcChar8*)lang);
    00715   /* We only want to know the filename. */
    00716   FcObjectSet *os = FcObjectSetBuild(FC_FILE, FC_SPACING, FC_SLANT, FC_WEIGHT, NULL);
    00717   /* Get the list of filenames matching the wanted language. */
    00718   FcFontSet *fs = FcFontList(NULL, pat, os);
    00719
    00720   /* We don't need these anymore. */
    00721   FcObjectSetDestroy(os);
    00722   FcPatternDestroy(pat);
    00723
    00724   if (fs != NULL) {
    00725     int best_weight = -1;
    00726     const char *best_font = NULL;
    00727
    00728     for (int i = 0; i < fs->nfont; i++) {
    00729       FcPattern *font = fs->fonts[i];
    00730
    00731       FcChar8 *file = NULL;
    00732       FcResult res = FcPatternGetString(font, FC_FILE, 0, &file);
    00733       if (res != FcResultMatch || file == NULL) {
    00734         continue;
    00735       }
    00736
    00737       /* Get a font with the right spacing .*/
    00738       int value = 0;
    00739       FcPatternGetInteger(font, FC_SPACING, 0, &value);
    00740       if (callback->Monospace() != (value == FC_MONO) && value != FC_DUAL) continue;
    00741
    00742       /* Do not use those that explicitly say they're slanted. */
    00743       FcPatternGetInteger(font, FC_SLANT, 0, &value);
    00744       if (value != 0) continue;
    00745
    00746       /* We want the fatter font as they look better at small sizes. */
    00747       FcPatternGetInteger(font, FC_WEIGHT, 0, &value);
    00748       if (value <= best_weight) continue;
    00749
    00750       callback->SetFontNames(settings, (const char*)file);
    00751
    00752       bool missing = callback->FindMissingGlyphs(NULL);
    00753       DEBUG(freetype, 1, "Font \"%s\" misses%s glyphs", file, missing ? "" : " no");
    00754
    00755       if (!missing) {
    00756         best_weight = value;
    00757         best_font   = (const char *)file;
    00758       }
    00759     }
    00760
    00761     if (best_font != NULL) {
    00762       ret = true;
    00763       callback->SetFontNames(settings, best_font);
    00764       InitFreeType(callback->Monospace());
    00765     }
    00766
    00767     /* Clean up the list of filenames. */
    00768     FcFontSetDestroy(fs);
    00769   }
    00770
    00771   FcFini();
    00772   return ret;
    00773 }
    00774
    00775 #else /* without WITH_FONTCONFIG */
    00776 FT_Error GetFontByFaceName(const char *font_name, FT_Face *face) {return FT_Err_Cannot_Open_Resource;}
    00777 bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid, MissingGlyphSearcher *callback) { return false; }
    00778 #endif /* WITH_FONTCONFIG */
    00779
    00780 #endif /* WITH_FREETYPE */