/*
* This file is part of OpenTTD.
* 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.
* 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.
* 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 .
*/
/** @file fontdetection.cpp Detection of the right font. */
#if defined(WITH_FREETYPE) || defined(_WIN32)
#include "stdafx.h"
#include "debug.h"
#include "fontdetection.h"
#include "string_func.h"
#include "strings_func.h"
#ifdef WITH_FREETYPE
extern FT_Library _library;
#endif /* WITH_FREETYPE */
/**
* Get the font loaded into a Freetype face by using a font-name.
* If no appropriate font is found, the function returns an error
*/
#if defined(__APPLE__)
/* ========================================================================================
* OSX support
* ======================================================================================== */
#include "os/macosx/macos.h"
#include "safeguards.h"
FT_Error GetFontByFaceName(const char *font_name, FT_Face *face)
{
FT_Error err = FT_Err_Cannot_Open_Resource;
/* Get font reference from name. */
UInt8 file_path[PATH_MAX];
OSStatus os_err = -1;
CFAutoRelease name(CFStringCreateWithCString(kCFAllocatorDefault, font_name, kCFStringEncodingUTF8));
/* Simply creating the font using CTFontCreateWithNameAndSize will *always* return
* something, no matter the name. As such, we can't use it to check for existence.
* We instead query the list of all font descriptors that match the given name which
* does not do this stupid name fallback. */
CFAutoRelease name_desc(CTFontDescriptorCreateWithNameAndSize(name.get(), 0.0));
CFAutoRelease mandatory_attribs(CFSetCreate(kCFAllocatorDefault, const_cast(reinterpret_cast(&kCTFontNameAttribute)), 1, &kCFTypeSetCallBacks));
CFAutoRelease descs(CTFontDescriptorCreateMatchingFontDescriptors(name_desc.get(), mandatory_attribs.get()));
/* Loop over all matches until we can get a path for one of them. */
for (CFIndex i = 0; descs.get() != nullptr && i < CFArrayGetCount(descs.get()) && os_err != noErr; i++) {
CFAutoRelease font(CTFontCreateWithFontDescriptor((CTFontDescriptorRef)CFArrayGetValueAtIndex(descs.get(), i), 0.0, nullptr));
CFAutoRelease fontURL((CFURLRef)CTFontCopyAttribute(font.get(), kCTFontURLAttribute));
if (CFURLGetFileSystemRepresentation(fontURL.get(), true, file_path, lengthof(file_path))) os_err = noErr;
}
if (os_err == noErr) {
DEBUG(freetype, 3, "Font path for %s: %s", font_name, file_path);
err = FT_New_Face(_library, (const char *)file_path, 0, face);
}
return err;
}
bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid, MissingGlyphSearcher *callback)
{
/* Determine fallback font using CoreText. This uses the language isocode
* to find a suitable font. CoreText is available from 10.5 onwards. */
char lang[16];
if (strcmp(language_isocode, "zh_TW") == 0) {
/* Traditional Chinese */
strecpy(lang, "zh-Hant", lastof(lang));
} else if (strcmp(language_isocode, "zh_CN") == 0) {
/* Simplified Chinese */
strecpy(lang, "zh-Hans", lastof(lang));
} else {
/* Just copy the first part of the isocode. */
strecpy(lang, language_isocode, lastof(lang));
char *sep = strchr(lang, '_');
if (sep != nullptr) *sep = '\0';
}
/* Create a font descriptor matching the wanted language and latin (english) glyphs.
* Can't use CFAutoRelease here for everything due to the way the dictionary has to be created. */
CFStringRef lang_codes[2];
lang_codes[0] = CFStringCreateWithCString(kCFAllocatorDefault, lang, kCFStringEncodingUTF8);
lang_codes[1] = CFSTR("en");
CFArrayRef lang_arr = CFArrayCreate(kCFAllocatorDefault, (const void **)lang_codes, lengthof(lang_codes), &kCFTypeArrayCallBacks);
CFAutoRelease lang_attribs(CFDictionaryCreate(kCFAllocatorDefault, const_cast(reinterpret_cast(&kCTFontLanguagesAttribute)), (const void **)&lang_arr, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
CFAutoRelease lang_desc(CTFontDescriptorCreateWithAttributes(lang_attribs.get()));
CFRelease(lang_arr);
CFRelease(lang_codes[0]);
/* Get array of all font descriptors for the wanted language. */
CFAutoRelease mandatory_attribs(CFSetCreate(kCFAllocatorDefault, const_cast(reinterpret_cast(&kCTFontLanguagesAttribute)), 1, &kCFTypeSetCallBacks));
CFAutoRelease descs(CTFontDescriptorCreateMatchingFontDescriptors(lang_desc.get(), mandatory_attribs.get()));
bool result = false;
for (CFIndex i = 0; descs.get() != nullptr && i < CFArrayGetCount(descs.get()); i++) {
CTFontDescriptorRef font = (CTFontDescriptorRef)CFArrayGetValueAtIndex(descs.get(), i);
/* Get font traits. */
CFAutoRelease traits((CFDictionaryRef)CTFontDescriptorCopyAttribute(font, kCTFontTraitsAttribute));
CTFontSymbolicTraits symbolic_traits;
CFNumberGetValue((CFNumberRef)CFDictionaryGetValue(traits.get(), kCTFontSymbolicTrait), kCFNumberIntType, &symbolic_traits);
/* Skip symbol fonts and vertical fonts. */
if ((symbolic_traits & kCTFontClassMaskTrait) == (CTFontStylisticClass)kCTFontSymbolicClass || (symbolic_traits & kCTFontVerticalTrait)) continue;
/* Skip bold fonts (especially Arial Bold, which looks worse than regular Arial). */
if (symbolic_traits & kCTFontBoldTrait) continue;
/* Select monospaced fonts if asked for. */
if (((symbolic_traits & kCTFontMonoSpaceTrait) == kCTFontMonoSpaceTrait) != callback->Monospace()) continue;
/* Get font name. */
char name[128];
CFAutoRelease font_name((CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontDisplayNameAttribute));
CFStringGetCString(font_name.get(), name, lengthof(name), kCFStringEncodingUTF8);
/* There are some special fonts starting with an '.' and the last
* resort font that aren't usable. Skip them. */
if (name[0] == '.' || strncmp(name, "LastResort", 10) == 0) continue;
/* Save result. */
callback->SetFontNames(settings, name);
if (!callback->FindMissingGlyphs()) {
DEBUG(freetype, 2, "CT-Font for %s: %s", language_isocode, name);
result = true;
break;
}
}
if (!result) {
/* For some OS versions, the font 'Arial Unicode MS' does not report all languages it
* supports. If we didn't find any other font, just try it, maybe we get lucky. */
callback->SetFontNames(settings, "Arial Unicode MS");
result = !callback->FindMissingGlyphs();
}
callback->FindMissingGlyphs();
return result;
}
#elif defined(WITH_FONTCONFIG) /* end ifdef __APPLE__ */
#include
#include "safeguards.h"
/* ========================================================================================
* FontConfig (unix) support
* ======================================================================================== */
FT_Error GetFontByFaceName(const char *font_name, FT_Face *face)
{
FT_Error err = FT_Err_Cannot_Open_Resource;
if (!FcInit()) {
ShowInfoF("Unable to load font configuration");
} else {
FcPattern *match;
FcPattern *pat;
FcFontSet *fs;
FcResult result;
char *font_style;
char *font_family;
/* Split & strip the font's style */
font_family = stredup(font_name);
font_style = strchr(font_family, ',');
if (font_style != nullptr) {
font_style[0] = '\0';
font_style++;
while (*font_style == ' ' || *font_style == '\t') font_style++;
}
/* Resolve the name and populate the information structure */
pat = FcNameParse((FcChar8*)font_family);
if (font_style != nullptr) FcPatternAddString(pat, FC_STYLE, (FcChar8*)font_style);
FcConfigSubstitute(0, pat, FcMatchPattern);
FcDefaultSubstitute(pat);
fs = FcFontSetCreate();
match = FcFontMatch(0, pat, &result);
if (fs != nullptr && match != nullptr) {
int i;
FcChar8 *family;
FcChar8 *style;
FcChar8 *file;
FcFontSetAdd(fs, match);
for (i = 0; err != FT_Err_Ok && i < fs->nfont; i++) {
/* Try the new filename */
if (FcPatternGetString(fs->fonts[i], FC_FILE, 0, &file) == FcResultMatch &&
FcPatternGetString(fs->fonts[i], FC_FAMILY, 0, &family) == FcResultMatch &&
FcPatternGetString(fs->fonts[i], FC_STYLE, 0, &style) == FcResultMatch) {
/* The correct style? */
if (font_style != nullptr && strcasecmp(font_style, (char*)style) != 0) continue;
/* Font config takes the best shot, which, if the family name is spelled
* wrongly a 'random' font, so check whether the family name is the
* same as the supplied name */
if (strcasecmp(font_family, (char*)family) == 0) {
err = FT_New_Face(_library, (char *)file, 0, face);
}
}
}
}
free(font_family);
FcPatternDestroy(pat);
FcFontSetDestroy(fs);
FcFini();
}
return err;
}
bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid, MissingGlyphSearcher *callback)
{
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];
seprintf(lang, lastof(lang), ":lang=%s", language_isocode);
char *split = strchr(lang, '_');
if (split != nullptr) *split = '\0';
/* First create a pattern to match the wanted language. */
FcPattern *pat = FcNameParse((FcChar8*)lang);
/* We only want to know the filename. */
FcObjectSet *os = FcObjectSetBuild(FC_FILE, FC_SPACING, FC_SLANT, FC_WEIGHT, nullptr);
/* Get the list of filenames matching the wanted language. */
FcFontSet *fs = FcFontList(nullptr, pat, os);
/* We don't need these anymore. */
FcObjectSetDestroy(os);
FcPatternDestroy(pat);
if (fs != nullptr) {
int best_weight = -1;
const char *best_font = nullptr;
for (int i = 0; i < fs->nfont; i++) {
FcPattern *font = fs->fonts[i];
FcChar8 *file = nullptr;
FcResult res = FcPatternGetString(font, FC_FILE, 0, &file);
if (res != FcResultMatch || file == nullptr) {
continue;
}
/* Get a font with the right spacing .*/
int value = 0;
FcPatternGetInteger(font, FC_SPACING, 0, &value);
if (callback->Monospace() != (value == FC_MONO) && value != FC_DUAL) continue;
/* Do not use those that explicitly say they're slanted. */
FcPatternGetInteger(font, FC_SLANT, 0, &value);
if (value != 0) continue;
/* We want the fatter font as they look better at small sizes. */
FcPatternGetInteger(font, FC_WEIGHT, 0, &value);
if (value <= best_weight) continue;
callback->SetFontNames(settings, (const char*)file);
bool missing = callback->FindMissingGlyphs();
DEBUG(freetype, 1, "Font \"%s\" misses%s glyphs", file, missing ? "" : " no");
if (!missing) {
best_weight = value;
best_font = (const char *)file;
}
}
if (best_font != nullptr) {
ret = true;
callback->SetFontNames(settings, best_font);
InitFreeType(callback->Monospace());
}
/* Clean up the list of filenames. */
FcFontSetDestroy(fs);
}
FcFini();
return ret;
}
#endif /* end ifdef WITH_FONTCONFIG */
#if !defined(_WIN32) && !defined(__APPLE__) && !defined(WITH_FONTCONFIG)
#ifdef WITH_FREETYPE
FT_Error GetFontByFaceName(const char *font_name, FT_Face *face) {return FT_Err_Cannot_Open_Resource;}
#endif /* WITH_FREETYPE */
bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid, MissingGlyphSearcher *callback) { return false; }
#endif /* !defined(_WIN32) && !defined(__APPLE__) && !defined(WITH_FONTCONFIG) */
#endif /* WITH_FREETYPE */