/* * 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 font_win32.cpp Functions related to font handling on Win32. */ #include "../../stdafx.h" #include "../../debug.h" #include "../../blitter/factory.hpp" #include "../../core/alloc_func.hpp" #include "../../core/math_func.hpp" #include "../../core/mem_func.hpp" #include "../../error_func.h" #include "../../fileio_func.h" #include "../../fontdetection.h" #include "../../fontcache.h" #include "../../fontcache/truetypefontcache.h" #include "../../string_func.h" #include "../../strings_func.h" #include "../../zoom_func.h" #include "font_win32.h" #include "../../table/control_codes.h" #include #include /* SHGetFolderPath */ #include "os/windows/win32.h" #undef small // Say what, Windows? #include "safeguards.h" #ifdef WITH_FREETYPE #include #include FT_FREETYPE_H extern FT_Library _library; /** * Get the short DOS 8.3 format for paths. * FreeType doesn't support Unicode filenames and Windows' fopen (as used * by FreeType) doesn't support UTF-8 filenames. So we have to convert the * filename into something that isn't UTF-8 but represents the Unicode file * name. This is the short DOS 8.3 format. This does not contain any * characters that fopen doesn't support. * @param long_path the path in system encoding. * @return the short path in ANSI (ASCII). */ static const char *GetShortPath(const wchar_t *long_path) { static char short_path[MAX_PATH]; wchar_t short_path_w[MAX_PATH]; GetShortPathName(long_path, short_path_w, lengthof(short_path_w)); WideCharToMultiByte(CP_ACP, 0, short_path_w, -1, short_path, lengthof(short_path), nullptr, nullptr); return short_path; } /* Get the font file to be loaded into Freetype by looping the registry * location where windows lists all installed fonts. Not very nice, will * surely break if the registry path changes, but it works. Much better * solution would be to use CreateFont, and extract the font data from it * by GetFontData. The problem with this is that the font file needs to be * kept in memory then until the font is no longer needed. This could mean * an additional memory usage of 30MB (just for fonts!) when using an eastern * font for all font sizes */ static const wchar_t *FONT_DIR_NT = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"; FT_Error GetFontByFaceName(const char *font_name, FT_Face *face) { FT_Error err = FT_Err_Cannot_Open_Resource; HKEY hKey; LONG ret; wchar_t vbuffer[MAX_PATH], dbuffer[256]; const char *font_path; uint index; size_t path_len; ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, FONT_DIR_NT, 0, KEY_READ, &hKey); if (ret != ERROR_SUCCESS) { Debug(fontcache, 0, "Cannot open registry key HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"); return err; } /* Convert font name to file system encoding. */ wchar_t *font_namep = wcsdup(OTTD2FS(font_name).c_str()); for (index = 0;; index++) { wchar_t *s; DWORD vbuflen = lengthof(vbuffer); DWORD dbuflen = lengthof(dbuffer); ret = RegEnumValue(hKey, index, vbuffer, &vbuflen, nullptr, nullptr, (byte *)dbuffer, &dbuflen); if (ret != ERROR_SUCCESS) goto registry_no_font_found; /* The font names in the registry are of the following 3 forms: * - ADMUI3.fon * - Book Antiqua Bold (TrueType) * - Batang & BatangChe & Gungsuh & GungsuhChe (TrueType) * We will strip the font-type '()' if any and work with the font name * itself, which must match exactly; if... * TTC files, font files which contain more than one font are separated * by '&'. Our best bet will be to do substr match for the fontname * and then let FreeType figure out which index to load */ s = wcschr(vbuffer, L'('); if (s != nullptr) s[-1] = '\0'; if (wcschr(vbuffer, L'&') == nullptr) { if (wcsicmp(vbuffer, font_namep) == 0) break; } else { if (wcsstr(vbuffer, font_namep) != nullptr) break; } } if (!SUCCEEDED(SHGetFolderPath(nullptr, CSIDL_FONTS, nullptr, SHGFP_TYPE_CURRENT, vbuffer))) { Debug(fontcache, 0, "SHGetFolderPath cannot return fonts directory"); goto folder_error; } /* Some fonts are contained in .ttc files, TrueType Collection fonts. These * contain multiple fonts inside this single file. GetFontData however * returns the whole file, so we need to check each font inside to get the * proper font. */ std::wstring pathbuf(vbuffer); pathbuf += L"\\"; pathbuf += dbuffer; /* Convert the path into something that FreeType understands. */ font_path = GetShortPath(pathbuf.c_str()); index = 0; do { err = FT_New_Face(_library, font_path, index, face); if (err != FT_Err_Ok) break; if (StrStartsWithIgnoreCase(font_name, (*face)->family_name)) break; /* Try english name if font name failed */ if (StrStartsWithIgnoreCase(font_name + strlen(font_name) + 1, (*face)->family_name)) break; err = FT_Err_Cannot_Open_Resource; } while ((FT_Long)++index != (*face)->num_faces); folder_error: registry_no_font_found: free(font_namep); RegCloseKey(hKey); return err; } /** * Fonts can have localised names and when the system locale is the same as * one of those localised names Windows will always return that localised name * instead of allowing to get the non-localised (English US) name of the font. * This will later on give problems as freetype uses the non-localised name of * the font and we need to compare based on that name. * Windows furthermore DOES NOT have an API to get the non-localised name nor * can we override the system locale. This means that we have to actually read * the font itself to gather the font name we want. * Based on: http://blogs.msdn.com/michkap/archive/2006/02/13/530814.aspx * @param logfont the font information to get the english name of. * @return the English name (if it could be found). */ static std::string GetEnglishFontName(const ENUMLOGFONTEX *logfont) { static char font_name[MAX_PATH]; const char *ret_font_name = nullptr; uint pos = 0; HDC dc; HGDIOBJ oldfont; byte *buf; DWORD dw; uint16 format, count, stringOffset, platformId, encodingId, languageId, nameId, length, offset; HFONT font = CreateFontIndirect(&logfont->elfLogFont); if (font == nullptr) goto err1; dc = GetDC(nullptr); oldfont = SelectObject(dc, font); dw = GetFontData(dc, 'eman', 0, nullptr, 0); if (dw == GDI_ERROR) goto err2; buf = MallocT(dw); dw = GetFontData(dc, 'eman', 0, buf, dw); if (dw == GDI_ERROR) goto err3; format = buf[pos++] << 8; format += buf[pos++]; assert(format == 0); count = buf[pos++] << 8; count += buf[pos++]; stringOffset = buf[pos++] << 8; stringOffset += buf[pos++]; for (uint i = 0; i < count; i++) { platformId = buf[pos++] << 8; platformId += buf[pos++]; encodingId = buf[pos++] << 8; encodingId += buf[pos++]; languageId = buf[pos++] << 8; languageId += buf[pos++]; nameId = buf[pos++] << 8; nameId += buf[pos++]; if (nameId != 1) { pos += 4; // skip length and offset continue; } length = buf[pos++] << 8; length += buf[pos++]; offset = buf[pos++] << 8; offset += buf[pos++]; /* Don't buffer overflow */ length = std::min(length, MAX_PATH - 1); for (uint j = 0; j < length; j++) font_name[j] = buf[stringOffset + offset + j]; font_name[length] = '\0'; if ((platformId == 1 && languageId == 0) || // Macintosh English (platformId == 3 && languageId == 0x0409)) { // Microsoft English (US) ret_font_name = font_name; break; } } err3: free(buf); err2: SelectObject(dc, oldfont); ReleaseDC(nullptr, dc); DeleteObject(font); err1: return ret_font_name == nullptr ? FS2OTTD((const wchar_t *)logfont->elfFullName) : std::string(ret_font_name); } #endif /* WITH_FREETYPE */ struct EFCParam { FontCacheSettings *settings; LOCALESIGNATURE locale; MissingGlyphSearcher *callback; std::vector fonts; bool Add(const std::wstring_view &font) { for (const auto &entry : this->fonts) { if (font.compare(entry) == 0) return false; } this->fonts.emplace_back(font); return true; } }; static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXTMETRICEX *metric, DWORD type, LPARAM lParam) { EFCParam *info = (EFCParam *)lParam; /* Skip duplicates */ if (!info->Add(logfont->elfFullName)) return 1; /* Only use TrueType fonts */ if (!(type & TRUETYPE_FONTTYPE)) return 1; /* Don't use SYMBOL fonts */ if (logfont->elfLogFont.lfCharSet == SYMBOL_CHARSET) return 1; /* Use monospaced fonts when asked for it. */ if (info->callback->Monospace() && (logfont->elfLogFont.lfPitchAndFamily & (FF_MODERN | FIXED_PITCH)) != (FF_MODERN | FIXED_PITCH)) 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 != nullptr) { HDC dc = GetDC(nullptr); HGDIOBJ oldfont = SelectObject(dc, font); GetTextCharsetInfo(dc, &fs, 0); SelectObject(dc, oldfont); ReleaseDC(nullptr, dc); DeleteObject(font); } if ((fs.fsCsb[0] & info->locale.lsCsbSupported[0]) == 0 && (fs.fsCsb[1] & info->locale.lsCsbSupported[1]) == 0) return 1; } char font_name[MAX_PATH]; convert_from_fs((const wchar_t *)logfont->elfFullName, font_name, lengthof(font_name)); #ifdef WITH_FREETYPE /* Add english name after font name */ std::string english_name = GetEnglishFontName(logfont); strecpy(font_name + strlen(font_name) + 1, english_name.c_str(), lastof(font_name)); /* Check whether we can actually load the font. */ bool ft_init = _library != nullptr; bool found = false; FT_Face face; /* Init FreeType if needed. */ if ((ft_init || FT_Init_FreeType(&_library) == FT_Err_Ok) && GetFontByFaceName(font_name, &face) == FT_Err_Ok) { FT_Done_Face(face); found = true; } if (!ft_init) { /* Uninit FreeType if we did the init. */ FT_Done_FreeType(_library); _library = nullptr; } if (!found) return 1; #else const char *english_name = font_name; #endif /* WITH_FREETYPE */ info->callback->SetFontNames(info->settings, font_name, &logfont->elfLogFont); if (info->callback->FindMissingGlyphs()) return 1; Debug(fontcache, 1, "Fallback font: {} ({})", font_name, english_name); return 0; // stop enumerating } bool SetFallbackFont(FontCacheSettings *settings, const char *language_isocode, int winlangid, MissingGlyphSearcher *callback) { Debug(fontcache, 1, "Trying fallback fonts"); EFCParam langInfo; if (GetLocaleInfo(MAKELCID(winlangid, SORT_DEFAULT), LOCALE_FONTSIGNATURE, (LPTSTR)&langInfo.locale, sizeof(langInfo.locale) / sizeof(wchar_t)) == 0) { /* Invalid langid or some other mysterious error, can't determine fallback font. */ Debug(fontcache, 1, "Can't get locale info for fallback font (langid=0x{:x})", winlangid); return false; } langInfo.settings = settings; langInfo.callback = callback; LOGFONT font; /* Enumerate all fonts. */ font.lfCharSet = DEFAULT_CHARSET; font.lfFaceName[0] = '\0'; font.lfPitchAndFamily = 0; HDC dc = GetDC(nullptr); int ret = EnumFontFamiliesEx(dc, &font, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&langInfo, 0); ReleaseDC(nullptr, dc); return ret == 0; } #ifndef ANTIALIASED_QUALITY #define ANTIALIASED_QUALITY 4 #endif /** * Create a new Win32FontCache. * @param fs The font size that is going to be cached. * @param logfont The font that has to be loaded. * @param pixels The number of pixels this font should be high. */ Win32FontCache::Win32FontCache(FontSize fs, const LOGFONT &logfont, int pixels) : TrueTypeFontCache(fs, pixels), logfont(logfont) { this->dc = CreateCompatibleDC(nullptr); this->SetFontSize(fs, pixels); } Win32FontCache::~Win32FontCache() { this->ClearFontCache(); DeleteDC(this->dc); DeleteObject(this->font); } void Win32FontCache::SetFontSize(FontSize fs, int pixels) { if (pixels == 0) { /* Try to determine a good height based on the minimal height recommended by the font. */ int scaled_height = ScaleGUITrad(FontCache::GetDefaultFontHeight(this->fs)); pixels = scaled_height; HFONT temp = CreateFontIndirect(&this->logfont); if (temp != nullptr) { HGDIOBJ old = SelectObject(this->dc, temp); UINT size = GetOutlineTextMetrics(this->dc, 0, nullptr); LPOUTLINETEXTMETRIC otm = (LPOUTLINETEXTMETRIC)new BYTE[size]; GetOutlineTextMetrics(this->dc, size, otm); /* Font height is minimum height plus the difference between the default * height for this font size and the small size. */ int diff = scaled_height - ScaleGUITrad(FontCache::GetDefaultFontHeight(FS_SMALL)); /* Clamp() is not used as scaled_height could be greater than MAX_FONT_SIZE, which is not permitted in Clamp(). */ pixels = std::min(std::max(std::min(otm->otmusMinimumPPEM, MAX_FONT_MIN_REC_SIZE) + diff, scaled_height), MAX_FONT_SIZE); delete[] (BYTE*)otm; SelectObject(dc, old); DeleteObject(temp); } } else { pixels = ScaleGUITrad(pixels); } this->used_size = pixels; /* Create GDI font handle. */ this->logfont.lfHeight = -pixels; this->logfont.lfWidth = 0; this->logfont.lfOutPrecision = ANTIALIASED_QUALITY; if (this->font != nullptr) { SelectObject(dc, this->old_font); DeleteObject(this->font); } this->font = CreateFontIndirect(&this->logfont); this->old_font = SelectObject(this->dc, this->font); /* Query the font metrics we needed. */ UINT otmSize = GetOutlineTextMetrics(this->dc, 0, nullptr); POUTLINETEXTMETRIC otm = (POUTLINETEXTMETRIC)new BYTE[otmSize]; GetOutlineTextMetrics(this->dc, otmSize, otm); this->units_per_em = otm->otmEMSquare; this->ascender = otm->otmTextMetrics.tmAscent; this->descender = otm->otmTextMetrics.tmDescent; this->height = this->ascender + this->descender; this->glyph_size.cx = otm->otmTextMetrics.tmMaxCharWidth; this->glyph_size.cy = otm->otmTextMetrics.tmHeight; this->fontname = FS2OTTD((LPWSTR)((BYTE *)otm + (ptrdiff_t)otm->otmpFaceName)); Debug(fontcache, 2, "Loaded font '{}' with size {}", this->fontname, pixels); delete[] (BYTE*)otm; } /** * Reset cached glyphs. */ void Win32FontCache::ClearFontCache() { /* GUI scaling might have changed, determine font size anew if it was automatically selected. */ if (this->font != nullptr) this->SetFontSize(this->fs, this->req_size); this->TrueTypeFontCache::ClearFontCache(); } /* virtual */ const Sprite *Win32FontCache::InternalGetGlyph(GlyphID key, bool aa) { GLYPHMETRICS gm; MAT2 mat = { {0, 1}, {0, 0}, {0, 0}, {0, 1} }; /* Call GetGlyphOutline with zero size initially to get required memory size. */ DWORD size = GetGlyphOutline(this->dc, key, GGO_GLYPH_INDEX | (aa ? GGO_GRAY8_BITMAP : GGO_BITMAP), &gm, 0, nullptr, &mat); if (size == GDI_ERROR) UserError("Unable to render font glyph"); /* Add 1 scaled pixel for the shadow on the medium font. Our sprite must be at least 1x1 pixel. */ uint shadow = (this->fs == FS_NORMAL) ? ScaleGUITrad(1) : 0; uint width = std::max(1U, (uint)gm.gmBlackBoxX + shadow); uint height = std::max(1U, (uint)gm.gmBlackBoxY + shadow); /* Limit glyph size to prevent overflows later on. */ if (width > MAX_GLYPH_DIM || height > MAX_GLYPH_DIM) UserError("Font glyph is too large"); /* Call GetGlyphOutline again with size to actually render the glyph. */ byte *bmp = new byte[size]; GetGlyphOutline(this->dc, key, GGO_GLYPH_INDEX | (aa ? GGO_GRAY8_BITMAP : GGO_BITMAP), &gm, size, bmp, &mat); /* GDI has rendered the glyph, now we allocate a sprite and copy the image into it. */ SpriteLoader::Sprite sprite; sprite.AllocateData(ZOOM_LVL_NORMAL, width * height); sprite.type = SpriteType::Font; sprite.colours = (aa ? SCC_PAL | SCC_ALPHA : SCC_PAL); sprite.width = width; sprite.height = height; sprite.x_offs = gm.gmptGlyphOrigin.x; sprite.y_offs = this->ascender - gm.gmptGlyphOrigin.y; if (size > 0) { /* All pixel data returned by GDI is in the form of DWORD-aligned rows. * For a non anti-aliased glyph, the returned bitmap has one bit per pixel. * For anti-aliased rendering, GDI uses the strange value range of 0 to 64, * inclusively. To map this to 0 to 255, we shift left by two and then * subtract one. */ uint pitch = Align(aa ? gm.gmBlackBoxX : std::max((gm.gmBlackBoxX + 7u) / 8u, 1u), 4); /* Draw shadow for medium size. */ if (this->fs == FS_NORMAL && !aa) { for (uint y = 0; y < gm.gmBlackBoxY; y++) { for (uint x = 0; x < gm.gmBlackBoxX; x++) { if (aa ? (bmp[x + y * pitch] > 0) : HasBit(bmp[(x / 8) + y * pitch], 7 - (x % 8))) { sprite.data[shadow + x + (shadow + y) * sprite.width].m = SHADOW_COLOUR; sprite.data[shadow + x + (shadow + y) * sprite.width].a = aa ? (bmp[x + y * pitch] << 2) - 1 : 0xFF; } } } } for (uint y = 0; y < gm.gmBlackBoxY; y++) { for (uint x = 0; x < gm.gmBlackBoxX; x++) { if (aa ? (bmp[x + y * pitch] > 0) : HasBit(bmp[(x / 8) + y * pitch], 7 - (x % 8))) { sprite.data[x + y * sprite.width].m = FACE_COLOUR; sprite.data[x + y * sprite.width].a = aa ? (bmp[x + y * pitch] << 2) - 1 : 0xFF; } } } } GlyphEntry new_glyph; new_glyph.sprite = BlitterFactory::GetCurrentBlitter()->Encode(&sprite, SimpleSpriteAlloc); new_glyph.width = gm.gmCellIncX; this->SetGlyphPtr(key, &new_glyph); delete[] bmp; return new_glyph.sprite; } /* virtual */ GlyphID Win32FontCache::MapCharToGlyph(WChar key) { assert(IsPrintable(key)); if (key >= SCC_SPRITE_START && key <= SCC_SPRITE_END) { return this->parent->MapCharToGlyph(key); } /* Convert characters outside of the BMP into surrogate pairs. */ WCHAR chars[2]; if (key >= 0x010000U) { chars[0] = (wchar_t)(((key - 0x010000U) >> 10) + 0xD800); chars[1] = (wchar_t)(((key - 0x010000U) & 0x3FF) + 0xDC00); } else { chars[0] = (wchar_t)(key & 0xFFFF); } WORD glyphs[2] = { 0, 0 }; GetGlyphIndicesW(this->dc, chars, key >= 0x010000U ? 2 : 1, glyphs, GGI_MARK_NONEXISTING_GLYPHS); return glyphs[0] != 0xFFFF ? glyphs[0] : 0; } /* virtual */ const void *Win32FontCache::InternalGetFontTable(uint32 tag, size_t &length) { DWORD len = GetFontData(this->dc, tag, 0, nullptr, 0); void *result = nullptr; if (len != GDI_ERROR && len > 0) { result = MallocT(len); GetFontData(this->dc, tag, 0, result, len); } length = len; return result; } /** * Loads the GDI font. * If a GDI font description is present, e.g. from the automatic font * fallback search, use it. Otherwise, try to resolve it by font name. * @param fs The font size to load. */ void LoadWin32Font(FontSize fs) { FontCacheSubSetting *settings = GetFontCacheSubSetting(fs); if (settings->font.empty()) return; const char *font_name = settings->font.c_str(); LOGFONT logfont; MemSetT(&logfont, 0); logfont.lfPitchAndFamily = fs == FS_MONO ? FIXED_PITCH : VARIABLE_PITCH; logfont.lfCharSet = DEFAULT_CHARSET; logfont.lfOutPrecision = OUT_OUTLINE_PRECIS; logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS; if (settings->os_handle != nullptr) { logfont = *(const LOGFONT *)settings->os_handle; } else if (strchr(font_name, '.') != nullptr) { /* Might be a font file name, try load it. */ wchar_t fontPath[MAX_PATH] = {}; /* See if this is an absolute path. */ if (FileExists(settings->font)) { convert_to_fs(font_name, fontPath, lengthof(fontPath)); } else { /* Scan the search-paths to see if it can be found. */ std::string full_font = FioFindFullPath(BASE_DIR, font_name); if (!full_font.empty()) { convert_to_fs(font_name, fontPath, lengthof(fontPath)); } } if (fontPath[0] != 0) { if (AddFontResourceEx(fontPath, FR_PRIVATE, 0) != 0) { /* Try a nice little undocumented function first for getting the internal font name. * Some documentation is found at: http://www.undocprint.org/winspool/getfontresourceinfo */ static DllLoader _gdi32(L"gdi32.dll"); typedef BOOL(WINAPI *PFNGETFONTRESOURCEINFO)(LPCTSTR, LPDWORD, LPVOID, DWORD); static PFNGETFONTRESOURCEINFO GetFontResourceInfo = _gdi32.GetProcAddress("GetFontResourceInfoW"); if (GetFontResourceInfo != nullptr) { /* Try to query an array of LOGFONTs that describe the file. */ DWORD len = 0; if (GetFontResourceInfo(fontPath, &len, nullptr, 2) && len >= sizeof(LOGFONT)) { LOGFONT *buf = (LOGFONT *)new byte[len]; if (GetFontResourceInfo(fontPath, &len, buf, 2)) { logfont = *buf; // Just use first entry. } delete[] (byte *)buf; } } /* No dice yet. Use the file name as the font face name, hoping it matches. */ if (logfont.lfFaceName[0] == 0) { wchar_t fname[_MAX_FNAME]; _wsplitpath(fontPath, nullptr, nullptr, fname, nullptr); wcsncpy_s(logfont.lfFaceName, lengthof(logfont.lfFaceName), fname, _TRUNCATE); logfont.lfWeight = strcasestr(font_name, " bold") != nullptr || strcasestr(font_name, "-bold") != nullptr ? FW_BOLD : FW_NORMAL; // Poor man's way to allow selecting bold fonts. } } else { ShowInfo("Unable to load file '{}' for {} font, using default windows font selection instead", font_name, FontSizeToName(fs)); } } } if (logfont.lfFaceName[0] == 0) { logfont.lfWeight = strcasestr(font_name, " bold") != nullptr ? FW_BOLD : FW_NORMAL; // Poor man's way to allow selecting bold fonts. convert_to_fs(font_name, logfont.lfFaceName, lengthof(logfont.lfFaceName)); } HFONT font = CreateFontIndirect(&logfont); if (font == nullptr) { ShowInfo("Unable to use '{}' for {} font, Win32 reported error 0x{:X}, using sprite font instead", font_name, FontSizeToName(fs), GetLastError()); return; } DeleteObject(font); new Win32FontCache(fs, logfont, settings->size); }