diff --git a/config.lib b/config.lib index 23b5d430de..dec0dc3a77 100644 --- a/config.lib +++ b/config.lib @@ -1718,9 +1718,9 @@ make_cflags_and_ldflags() { # Some icu-configs have the 'feature' of not adding a space where others do add the space if [ "$static_icu" != "0" ]; then - LIBS="$LIBS `$icu_config --ldflags-searchpath` `$icu_config --ldflags-libsonly | tr '\n\r' ' ' | sed s/licu/lsicu/g`" + LIBS="$LIBS `$icu_config --ldflags-searchpath` `$icu_config --ldflags-libsonly --ldflags-layout | tr '\n\r' ' ' | sed s/licu/lsicu/g`" else - LIBS="$LIBS `$icu_config --ldflags-searchpath` `$icu_config --ldflags-libsonly | tr '\n\r' ' '`" + LIBS="$LIBS `$icu_config --ldflags-searchpath` `$icu_config --ldflags-libsonly --ldflags-layout | tr '\n\r' ' '`" fi fi diff --git a/projects/openttd_vs100.vcxproj b/projects/openttd_vs100.vcxproj index 4ab6f45e71..9f5a991f0c 100644 --- a/projects/openttd_vs100.vcxproj +++ b/projects/openttd_vs100.vcxproj @@ -131,7 +131,7 @@ 0x0809 - winmm.lib;ws2_32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;%(AdditionalDependencies) + winmm.lib;ws2_32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;icule.lib;iculx.lib;%(AdditionalDependencies) true %(IgnoreSpecificDefaultLibraries) true @@ -176,7 +176,7 @@ 0x0809 - winmm.lib;ws2_32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;%(AdditionalDependencies) + winmm.lib;ws2_32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;icule.lib;iculx.lib;%(AdditionalDependencies) true LIBCMT.lib;%(IgnoreSpecificDefaultLibraries) true @@ -233,7 +233,7 @@ 0x0809 - winmm.lib;ws2_32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;%(AdditionalDependencies) + winmm.lib;ws2_32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;icule.lib;iculx.lib;%(AdditionalDependencies) true %(IgnoreSpecificDefaultLibraries) true @@ -280,7 +280,7 @@ 0x0809 - winmm.lib;ws2_32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;%(AdditionalDependencies) + winmm.lib;ws2_32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;icule.lib;iculx.lib;%(AdditionalDependencies) true LIBCMT.lib;%(IgnoreSpecificDefaultLibraries) true @@ -317,11 +317,13 @@ + + @@ -440,11 +442,13 @@ + + diff --git a/projects/openttd_vs100.vcxproj.filters b/projects/openttd_vs100.vcxproj.filters index 34a8b4d000..0ecfeeb3a3 100644 --- a/projects/openttd_vs100.vcxproj.filters +++ b/projects/openttd_vs100.vcxproj.filters @@ -180,6 +180,9 @@ Source Files + + Source Files + Source Files @@ -195,6 +198,9 @@ Source Files + + Source Files + Source Files @@ -549,6 +555,9 @@ Header Files + + Header Files + Header Files @@ -564,6 +573,9 @@ Header Files + + Header Files + Header Files diff --git a/projects/openttd_vs100.vcxproj.in b/projects/openttd_vs100.vcxproj.in index 54b0063ec0..46b934c98f 100644 --- a/projects/openttd_vs100.vcxproj.in +++ b/projects/openttd_vs100.vcxproj.in @@ -131,7 +131,7 @@ 0x0809 - winmm.lib;ws2_32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;%(AdditionalDependencies) + winmm.lib;ws2_32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;icule.lib;iculx.lib;%(AdditionalDependencies) true %(IgnoreSpecificDefaultLibraries) true @@ -176,7 +176,7 @@ 0x0809 - winmm.lib;ws2_32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;%(AdditionalDependencies) + winmm.lib;ws2_32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;icule.lib;iculx.lib;%(AdditionalDependencies) true LIBCMT.lib;%(IgnoreSpecificDefaultLibraries) true @@ -233,7 +233,7 @@ 0x0809 - winmm.lib;ws2_32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;%(AdditionalDependencies) + winmm.lib;ws2_32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;icule.lib;iculx.lib;%(AdditionalDependencies) true %(IgnoreSpecificDefaultLibraries) true @@ -280,7 +280,7 @@ 0x0809 - winmm.lib;ws2_32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;%(AdditionalDependencies) + winmm.lib;ws2_32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;icule.lib;iculx.lib;%(AdditionalDependencies) true LIBCMT.lib;%(IgnoreSpecificDefaultLibraries) true diff --git a/projects/openttd_vs80.vcproj b/projects/openttd_vs80.vcproj index c6e3e595f7..1eec157596 100644 --- a/projects/openttd_vs80.vcproj +++ b/projects/openttd_vs80.vcproj @@ -87,7 +87,7 @@ /> + + @@ -558,6 +562,10 @@ RelativePath=".\..\src\gfxinit.cpp" > + + @@ -1034,6 +1042,10 @@ RelativePath=".\..\src\fontcache.h" > + + @@ -1054,6 +1066,10 @@ RelativePath=".\..\src\gfx_func.h" > + + diff --git a/projects/openttd_vs80.vcproj.in b/projects/openttd_vs80.vcproj.in index cbdc4f1fc0..162c9b136e 100644 --- a/projects/openttd_vs80.vcproj.in +++ b/projects/openttd_vs80.vcproj.in @@ -87,7 +87,7 @@ /> + + @@ -555,6 +559,10 @@ RelativePath=".\..\src\gfxinit.cpp" > + + @@ -1031,6 +1039,10 @@ RelativePath=".\..\src\fontcache.h" > + + @@ -1051,6 +1063,10 @@ RelativePath=".\..\src\gfx_func.h" > + + diff --git a/projects/openttd_vs90.vcproj.in b/projects/openttd_vs90.vcproj.in index d92d95425a..31946097cc 100644 --- a/projects/openttd_vs90.vcproj.in +++ b/projects/openttd_vs90.vcproj.in @@ -88,7 +88,7 @@ /> fs == parent->fs); + FontCache::caches[this->fs] = this; +} + +/** Clean everything up. */ +FontCache::~FontCache() +{ + assert(this->fs == parent->fs); + FontCache::caches[this->fs] = this->parent; +} + + +/** + * Get height of a character for a given font size. + * @param size Font size to get height of + * @return Height of characters in the given font (pixels) + */ +int GetCharacterHeight(FontSize size) +{ + return FontCache::Get(size)->GetHeight(); +} + + +/** Font cache for fonts that are based on a freetype font. */ +class SpriteFontCache : public FontCache { +private: + SpriteID **glyph_to_spriteid_map; ///< Mapping of glyphs to sprite IDs. + + void ClearGlyphToSpriteMap(); +public: + SpriteFontCache(FontSize fs); + ~SpriteFontCache(); + virtual SpriteID GetUnicodeGlyph(WChar key); + virtual void SetUnicodeGlyph(WChar key, SpriteID sprite); + virtual void InitializeUnicodeGlyphMap(); + virtual void ClearFontCache() {} + virtual const Sprite *GetGlyph(GlyphID key); + virtual uint GetGlyphWidth(GlyphID key); + virtual bool GetDrawGlyphShadow(); + virtual GlyphID MapCharToGlyph(WChar key) { return SPRITE_GLYPH | key; } + virtual const void *GetFontTable(uint32 tag, size_t &length) { length = 0; return NULL; } +}; + +/** + * Create a new sprite font cache. + * @param fs The font size to create the cache for. + */ +SpriteFontCache::SpriteFontCache(FontSize fs) : FontCache(fs), glyph_to_spriteid_map(NULL) +{ + this->InitializeUnicodeGlyphMap(); +} + +/** + * Free everything we allocated. + */ +SpriteFontCache::~SpriteFontCache() +{ + this->ClearGlyphToSpriteMap(); +} + +SpriteID SpriteFontCache::GetUnicodeGlyph(GlyphID key) +{ + if (this->glyph_to_spriteid_map[GB(key, 8, 8)] == NULL) return 0; + return this->glyph_to_spriteid_map[GB(key, 8, 8)][GB(key, 0, 8)]; +} + +void SpriteFontCache::SetUnicodeGlyph(GlyphID key, SpriteID sprite) +{ + if (this->glyph_to_spriteid_map == NULL) this->glyph_to_spriteid_map = CallocT(256); + if (this->glyph_to_spriteid_map[GB(key, 8, 8)] == NULL) this->glyph_to_spriteid_map[GB(key, 8, 8)] = CallocT(256); + this->glyph_to_spriteid_map[GB(key, 8, 8)][GB(key, 0, 8)] = sprite; +} + +void SpriteFontCache::InitializeUnicodeGlyphMap() +{ + /* Clear out existing glyph map if it exists */ + this->ClearGlyphToSpriteMap(); + + SpriteID base; + switch (this->fs) { + default: NOT_REACHED(); + case FS_MONO: // Use normal as default for mono spaced font, i.e. FALL THROUGH + case FS_NORMAL: base = SPR_ASCII_SPACE; break; + case FS_SMALL: base = SPR_ASCII_SPACE_SMALL; break; + case FS_LARGE: base = SPR_ASCII_SPACE_BIG; break; + } + + for (uint i = ASCII_LETTERSTART; i < 256; i++) { + SpriteID sprite = base + i - ASCII_LETTERSTART; + if (!SpriteExists(sprite)) continue; + this->SetUnicodeGlyph(i, sprite); + this->SetUnicodeGlyph(i + SCC_SPRITE_START, sprite); + } + + for (uint i = 0; i < lengthof(_default_unicode_map); i++) { + byte key = _default_unicode_map[i].key; + if (key == CLRA) { + /* Clear the glyph. This happens if the glyph at this code point + * is non-standard and should be accessed by an SCC_xxx enum + * entry only. */ + this->SetUnicodeGlyph(_default_unicode_map[i].code, 0); + } else { + SpriteID sprite = base + key - ASCII_LETTERSTART; + this->SetUnicodeGlyph(_default_unicode_map[i].code, sprite); + } } } +/** + * Clear the glyph to sprite mapping. + */ +void SpriteFontCache::ClearGlyphToSpriteMap() +{ + if (this->glyph_to_spriteid_map == NULL) return; + + for (uint i = 0; i < 256; i++) { + free(this->glyph_to_spriteid_map[i]); + } + free(this->glyph_to_spriteid_map); + this->glyph_to_spriteid_map = NULL; +} + +const Sprite *SpriteFontCache::GetGlyph(GlyphID key) +{ + SpriteID sprite = this->GetUnicodeGlyph(key); + if (sprite == 0) sprite = this->GetUnicodeGlyph('?'); + return GetSprite(sprite, ST_FONT); +} + +uint SpriteFontCache::GetGlyphWidth(GlyphID key) +{ + SpriteID sprite = this->GetUnicodeGlyph(key); + if (sprite == 0) sprite = this->GetUnicodeGlyph('?'); + return SpriteExists(sprite) ? GetSprite(sprite, ST_FONT)->width + (this->fs != FS_NORMAL) : 0; +} + +bool SpriteFontCache::GetDrawGlyphShadow() +{ + return false; +} + +/*static */ FontCache *FontCache::caches[FS_END] = { new SpriteFontCache(FS_NORMAL), new SpriteFontCache(FS_SMALL), new SpriteFontCache(FS_LARGE), new SpriteFontCache(FS_MONO) }; + #ifdef WITH_FREETYPE #include #include FT_FREETYPE_H #include FT_GLYPH_H +#include FT_TRUETYPE_TABLES_H -#ifdef WITH_FONTCONFIG -#include -#endif +/** Font cache for fonts that are based on a freetype font. */ +class FreeTypeFontCache : public FontCache { +private: + FT_Face face; ///< The font face associated with this font. -static FT_Library _library = NULL; -static FT_Face _face_small = NULL; -static FT_Face _face_medium = NULL; -static FT_Face _face_large = NULL; -static FT_Face _face_mono = NULL; -static int _ascender[FS_END]; + typedef SmallMap > FontTable; ///< Table with font table cache + FontTable font_tables; ///< Cached font tables. + + /** Container for information about a glyph. */ + struct GlyphEntry { + Sprite *sprite; ///< The loaded sprite. + byte width; ///< The width of the glyph. + bool duplicate; ///< Whether this glyph entry is a duplicate, i.e. may this be freed? + }; + + /** + * The glyph cache. This is structured to reduce memory consumption. + * 1) There is a 'segment' table for each font size. + * 2) Each segment table is a discrete block of characters. + * 3) Each block contains 256 (aligned) characters sequential characters. + * + * The cache is accessed in the following way: + * For character 0x0041 ('A'): glyph_to_sprite[0x00][0x41] + * For character 0x20AC (Euro): glyph_to_sprite[0x20][0xAC] + * + * Currently only 256 segments are allocated, "limiting" us to 65536 characters. + * This can be simply changed in the two functions Get & SetGlyphPtr. + */ + GlyphEntry **glyph_to_sprite; + + GlyphEntry *GetGlyphPtr(GlyphID key); + void SetGlyphPtr(GlyphID key, const GlyphEntry *glyph, bool duplicate = false); + +public: + FreeTypeFontCache(FontSize fs, FT_Face face, int pixels); + ~FreeTypeFontCache(); + virtual SpriteID GetUnicodeGlyph(WChar key) { return this->parent->GetUnicodeGlyph(key); } + virtual void SetUnicodeGlyph(WChar key, SpriteID sprite) { this->parent->SetUnicodeGlyph(key, sprite); } + virtual void InitializeUnicodeGlyphMap() { this->parent->InitializeUnicodeGlyphMap(); } + virtual void ClearFontCache(); + virtual const Sprite *GetGlyph(GlyphID key); + virtual uint GetGlyphWidth(GlyphID key); + virtual bool GetDrawGlyphShadow(); + virtual GlyphID MapCharToGlyph(WChar key); + virtual const void *GetFontTable(uint32 tag, size_t &length); +}; + +FT_Library _library = NULL; FreeTypeSettings _freetype; @@ -61,779 +241,46 @@ static const byte FACE_COLOUR = 1; static const byte SHADOW_COLOUR = 2; /** - * Get the font loaded into a Freetype face by using a font-name. - * If no appropriate font is found, the function returns an error + * Create a new FreeTypeFontCache. + * @param fs The font size that is going to be cached. + * @param face The font that has to be loaded. + * @param pixels The number of pixels this font should be high. */ - -/* ======================================================================================== - * Windows support - * ======================================================================================== */ - -#ifdef WIN32 -#include -#include /* SHGetFolderPath */ -#include "os/windows/win32.h" - -/** - * 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 UTF-8. - * @return the short path in ANSI (ASCII). - */ -char *GetShortPath(const char *long_path) +FreeTypeFontCache::FreeTypeFontCache(FontSize fs, FT_Face face, int pixels) : FontCache(fs), face(face), glyph_to_sprite(NULL) { - static char short_path[MAX_PATH]; -#ifdef UNICODE - /* The non-unicode GetShortPath doesn't support UTF-8..., - * so convert the path to wide chars, then get the short - * path and convert it back again. */ - wchar_t long_path_w[MAX_PATH]; - MultiByteToWideChar(CP_UTF8, 0, long_path, -1, long_path_w, MAX_PATH); + assert(face != NULL); - wchar_t short_path_w[MAX_PATH]; - GetShortPathNameW(long_path_w, short_path_w, MAX_PATH); + if (pixels == 0) { + /* Try to determine a good height based on the minimal height recommended by the font. */ + pixels = _default_font_height[this->fs]; - WideCharToMultiByte(CP_ACP, 0, short_path_w, -1, short_path, MAX_PATH, NULL, NULL); -#else - /* Technically not needed, but do it for consistency. */ - GetShortPathNameA(long_path, short_path, MAX_PATH); -#endif - 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 */ -#define FONT_DIR_NT "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts" -#define FONT_DIR_9X "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Fonts" -static FT_Error GetFontByFaceName(const char *font_name, FT_Face *face) -{ - FT_Error err = FT_Err_Cannot_Open_Resource; - HKEY hKey; - LONG ret; - TCHAR vbuffer[MAX_PATH], dbuffer[256]; - TCHAR *font_namep; - char *font_path; - uint index; - - /* On windows NT (2000, NT3.5, XP, etc.) the fonts are stored in the - * "Windows NT" key, on Windows 9x in the Windows key. To save us having - * to retrieve the windows version, we'll just query both */ - ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T(FONT_DIR_NT), 0, KEY_READ, &hKey); - if (ret != ERROR_SUCCESS) ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T(FONT_DIR_9X), 0, KEY_READ, &hKey); - - if (ret != ERROR_SUCCESS) { - DEBUG(freetype, 0, "Cannot open registry key HKLM\\SOFTWARE\\Microsoft\\Windows (NT)\\CurrentVersion\\Fonts"); - return err; - } - - /* For Unicode we need some conversion between widechar and - * normal char to match the data returned by RegEnumValue, - * otherwise just use parameter */ -#if defined(UNICODE) - font_namep = MallocT(MAX_PATH); - MB_TO_WIDE_BUFFER(font_name, font_namep, MAX_PATH * sizeof(TCHAR)); -#else - font_namep = const_cast(font_name); // only cast because in unicode pointer is not const -#endif - - for (index = 0;; index++) { - TCHAR *s; - DWORD vbuflen = lengthof(vbuffer); - DWORD dbuflen = lengthof(dbuffer); - - ret = RegEnumValue(hKey, index, vbuffer, &vbuflen, NULL, NULL, (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 = _tcschr(vbuffer, _T('(')); - if (s != NULL) s[-1] = '\0'; - - if (_tcschr(vbuffer, _T('&')) == NULL) { - if (_tcsicmp(vbuffer, font_namep) == 0) break; - } else { - if (_tcsstr(vbuffer, font_namep) != NULL) break; + TT_Header *head = (TT_Header *)FT_Get_Sfnt_Table(this->face, ft_sfnt_head); + if (head != NULL) { + /* Font height is minimum height plus the difference between the default + * height for this font size and the small size. */ + int diff = _default_font_height[this->fs] - _default_font_height[FS_SMALL]; + pixels = Clamp(min(head->Lowest_Rec_PPEM, 20) + diff, _default_font_height[this->fs], MAX_FONT_SIZE); } } - if (!SUCCEEDED(OTTDSHGetFolderPath(NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, vbuffer))) { - DEBUG(freetype, 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. - * Also note that FreeType does not support UNICODE filenames! */ -#if defined(UNICODE) - /* We need a cast here back from wide because FreeType doesn't support - * widechar filenames. Just use the buffer we allocated before for the - * font_name search */ - font_path = (char*)font_namep; - WIDE_TO_MB_BUFFER(vbuffer, font_path, MAX_PATH * sizeof(TCHAR)); -#else - font_path = vbuffer; -#endif - - ttd_strlcat(font_path, "\\", MAX_PATH * sizeof(TCHAR)); - ttd_strlcat(font_path, WIDE_TO_MB(dbuffer), MAX_PATH * sizeof(TCHAR)); - - /* Convert the path into something that FreeType understands */ - font_path = GetShortPath(font_path); - - index = 0; - do { - err = FT_New_Face(_library, font_path, index, face); - if (err != FT_Err_Ok) break; - - if (strncasecmp(font_name, (*face)->family_name, strlen((*face)->family_name)) == 0) break; - /* Try english name if font name failed */ - if (strncasecmp(font_name + strlen(font_name) + 1, (*face)->family_name, strlen((*face)->family_name)) == 0) break; - err = FT_Err_Cannot_Open_Resource; - - } while ((FT_Long)++index != (*face)->num_faces); - - -folder_error: -registry_no_font_found: -#if defined(UNICODE) - free(font_namep); -#endif - 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 const char *GetEnglishFontName(const ENUMLOGFONTEX *logfont) -{ - static char font_name[MAX_PATH]; - const char *ret_font_name = NULL; - 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 == NULL) goto err1; - - dc = GetDC(NULL); - oldfont = SelectObject(dc, font); - dw = GetFontData(dc, 'eman', 0, NULL, 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 = 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(NULL, dc); - DeleteObject(font); -err1: - return ret_font_name == NULL ? WIDE_TO_MB((const TCHAR*)logfont->elfFullName) : ret_font_name; -} - -class FontList { -protected: - TCHAR **fonts; - uint items; - uint capacity; - -public: - FontList() : fonts(NULL), items(0), capacity(0) { }; - - ~FontList() { - if (this->fonts == NULL) return; - - for (uint i = 0; i < this->items; i++) { - free(this->fonts[i]); - } - - free(this->fonts); - } - - bool Add(const TCHAR *font) { - for (uint i = 0; i < this->items; i++) { - if (_tcscmp(this->fonts[i], font) == 0) return false; - } - - if (this->items == this->capacity) { - this->capacity += 10; - this->fonts = ReallocT(this->fonts, this->capacity); - } - - this->fonts[this->items++] = _tcsdup(font); - - return true; - } -}; - -struct EFCParam { - FreeTypeSettings *settings; - LOCALESIGNATURE locale; - MissingGlyphSearcher *callback; - FontList fonts; -}; - -static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXTMETRICEX *metric, DWORD type, LPARAM lParam) -{ - EFCParam *info = (EFCParam *)lParam; - - /* Skip duplicates */ - if (!info->fonts.Add((const TCHAR*)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 != 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; - } - - char font_name[MAX_PATH]; -#if defined(UNICODE) - WIDE_TO_MB_BUFFER((const TCHAR*)logfont->elfFullName, font_name, lengthof(font_name)); -#else - strecpy(font_name, (const TCHAR*)logfont->elfFullName, lastof(font_name)); -#endif - - /* Add english name after font name */ - const char *english_name = GetEnglishFontName(logfont); - strecpy(font_name + strlen(font_name) + 1, english_name, lastof(font_name)); - - /* Check whether we can actually load the font. */ - bool ft_init = _library != NULL; - 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 = NULL; - } - - if (!found) return 1; - - info->callback->SetFontNames(info->settings, font_name); - if (info->callback->FindMissingGlyphs(NULL)) return 1; - DEBUG(freetype, 1, "Fallback font: %s (%s)", font_name, english_name); - return 0; // stop enumerating -} - -bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid, MissingGlyphSearcher *callback) -{ - DEBUG(freetype, 1, "Trying fallback fonts"); - EFCParam langInfo; - if (GetLocaleInfo(MAKELCID(winlangid, SORT_DEFAULT), LOCALE_FONTSIGNATURE, (LPTSTR)&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; - langInfo.callback = callback; - - 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(__APPLE__) /* end ifdef Win32 */ -/* ======================================================================================== - * OSX support - * ======================================================================================== */ - -#include "os/macosx/macos.h" - -FT_Error GetFontByFaceName(const char *font_name, FT_Face *face) -{ - FT_Error err = FT_Err_Cannot_Open_Resource; - - /* Get font reference from name. */ - CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, font_name, kCFStringEncodingUTF8); - ATSFontRef font = ATSFontFindFromName(name, kATSOptionFlagsDefault); - CFRelease(name); - if (font == kInvalidFont) return err; - - /* Get a file system reference for the font. */ - FSRef ref; - OSStatus os_err = -1; -#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) - if (MacOSVersionIsAtLeast(10, 5, 0)) { - os_err = ATSFontGetFileReference(font, &ref); - } else -#endif - { -#if (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5) && !__LP64__ - /* This type was introduced with the 10.5 SDK. */ -#if (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5) - #define ATSFSSpec FSSpec -#endif - FSSpec spec; - os_err = ATSFontGetFileSpecification(font, (ATSFSSpec *)&spec); - if (os_err == noErr) os_err = FSpMakeFSRef(&spec, &ref); -#endif - } - - if (os_err == noErr) { - /* Get unix path for file. */ - UInt8 file_path[PATH_MAX]; - if (FSRefMakePath(&ref, file_path, sizeof(file_path)) == 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) -{ - const char *str; - bool result = false; - - callback->FindMissingGlyphs(&str); - -#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) - if (MacOSVersionIsAtLeast(10, 5, 0)) { - /* 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 if (strncmp(language_isocode, "ur", 2) == 0) { - /* The urdu alphabet is variant of persian. As OS X has no default - * font that advertises an urdu language code, search for persian - * support instead. */ - strecpy(lang, "fa", lastof(lang)); - } else { - /* Just copy the first part of the isocode. */ - strecpy(lang, language_isocode, lastof(lang)); - char *sep = strchr(lang, '_'); - if (sep != NULL) *sep = '\0'; - } - - CFStringRef lang_code; - lang_code = CFStringCreateWithCString(kCFAllocatorDefault, lang, kCFStringEncodingUTF8); - - /* Create a font iterator and iterate over all fonts that - * are available to the application. */ - ATSFontIterator itr; - ATSFontRef font; - ATSFontIteratorCreate(kATSFontContextLocal, NULL, NULL, kATSOptionFlagsUnRestrictedScope, &itr); - while (!result && ATSFontIteratorNext(itr, &font) == noErr) { - /* Get CoreText font handle. */ - CTFontRef font_ref = CTFontCreateWithPlatformFont(font, 0.0, NULL, NULL); - CFArrayRef langs = CTFontCopySupportedLanguages(font_ref); - if (langs != NULL) { - /* Font has a list of supported languages. */ - for (CFIndex i = 0; i < CFArrayGetCount(langs); i++) { - CFStringRef lang = (CFStringRef)CFArrayGetValueAtIndex(langs, i); - if (CFStringCompare(lang, lang_code, kCFCompareAnchored) == kCFCompareEqualTo) { - /* Lang code is supported by font, get full font name. */ - CFStringRef font_name = CTFontCopyFullName(font_ref); - char name[128]; - CFStringGetCString(font_name, name, lengthof(name), kCFStringEncodingUTF8); - CFRelease(font_name); - /* Skip some inappropriate or ugly looking fonts that have better alternatives. */ - if (strncmp(name, "Courier", 7) == 0 || strncmp(name, "Apple Symbols", 13) == 0 || - strncmp(name, ".Aqua", 5) == 0 || strncmp(name, "LastResort", 10) == 0 || - strncmp(name, "GB18030 Bitmap", 14) == 0) continue; - - /* Save result. */ - callback->SetFontNames(settings, name); - DEBUG(freetype, 2, "CT-Font for %s: %s", language_isocode, name); - result = true; - break; - } - } - CFRelease(langs); - } - CFRelease(font_ref); - } - ATSFontIteratorRelease(&itr); - CFRelease(lang_code); - } else -#endif - { -#if (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5) && !__LP64__ - /* Determine fallback font using ATSUI. This uses a string sample with - * missing characters. This is not failure-proof, but a better way like - * using the isocode as in the CoreText code path is not available. - * ATSUI was deprecated with 10.6 and is only partially available in - * 64-bit mode. */ - - /* Remove all control characters in the range from SCC_CONTROL_START to - * SCC_CONTROL_END as well as all ASCII < 0x20 from the string as it will - * mess with the automatic font detection */ - char buff[256]; // This length is enough to find a suitable replacement font - strecpy(buff, str, lastof(buff)); - str_validate(buff, lastof(buff), SVS_ALLOW_NEWLINE); - - /* Extract a UniChar representation of the sample string. */ - CFStringRef cf_str = CFStringCreateWithCString(kCFAllocatorDefault, buff, kCFStringEncodingUTF8); - if (cf_str == NULL) { - /* Something went wrong. Corrupt/invalid sample string? */ - return false; - } - CFIndex str_len = CFStringGetLength(cf_str); - UniChar string[str_len]; - CFStringGetCharacters(cf_str, CFRangeMake(0, str_len), string); - - /* Create a default text style with the default font. */ - ATSUStyle style; - ATSUCreateStyle(&style); - - /* Create a text layout object from the sample string using the text style. */ - UniCharCount run_len = kATSUToTextEnd; - ATSUTextLayout text_layout; - ATSUCreateTextLayoutWithTextPtr(string, kATSUFromTextBeginning, kATSUToTextEnd, str_len, 1, &run_len, &style, &text_layout); - - /* Try to match a font for the sample text. ATSUMatchFontsToText stops after - * it finds the first continuous character run not renderable with the currently - * selected font starting at offset. The matching needs to be repeated until - * the end of the string is reached to make sure the fallback font matches for - * all characters in the string and not only the first run. */ - UniCharArrayOffset offset = kATSUFromTextBeginning; - OSStatus os_err; - do { - ATSUFontID font; - UniCharCount run_len; - os_err = ATSUMatchFontsToText(text_layout, offset, kATSUToTextEnd, &font, &offset, &run_len); - if (os_err == kATSUFontsMatched) { - /* Found a better fallback font. Update the text layout - * object with the new font. */ - ATSUAttributeTag tag = kATSUFontTag; - ByteCount size = sizeof(font); - ATSUAttributeValuePtr val = &font; - ATSUSetAttributes(style, 1, &tag, &size, &val); - offset += run_len; - } - /* Exit if the end of the string is reached or some other error occurred. */ - } while (os_err == kATSUFontsMatched && offset < (UniCharArrayOffset)str_len); - - if (os_err == noErr || os_err == kATSUFontsMatched) { - /* ATSUMatchFontsToText exited normally. Extract font - * out of the text layout object. */ - ATSUFontID font; - ByteCount act_len; - ATSUGetAttribute(style, kATSUFontTag, sizeof(font), &font, &act_len); - - /* Get unique font name. The result is not a c-string, we have - * to leave space for a \0 and terminate it ourselves. */ - char name[128]; - ATSUFindFontName(font, kFontUniqueName, kFontNoPlatformCode, kFontNoScriptCode, kFontNoLanguageCode, 127, name, &act_len, NULL); - name[act_len > 127 ? 127 : act_len] = '\0'; - - /* Save Result. */ - callback->SetFontNames(settings, name); - DEBUG(freetype, 2, "ATSUI-Font for %s: %s", language_isocode, name); - result = true; - } - - ATSUDisposeTextLayout(text_layout); - ATSUDisposeStyle(style); - CFRelease(cf_str); -#endif - } - - if (result && strncmp(settings->medium_font, "Geeza Pro", 9) == 0) { - /* The font 'Geeza Pro' is often found for arabic characters, but - * it has the 'tiny' problem of not having any latin characters. - * 'Arial Unicode MS' on the other hand has arabic and latin glyphs, - * but seems to 'forget' to inform the OS about this fact. Manually - * substitute the latter for the former if it is loadable. */ - bool ft_init = _library != NULL; - FT_Face face; - /* Init FreeType if needed. */ - if ((ft_init || FT_Init_FreeType(&_library) == FT_Err_Ok) && GetFontByFaceName("Arial Unicode MS", &face) == FT_Err_Ok) { - FT_Done_Face(face); - callback->SetFontNames(settings, "Arial Unicode MS"); - DEBUG(freetype, 1, "Replacing font 'Geeza Pro' with 'Arial Unicode MS'"); - } - if (!ft_init) { - /* Uninit FreeType if we did the init. */ - FT_Done_FreeType(_library); - _library = NULL; - } - } - - callback->FindMissingGlyphs(NULL); - return result; -} - -#elif defined(WITH_FONTCONFIG) /* end ifdef __APPLE__ */ -/* ======================================================================================== - * FontConfig (unix) support - * ======================================================================================== */ -static 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 = strdup(font_name); - font_style = strchr(font_family, ','); - if (font_style != NULL) { - 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 != NULL) FcPatternAddString(pat, FC_STYLE, (FcChar8*)font_style); - FcConfigSubstitute(0, pat, FcMatchPattern); - FcDefaultSubstitute(pat); - fs = FcFontSetCreate(); - match = FcFontMatch(0, pat, &result); - - if (fs != NULL && match != NULL) { - 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 != NULL && 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 != NULL) *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, NULL); - /* Get the list of filenames matching the wanted language. */ - FcFontSet *fs = FcFontList(NULL, pat, os); - - /* We don't need these anymore. */ - FcObjectSetDestroy(os); - FcPatternDestroy(pat); - - if (fs != NULL) { - int best_weight = -1; - const char *best_font = NULL; - - for (int i = 0; i < fs->nfont; i++) { - FcPattern *font = fs->fonts[i]; - - FcChar8 *file = NULL; - FcResult res = FcPatternGetString(font, FC_FILE, 0, &file); - if (res != FcResultMatch || file == NULL) { - 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(NULL); - DEBUG(freetype, 1, "Font \"%s\" misses%s glyphs", file, missing ? "" : " no"); - - if (!missing) { - best_weight = value; - best_font = (const char *)file; - } - } - - if (best_font != NULL) { - ret = true; - callback->SetFontNames(settings, best_font); - InitFreeType(callback->Monospace()); - } - - /* Clean up the list of filenames. */ - FcFontSetDestroy(fs); - } - - 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, int winlangid, MissingGlyphSearcher *callback) { return false; } -#endif /* WITH_FONTCONFIG */ - -static void SetFontGeometry(FT_Face face, FontSize size, int pixels) -{ - FT_Error err = FT_Set_Pixel_Sizes(face, 0, pixels); + FT_Error err = FT_Set_Pixel_Sizes(this->face, 0, pixels); if (err == FT_Err_Invalid_Pixel_Size) { /* Find nearest size to that requested */ - FT_Bitmap_Size *bs = face->available_sizes; - int i = face->num_fixed_sizes; + FT_Bitmap_Size *bs = this->face->available_sizes; + int i = this->face->num_fixed_sizes; int n = bs->height; for (; --i; bs++) { if (abs(pixels - bs->height) < abs(pixels - n)) n = bs->height; } - FT_Set_Pixel_Sizes(face, 0, n); + FT_Set_Pixel_Sizes(this->face, 0, n); } - int asc = face->size->metrics.ascender >> 6; - int dec = face->size->metrics.descender >> 6; - - _ascender[size] = asc; - _font_height[size] = asc - dec; + this->units_per_em = this->face->units_per_EM; + this->ascender = this->face->size->metrics.ascender >> 6; + this->descender = this->face->size->metrics.descender >> 6; + this->height = this->ascender - this->descender; } /** @@ -841,87 +288,20 @@ static void SetFontGeometry(FT_Face face, FontSize size, int pixels) * First type to load the fontname as if it were a path. If that fails, * try to resolve the filename of the font using fontconfig, where the * format is 'font family name' or 'font family name, font style'. + * @param fs The font size to load. */ -static void LoadFreeTypeFont(const char *font_name, FT_Face *face, const char *type) +static void LoadFreeTypeFont(FontSize fs) { - FT_Error error; - - if (StrEmpty(font_name)) return; - - error = FT_New_Face(_library, font_name, 0, face); - - if (error != FT_Err_Ok) error = GetFontByFaceName(font_name, face); - - if (error == FT_Err_Ok) { - DEBUG(freetype, 2, "Requested '%s', using '%s %s'", font_name, (*face)->family_name, (*face)->style_name); - - /* Attempt to select the unicode character map */ - error = FT_Select_Charmap(*face, ft_encoding_unicode); - if (error == FT_Err_Ok) return; // Success - - if (error == FT_Err_Invalid_CharMap_Handle) { - /* Try to pick a different character map instead. We default to - * the first map, but platform_id 0 encoding_id 0 should also - * be unicode (strange system...) */ - FT_CharMap found = (*face)->charmaps[0]; - int i; - - for (i = 0; i < (*face)->num_charmaps; i++) { - FT_CharMap charmap = (*face)->charmaps[i]; - if (charmap->platform_id == 0 && charmap->encoding_id == 0) { - found = charmap; - } - } - - if (found != NULL) { - error = FT_Set_Charmap(*face, found); - if (error == FT_Err_Ok) return; - } - } + FreeTypeSubSetting *settings = NULL; + switch (fs) { + default: NOT_REACHED(); + case FS_SMALL: settings = &_freetype.small; break; + case FS_NORMAL: settings = &_freetype.medium; break; + case FS_LARGE: settings = &_freetype.large; break; + case FS_MONO: settings = &_freetype.mono; break; } - FT_Done_Face(*face); - *face = NULL; - - ShowInfoF("Unable to use '%s' for %s font, FreeType reported error 0x%X, using sprite font instead", font_name, type, error); -} - - -static void ResetGlyphCache(bool monospace); - -/** - * 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; -} - -/** - * (Re)initialize the freetype related things, i.e. load the non-sprite fonts. - * @param monospace Whether to initialise the monospace or regular fonts. - */ -void InitFreeType(bool monospace) -{ - ResetFontSizes(monospace); - ResetGlyphCache(monospace); - - if (monospace) { - UnloadFace(&_face_mono); - } else { - UnloadFace(&_face_small); - UnloadFace(&_face_medium); - UnloadFace(&_face_large); - } - - if (StrEmpty(_freetype.small_font) && StrEmpty(_freetype.medium_font) && StrEmpty(_freetype.large_font) && StrEmpty(_freetype.mono_font)) { - DEBUG(freetype, 1, "No font faces specified, using sprite fonts instead"); - return; - } + if (StrEmpty(settings->font)) return; if (_library == NULL) { if (FT_Init_FreeType(&_library) != FT_Err_Ok) { @@ -932,140 +312,109 @@ void InitFreeType(bool monospace) DEBUG(freetype, 2, "Initialized"); } - /* Load each font */ - if (monospace) { - LoadFreeTypeFont(_freetype.mono_font , &_face_mono, "mono"); + FT_Face face = NULL; + FT_Error error = FT_New_Face(_library, settings->font, 0, &face); - if (_face_mono != NULL) { - SetFontGeometry(_face_mono, FS_MONO, _freetype.mono_size); - } - } else { - LoadFreeTypeFont(_freetype.small_font, &_face_small, "small"); - LoadFreeTypeFont(_freetype.medium_font, &_face_medium, "medium"); - LoadFreeTypeFont(_freetype.large_font, &_face_large, "large"); + if (error != FT_Err_Ok) error = GetFontByFaceName(settings->font, &face); - /* Set each font size */ - if (_face_small != NULL) { - SetFontGeometry(_face_small, FS_SMALL, _freetype.small_size); - } - if (_face_medium != NULL) { - SetFontGeometry(_face_medium, FS_NORMAL, _freetype.medium_size); - } - if (_face_large != NULL) { - SetFontGeometry(_face_large, FS_LARGE, _freetype.large_size); + if (error == FT_Err_Ok) { + DEBUG(freetype, 2, "Requested '%s', using '%s %s'", settings->font, face->family_name, face->style_name); + + /* Attempt to select the unicode character map */ + error = FT_Select_Charmap(face, ft_encoding_unicode); + if (error == FT_Err_Ok) goto found_face; // Success + + if (error == FT_Err_Invalid_CharMap_Handle) { + /* Try to pick a different character map instead. We default to + * the first map, but platform_id 0 encoding_id 0 should also + * be unicode (strange system...) */ + FT_CharMap found = face->charmaps[0]; + int i; + + for (i = 0; i < face->num_charmaps; i++) { + FT_CharMap charmap = face->charmaps[i]; + if (charmap->platform_id == 0 && charmap->encoding_id == 0) { + found = charmap; + } + } + + if (found != NULL) { + error = FT_Set_Charmap(face, found); + if (error == FT_Err_Ok) goto found_face; + } } } + + FT_Done_Face(face); + + static const char *SIZE_TO_NAME[] = { "medium", "small", "large", "mono" }; + ShowInfoF("Unable to use '%s' for %s font, FreeType reported error 0x%X, using sprite font instead", settings->font, SIZE_TO_NAME[fs], error); + return; + +found_face: + new FreeTypeFontCache(fs, face, settings->size); } + /** - * Free everything allocated w.r.t. fonts. + * Free everything that was allocated for this font cache. */ -void UninitFreeType() +FreeTypeFontCache::~FreeTypeFontCache() { - ResetGlyphCache(true); - ResetGlyphCache(false); + FT_Done_Face(this->face); + this->ClearFontCache(); - UnloadFace(&_face_small); - UnloadFace(&_face_medium); - UnloadFace(&_face_large); - UnloadFace(&_face_mono); - - FT_Done_FreeType(_library); - _library = NULL; + for (FontTable::iterator iter = this->font_tables.Begin(); iter != this->font_tables.End(); iter++) { + free(iter->second.second); + } } /** * Reset cached glyphs. */ -void ClearFontCache() +void FreeTypeFontCache::ClearFontCache() { - ResetGlyphCache(true); - ResetGlyphCache(false); -} + if (this->glyph_to_sprite == NULL) return; -static FT_Face GetFontFace(FontSize size) -{ - switch (size) { - default: NOT_REACHED(); - case FS_NORMAL: return _face_medium; - case FS_SMALL: return _face_small; - case FS_LARGE: return _face_large; - case FS_MONO: return _face_mono; - } -} - - -struct GlyphEntry { - Sprite *sprite; - byte width; - bool duplicate; -}; - - -/* The glyph cache. This is structured to reduce memory consumption. - * 1) There is a 'segment' table for each font size. - * 2) Each segment table is a discrete block of characters. - * 3) Each block contains 256 (aligned) characters sequential characters. - * - * The cache is accessed in the following way: - * For character 0x0041 ('A'): _glyph_ptr[FS_NORMAL][0x00][0x41] - * For character 0x20AC (Euro): _glyph_ptr[FS_NORMAL][0x20][0xAC] - * - * Currently only 256 segments are allocated, "limiting" us to 65536 characters. - * This can be simply changed in the two functions Get & SetGlyphPtr. - */ -static GlyphEntry **_glyph_ptr[FS_END]; - -/** - * Clear the complete cache - * @param monospace Whether to reset the monospace or regular font. - */ -static void ResetGlyphCache(bool monospace) -{ - for (FontSize i = FS_BEGIN; i < FS_END; i++) { - if (monospace != (i == FS_MONO)) continue; - if (_glyph_ptr[i] == NULL) continue; + for (int i = 0; i < 256; i++) { + if (this->glyph_to_sprite[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].duplicate) continue; - free(_glyph_ptr[i][j][k].sprite); - } - - free(_glyph_ptr[i][j]); + if (this->glyph_to_sprite[i][j].duplicate) continue; + free(this->glyph_to_sprite[i][j].sprite); } - free(_glyph_ptr[i]); - _glyph_ptr[i] = NULL; + free(this->glyph_to_sprite[i]); } + + free(this->glyph_to_sprite); + this->glyph_to_sprite = NULL; } -static GlyphEntry *GetGlyphPtr(FontSize size, WChar key) +FreeTypeFontCache::GlyphEntry *FreeTypeFontCache::GetGlyphPtr(GlyphID key) { - if (_glyph_ptr[size] == NULL) return NULL; - if (_glyph_ptr[size][GB(key, 8, 8)] == NULL) return NULL; - return &_glyph_ptr[size][GB(key, 8, 8)][GB(key, 0, 8)]; + if (this->glyph_to_sprite == NULL) return NULL; + if (this->glyph_to_sprite[GB(key, 8, 8)] == NULL) return NULL; + return &this->glyph_to_sprite[GB(key, 8, 8)][GB(key, 0, 8)]; } -static void SetGlyphPtr(FontSize size, WChar key, const GlyphEntry *glyph, bool duplicate = false) +void FreeTypeFontCache::SetGlyphPtr(GlyphID key, const GlyphEntry *glyph, bool duplicate) { - if (_glyph_ptr[size] == NULL) { - DEBUG(freetype, 3, "Allocating root glyph cache for size %u", size); - _glyph_ptr[size] = CallocT(256); + if (this->glyph_to_sprite == NULL) { + DEBUG(freetype, 3, "Allocating root glyph cache for size %u", this->fs); + this->glyph_to_sprite = CallocT(256); } - if (_glyph_ptr[size][GB(key, 8, 8)] == NULL) { - DEBUG(freetype, 3, "Allocating glyph cache for range 0x%02X00, size %u", GB(key, 8, 8), size); - _glyph_ptr[size][GB(key, 8, 8)] = CallocT(256); + if (this->glyph_to_sprite[GB(key, 8, 8)] == NULL) { + DEBUG(freetype, 3, "Allocating glyph cache for range 0x%02X00, size %u", GB(key, 8, 8), this->fs); + this->glyph_to_sprite[GB(key, 8, 8)] = CallocT(256); } - DEBUG(freetype, 4, "Set glyph for unicode character 0x%04X, size %u", key, size); - _glyph_ptr[size][GB(key, 8, 8)][GB(key, 0, 8)].sprite = glyph->sprite; - _glyph_ptr[size][GB(key, 8, 8)][GB(key, 0, 8)].width = glyph->width; - _glyph_ptr[size][GB(key, 8, 8)][GB(key, 0, 8)].duplicate = duplicate; + DEBUG(freetype, 4, "Set glyph for unicode character 0x%04X, size %u", key, this->fs); + this->glyph_to_sprite[GB(key, 8, 8)][GB(key, 0, 8)].sprite = glyph->sprite; + this->glyph_to_sprite[GB(key, 8, 8)][GB(key, 0, 8)].width = glyph->width; + this->glyph_to_sprite[GB(key, 8, 8)][GB(key, 0, 8)].duplicate = duplicate; } static void *AllocateFont(size_t size) @@ -1082,95 +431,72 @@ static bool GetFontAAState(FontSize size) switch (size) { default: NOT_REACHED(); - case FS_NORMAL: return _freetype.medium_aa; - case FS_SMALL: return _freetype.small_aa; - case FS_LARGE: return _freetype.large_aa; - case FS_MONO: return _freetype.mono_aa; + case FS_NORMAL: return _freetype.medium.aa; + case FS_SMALL: return _freetype.small.aa; + case FS_LARGE: return _freetype.large.aa; + case FS_MONO: return _freetype.mono.aa; } } -const Sprite *GetGlyph(FontSize size, WChar key) +const Sprite *FreeTypeFontCache::GetGlyph(GlyphID key) { - FT_Face face = GetFontFace(size); - FT_GlyphSlot slot; - GlyphEntry new_glyph; - GlyphEntry *glyph; - SpriteLoader::Sprite sprite; - int width; - int height; - int x; - int y; - - assert(IsPrintable(key)); - - /* Bail out if no face loaded, or for our special characters */ - if (face == NULL || (key >= SCC_SPRITE_START && key <= SCC_SPRITE_END)) { - SpriteID sprite = GetUnicodeGlyph(size, key); - if (sprite == 0) sprite = GetUnicodeGlyph(size, '?'); - - /* Load the sprite if it's known. */ - if (sprite != 0) return GetSprite(sprite, ST_FONT); - - /* For the 'rare' case there is no font available at all. */ - if (face == NULL) error("No sprite font and no real font either... bailing!"); - - /* Use the '?' from the freetype font. */ - key = '?'; - } + if ((key & SPRITE_GLYPH) != 0) return parent->GetGlyph(key); /* Check for the glyph in our cache */ - glyph = GetGlyphPtr(size, key); + GlyphEntry *glyph = this->GetGlyphPtr(key); if (glyph != NULL && glyph->sprite != NULL) return glyph->sprite; - slot = face->glyph; + FT_GlyphSlot slot = this->face->glyph; - bool aa = GetFontAAState(size); + bool aa = GetFontAAState(this->fs); - FT_UInt glyph_index = FT_Get_Char_Index(face, key); - if (glyph_index == 0) { - if (key == '?') { + GlyphEntry new_glyph; + if (key == 0) { + GlyphID question_glyph = this->MapCharToGlyph('?'); + if (question_glyph == 0) { /* The font misses the '?' character. Use sprite font. */ - SpriteID sprite = GetUnicodeGlyph(size, key); + SpriteID sprite = this->GetUnicodeGlyph(key); Sprite *spr = (Sprite*)GetRawSprite(sprite, ST_FONT, AllocateFont); assert(spr != NULL); new_glyph.sprite = spr; - new_glyph.width = spr->width + (size != FS_NORMAL); - SetGlyphPtr(size, key, &new_glyph, false); + new_glyph.width = spr->width + (this->fs != FS_NORMAL); + this->SetGlyphPtr(key, &new_glyph, false); return new_glyph.sprite; } else { /* Use '?' for missing characters. */ - GetGlyph(size, '?'); - glyph = GetGlyphPtr(size, '?'); - SetGlyphPtr(size, key, glyph, true); + this->GetGlyph(question_glyph); + glyph = this->GetGlyphPtr(question_glyph); + this->SetGlyphPtr(key, glyph, true); return glyph->sprite; } } - FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT); - FT_Render_Glyph(face->glyph, aa ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO); + FT_Load_Glyph(this->face, key, FT_LOAD_DEFAULT); + FT_Render_Glyph(this->face->glyph, aa ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO); /* Despite requesting a normal glyph, FreeType may have returned a bitmap */ aa = (slot->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY); /* Add 1 pixel for the shadow on the medium font. Our sprite must be at least 1x1 pixel */ - width = max(1, slot->bitmap.width + (size == FS_NORMAL)); - height = max(1, slot->bitmap.rows + (size == FS_NORMAL)); + int width = max(1, slot->bitmap.width + (this->fs == FS_NORMAL)); + int height = max(1, slot->bitmap.rows + (this->fs == FS_NORMAL)); /* Limit glyph size to prevent overflows later on. */ if (width > 256 || height > 256) usererror("Font glyph is too large"); /* FreeType 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 = ST_FONT; sprite.width = width; sprite.height = height; sprite.x_offs = slot->bitmap_left; - sprite.y_offs = _ascender[size] - slot->bitmap_top; + sprite.y_offs = this->ascender - slot->bitmap_top; /* Draw shadow for medium size */ - if (size == FS_NORMAL && !aa) { - for (y = 0; y < slot->bitmap.rows; y++) { - for (x = 0; x < slot->bitmap.width; x++) { + if (this->fs == FS_NORMAL && !aa) { + for (int y = 0; y < slot->bitmap.rows; y++) { + for (int x = 0; x < slot->bitmap.width; x++) { if (aa ? (slot->bitmap.buffer[x + y * slot->bitmap.pitch] > 0) : HasBit(slot->bitmap.buffer[(x / 8) + y * slot->bitmap.pitch], 7 - (x % 8))) { sprite.data[1 + x + (1 + y) * sprite.width].m = SHADOW_COLOUR; sprite.data[1 + x + (1 + y) * sprite.width].a = aa ? slot->bitmap.buffer[x + y * slot->bitmap.pitch] : 0xFF; @@ -1179,8 +505,8 @@ const Sprite *GetGlyph(FontSize size, WChar key) } } - for (y = 0; y < slot->bitmap.rows; y++) { - for (x = 0; x < slot->bitmap.width; x++) { + for (int y = 0; y < slot->bitmap.rows; y++) { + for (int x = 0; x < slot->bitmap.width; x++) { if (aa ? (slot->bitmap.buffer[x + y * slot->bitmap.pitch] > 0) : HasBit(slot->bitmap.buffer[(x / 8) + y * slot->bitmap.pitch], 7 - (x % 8))) { sprite.data[x + y * sprite.width].m = FACE_COLOUR; sprite.data[x + y * sprite.width].a = aa ? slot->bitmap.buffer[x + y * slot->bitmap.pitch] : 0xFF; @@ -1191,108 +517,97 @@ const Sprite *GetGlyph(FontSize size, WChar key) new_glyph.sprite = BlitterFactoryBase::GetCurrentBlitter()->Encode(&sprite, AllocateFont); new_glyph.width = slot->advance.x >> 6; - SetGlyphPtr(size, key, &new_glyph); + this->SetGlyphPtr(key, &new_glyph); return new_glyph.sprite; } -bool GetDrawGlyphShadow() +bool FreeTypeFontCache::GetDrawGlyphShadow() { - return GetFontFace(FS_NORMAL) != NULL && GetFontAAState(FS_NORMAL); + return this->fs == FS_NORMAL && GetFontAAState(FS_NORMAL); } -uint GetGlyphWidth(FontSize size, WChar key) +uint FreeTypeFontCache::GetGlyphWidth(GlyphID key) { - FT_Face face = GetFontFace(size); - GlyphEntry *glyph; + if ((key & SPRITE_GLYPH) != 0) return this->parent->GetGlyphWidth(key); - if (face == NULL || (key >= SCC_SPRITE_START && key <= SCC_SPRITE_END)) { - SpriteID sprite = GetUnicodeGlyph(size, key); - if (sprite == 0) sprite = GetUnicodeGlyph(size, '?'); - return SpriteExists(sprite) ? GetSprite(sprite, ST_FONT)->width + (size != FS_NORMAL && size != FS_MONO) : 0; - } - - glyph = GetGlyphPtr(size, key); + GlyphEntry *glyph = this->GetGlyphPtr(key); if (glyph == NULL || glyph->sprite == NULL) { - GetGlyph(size, key); - glyph = GetGlyphPtr(size, key); + this->GetGlyph(key); + glyph = this->GetGlyphPtr(key); } return glyph->width; } +GlyphID FreeTypeFontCache::MapCharToGlyph(WChar key) +{ + assert(IsPrintable(key)); + + if (key >= SCC_SPRITE_START && key <= SCC_SPRITE_END) { + return this->parent->MapCharToGlyph(key); + } + + return FT_Get_Char_Index(this->face, key); +} + +const void *FreeTypeFontCache::GetFontTable(uint32 tag, size_t &length) +{ + const FontTable::iterator iter = this->font_tables.Find(tag); + if (iter != this->font_tables.End()) { + length = iter->second.first; + return iter->second.second; + } + + FT_ULong len = 0; + FT_Byte *result = NULL; + + FT_Load_Sfnt_Table(this->face, tag, 0, NULL, &len); + + if (len > 0) { + result = MallocT(len); + FT_Load_Sfnt_Table(this->face, tag, 0, result, &len); + } + length = len; + + this->font_tables.Insert(tag, SmallPair(length, result)); + return result; +} #endif /* WITH_FREETYPE */ -/* Sprite based glyph mapping */ - -#include "table/unicode.h" - -static SpriteID **_unicode_glyph_map[FS_END]; - - -/** Get the SpriteID of the first glyph for the given font size */ -static SpriteID GetFontBase(FontSize size) +/** + * (Re)initialize the freetype related things, i.e. load the non-sprite fonts. + * @param monospace Whether to initialise the monospace or regular fonts. + */ +void InitFreeType(bool monospace) { - switch (size) { - default: NOT_REACHED(); - case FS_NORMAL: return SPR_ASCII_SPACE; - case FS_SMALL: return SPR_ASCII_SPACE_SMALL; - case FS_LARGE: return SPR_ASCII_SPACE_BIG; - case FS_MONO: return SPR_ASCII_SPACE; + for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) { + if (monospace != (fs == FS_MONO)) continue; + + FontCache *fc = FontCache::Get(fs); + if (fc->HasParent()) delete fc; + +#ifdef WITH_FREETYPE + LoadFreeTypeFont(fs); +#endif } } - -SpriteID GetUnicodeGlyph(FontSize size, uint32 key) +/** + * Free everything allocated w.r.t. fonts. + */ +void UninitFreeType() { - if (_unicode_glyph_map[size][GB(key, 8, 8)] == NULL) return 0; - return _unicode_glyph_map[size][GB(key, 8, 8)][GB(key, 0, 8)]; -} - - -void SetUnicodeGlyph(FontSize size, uint32 key, SpriteID sprite) -{ - if (_unicode_glyph_map[size] == NULL) _unicode_glyph_map[size] = CallocT(256); - if (_unicode_glyph_map[size][GB(key, 8, 8)] == NULL) _unicode_glyph_map[size][GB(key, 8, 8)] = CallocT(256); - _unicode_glyph_map[size][GB(key, 8, 8)][GB(key, 0, 8)] = sprite; -} - - -void InitializeUnicodeGlyphMap() -{ - for (FontSize size = FS_BEGIN; size != FS_END; size++) { - /* Clear out existing glyph map if it exists */ - if (_unicode_glyph_map[size] != NULL) { - for (uint i = 0; i < 256; i++) { - free(_unicode_glyph_map[size][i]); - } - free(_unicode_glyph_map[size]); - _unicode_glyph_map[size] = NULL; - } - - SpriteID base = GetFontBase(size); - - for (uint i = ASCII_LETTERSTART; i < 256; i++) { - SpriteID sprite = base + i - ASCII_LETTERSTART; - if (!SpriteExists(sprite)) continue; - SetUnicodeGlyph(size, i, sprite); - SetUnicodeGlyph(size, i + SCC_SPRITE_START, sprite); - } - - for (uint i = 0; i < lengthof(_default_unicode_map); i++) { - byte key = _default_unicode_map[i].key; - if (key == CLRA) { - /* Clear the glyph. This happens if the glyph at this code point - * is non-standard and should be accessed by an SCC_xxx enum - * entry only. */ - SetUnicodeGlyph(size, _default_unicode_map[i].code, 0); - } else { - SpriteID sprite = base + key - ASCII_LETTERSTART; - SetUnicodeGlyph(size, _default_unicode_map[i].code, sprite); - } - } + for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) { + FontCache *fc = FontCache::Get(fs); + if (fc->HasParent()) delete fc; } + +#ifdef WITH_FREETYPE + FT_Done_FreeType(_library); + _library = NULL; +#endif /* WITH_FREETYPE */ } diff --git a/src/fontcache.h b/src/fontcache.h index d3ee5b1eac..1f52ddcdc4 100644 --- a/src/fontcache.h +++ b/src/fontcache.h @@ -12,84 +12,200 @@ #ifndef FONTCACHE_H #define FONTCACHE_H +#include "string_type.h" #include "spritecache.h" +/** Glyphs are characters from a font. */ +typedef uint32 GlyphID; +static const GlyphID SPRITE_GLYPH = 1U << 30; + +/** Font cache for basic fonts. */ +class FontCache { +private: + static FontCache *caches[FS_END]; ///< All the font caches. +protected: + FontCache *parent; ///< The parent of this font cache. + const FontSize fs; ///< The size of the font. + int height; ///< The height of the font. + int ascender; ///< The ascender value of the font. + int descender; ///< The descender value of the font. + int units_per_em; ///< The units per EM value of the font. +public: + FontCache(FontSize fs); + virtual ~FontCache(); + + /** + * Get the FontSize of the font. + * @return The FontSize. + */ + inline FontSize GetSize() const { return this->fs; } + + /** + * Get the height of the font. + * @return The height of the font. + */ + inline int GetHeight() const { return this->height; } + + /** + * Get the ascender value of the font. + * @return The ascender value of the font. + */ + inline int GetAscender() const { return this->ascender; } + + /** + * Get the descender value of the font. + * @return The descender value of the font. + */ + inline int GetDescender() const{ return this->descender; } + + /** + * Get the units per EM value of the font. + * @return The units per EM value of the font. + */ + inline int GetUnitsPerEM() const { return this->units_per_em; } + + /** + * Get the SpriteID mapped to the given key + * @param key The key to get the sprite for. + * @return The sprite. + */ + virtual SpriteID GetUnicodeGlyph(WChar key) = 0; + + /** + * Map a SpriteID to the key + * @param key The key to map to. + * @param sprite The sprite that is being mapped. + */ + virtual void SetUnicodeGlyph(WChar key, SpriteID sprite) = 0; + + /** Initialize the glyph map */ + virtual void InitializeUnicodeGlyphMap() = 0; + + /** Clear the font cache. */ + virtual void ClearFontCache() = 0; + + /** + * Get the glyph (sprite) of the given key. + * @param key The key to look up. + * @return The sprite. + */ + virtual const Sprite *GetGlyph(GlyphID key) = 0; + + /** + * Get the width of the glyph with the given key. + * @param key The key to look up. + * @return The width. + */ + virtual uint GetGlyphWidth(GlyphID key) = 0; + + /** + * Do we need to draw a glyph shadow? + * @return True if it has to be done, otherwise false. + */ + virtual bool GetDrawGlyphShadow() = 0; + + /** + * Map a character into a glyph. + * @param key The character. + * @return The glyph ID used to draw the character. + */ + virtual GlyphID MapCharToGlyph(WChar key) = 0; + + /** + * Read a font table from the font. + * @param tag The of the table to load. + * @param length The length of the read data. + * @return The loaded table data. + */ + virtual const void *GetFontTable(uint32 tag, size_t &length) = 0; + + /** + * Get the font cache of a given font size. + * @param fs The font size to look up. + * @return The font cache. + */ + static inline FontCache *Get(FontSize fs) + { + assert(fs < FS_END); + return FontCache::caches[fs]; + } + + /** + * Check whether the font cache has a parent. + */ + inline bool HasParent() + { + return this->parent != NULL; + } +}; + /** Get the SpriteID mapped to the given font size and key */ -SpriteID GetUnicodeGlyph(FontSize size, uint32 key); +static inline SpriteID GetUnicodeGlyph(FontSize size, WChar key) +{ + return FontCache::Get(size)->GetUnicodeGlyph(key); +} /** Map a SpriteID to the font size and key */ -void SetUnicodeGlyph(FontSize size, uint32 key, SpriteID sprite); +static inline void SetUnicodeGlyph(FontSize size, WChar key, SpriteID sprite) +{ + FontCache::Get(size)->SetUnicodeGlyph(key, sprite); +} /** Initialize the glyph map */ -void InitializeUnicodeGlyphMap(); +static inline void InitializeUnicodeGlyphMap() +{ + for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) { + FontCache::Get(fs)->InitializeUnicodeGlyphMap(); + } +} + +static inline void ClearFontCache() { + for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) { + FontCache::Get(fs)->ClearFontCache(); + } +} + +/** Get the Sprite for a glyph */ +static inline const Sprite *GetGlyph(FontSize size, WChar key) +{ + FontCache *fc = FontCache::Get(size); + return fc->GetGlyph(fc->MapCharToGlyph(key)); +} + +/** Get the width of a glyph */ +static inline uint GetGlyphWidth(FontSize size, WChar key) +{ + FontCache *fc = FontCache::Get(size); + return fc->GetGlyphWidth(fc->MapCharToGlyph(key)); +} + +static inline bool GetDrawGlyphShadow(FontSize size) +{ + return FontCache::Get(size)->GetDrawGlyphShadow(); +} #ifdef WITH_FREETYPE +/** Settings for a single freetype font. */ +struct FreeTypeSubSetting { + char font[MAX_PATH]; ///< The name of the font, or path to the font. + uint size; ///< The (requested) size of the font. + bool aa; ///< Whether to do anti aliasing or not. +}; + +/** Settings for the freetype fonts. */ struct FreeTypeSettings { - char small_font[MAX_PATH]; - char medium_font[MAX_PATH]; - char large_font[MAX_PATH]; - char mono_font[MAX_PATH]; - uint small_size; - uint medium_size; - uint large_size; - uint mono_size; - bool small_aa; - bool medium_aa; - bool large_aa; - bool mono_aa; + FreeTypeSubSetting small; ///< The smallest font; mostly used for zoomed out view. + FreeTypeSubSetting medium; ///< The normal font size. + FreeTypeSubSetting large; ///< The largest font; mostly used for newspapers. + FreeTypeSubSetting mono; ///< The mono space font used for license/readme viewers. }; extern FreeTypeSettings _freetype; -void InitFreeType(bool monospace); -void UninitFreeType(); -void ClearFontCache(); -const Sprite *GetGlyph(FontSize size, uint32 key); -uint GetGlyphWidth(FontSize size, uint32 key); -bool GetDrawGlyphShadow(); - -/** - * 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. - * @param callback The function to call to check for missing glyphs. - * @return true if a font has been set, false otherwise. - */ -bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid, class MissingGlyphSearcher *callback); - -#else - -/* Stub for initializiation */ -static inline void InitFreeType(bool monospace) { extern void ResetFontSizes(bool monospace); ResetFontSizes(monospace); } -static inline void UninitFreeType() {} -static inline void ClearFontCache() {} - -/** Get the Sprite for a glyph */ -static inline const Sprite *GetGlyph(FontSize size, uint32 key) -{ - SpriteID sprite = GetUnicodeGlyph(size, key); - if (sprite == 0) sprite = GetUnicodeGlyph(size, '?'); - return GetSprite(sprite, ST_FONT); -} - - -/** Get the width of a glyph */ -static inline uint GetGlyphWidth(FontSize size, uint32 key) -{ - SpriteID sprite = GetUnicodeGlyph(size, key); - if (sprite == 0) sprite = GetUnicodeGlyph(size, '?'); - return SpriteExists(sprite) ? GetSprite(sprite, ST_FONT)->width + (size != FS_NORMAL) : 0; -} - -static inline bool GetDrawGlyphShadow() -{ - return false; -} - #endif /* WITH_FREETYPE */ +void InitFreeType(bool monospace); +void UninitFreeType(); + #endif /* FONTCACHE_H */ diff --git a/src/fontdetection.cpp b/src/fontdetection.cpp new file mode 100644 index 0000000000..3064268636 --- /dev/null +++ b/src/fontdetection.cpp @@ -0,0 +1,780 @@ +/* $Id$ */ + +/* + * 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. */ + +#ifdef WITH_FREETYPE + +#include "stdafx.h" +#include "debug.h" +#include "fontdetection.h" +#include "string_func.h" +#include "strings_func.h" + +extern FT_Library _library; + +/** + * Get the font loaded into a Freetype face by using a font-name. + * If no appropriate font is found, the function returns an error + */ + +/* ======================================================================================== + * Windows support + * ======================================================================================== */ + +#ifdef WIN32 +#include "core/alloc_func.hpp" +#include "core/math_func.hpp" +#include +#include /* SHGetFolderPath */ +#include "os/windows/win32.h" + +/** + * 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 UTF-8. + * @return the short path in ANSI (ASCII). + */ +char *GetShortPath(const char *long_path) +{ + static char short_path[MAX_PATH]; +#ifdef UNICODE + /* The non-unicode GetShortPath doesn't support UTF-8..., + * so convert the path to wide chars, then get the short + * path and convert it back again. */ + wchar_t long_path_w[MAX_PATH]; + MultiByteToWideChar(CP_UTF8, 0, long_path, -1, long_path_w, MAX_PATH); + + wchar_t short_path_w[MAX_PATH]; + GetShortPathNameW(long_path_w, short_path_w, MAX_PATH); + + WideCharToMultiByte(CP_ACP, 0, short_path_w, -1, short_path, MAX_PATH, NULL, NULL); +#else + /* Technically not needed, but do it for consistency. */ + GetShortPathNameA(long_path, short_path, MAX_PATH); +#endif + 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 */ +#define FONT_DIR_NT "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts" +#define FONT_DIR_9X "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Fonts" +FT_Error GetFontByFaceName(const char *font_name, FT_Face *face) +{ + FT_Error err = FT_Err_Cannot_Open_Resource; + HKEY hKey; + LONG ret; + TCHAR vbuffer[MAX_PATH], dbuffer[256]; + TCHAR *font_namep; + char *font_path; + uint index; + + /* On windows NT (2000, NT3.5, XP, etc.) the fonts are stored in the + * "Windows NT" key, on Windows 9x in the Windows key. To save us having + * to retrieve the windows version, we'll just query both */ + ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T(FONT_DIR_NT), 0, KEY_READ, &hKey); + if (ret != ERROR_SUCCESS) ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T(FONT_DIR_9X), 0, KEY_READ, &hKey); + + if (ret != ERROR_SUCCESS) { + DEBUG(freetype, 0, "Cannot open registry key HKLM\\SOFTWARE\\Microsoft\\Windows (NT)\\CurrentVersion\\Fonts"); + return err; + } + + /* For Unicode we need some conversion between widechar and + * normal char to match the data returned by RegEnumValue, + * otherwise just use parameter */ +#if defined(UNICODE) + font_namep = MallocT(MAX_PATH); + MB_TO_WIDE_BUFFER(font_name, font_namep, MAX_PATH * sizeof(TCHAR)); +#else + font_namep = const_cast(font_name); // only cast because in unicode pointer is not const +#endif + + for (index = 0;; index++) { + TCHAR *s; + DWORD vbuflen = lengthof(vbuffer); + DWORD dbuflen = lengthof(dbuffer); + + ret = RegEnumValue(hKey, index, vbuffer, &vbuflen, NULL, NULL, (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 = _tcschr(vbuffer, _T('(')); + if (s != NULL) s[-1] = '\0'; + + if (_tcschr(vbuffer, _T('&')) == NULL) { + if (_tcsicmp(vbuffer, font_namep) == 0) break; + } else { + if (_tcsstr(vbuffer, font_namep) != NULL) break; + } + } + + if (!SUCCEEDED(OTTDSHGetFolderPath(NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, vbuffer))) { + DEBUG(freetype, 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. + * Also note that FreeType does not support UNICODE filenames! */ +#if defined(UNICODE) + /* We need a cast here back from wide because FreeType doesn't support + * widechar filenames. Just use the buffer we allocated before for the + * font_name search */ + font_path = (char*)font_namep; + WIDE_TO_MB_BUFFER(vbuffer, font_path, MAX_PATH * sizeof(TCHAR)); +#else + font_path = vbuffer; +#endif + + ttd_strlcat(font_path, "\\", MAX_PATH * sizeof(TCHAR)); + ttd_strlcat(font_path, WIDE_TO_MB(dbuffer), MAX_PATH * sizeof(TCHAR)); + + /* Convert the path into something that FreeType understands */ + font_path = GetShortPath(font_path); + + index = 0; + do { + err = FT_New_Face(_library, font_path, index, face); + if (err != FT_Err_Ok) break; + + if (strncasecmp(font_name, (*face)->family_name, strlen((*face)->family_name)) == 0) break; + /* Try english name if font name failed */ + if (strncasecmp(font_name + strlen(font_name) + 1, (*face)->family_name, strlen((*face)->family_name)) == 0) break; + err = FT_Err_Cannot_Open_Resource; + + } while ((FT_Long)++index != (*face)->num_faces); + + +folder_error: +registry_no_font_found: +#if defined(UNICODE) + free(font_namep); +#endif + 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 const char *GetEnglishFontName(const ENUMLOGFONTEX *logfont) +{ + static char font_name[MAX_PATH]; + const char *ret_font_name = NULL; + 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 == NULL) goto err1; + + dc = GetDC(NULL); + oldfont = SelectObject(dc, font); + dw = GetFontData(dc, 'eman', 0, NULL, 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 = 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(NULL, dc); + DeleteObject(font); +err1: + return ret_font_name == NULL ? WIDE_TO_MB((const TCHAR*)logfont->elfFullName) : ret_font_name; +} + +class FontList { +protected: + TCHAR **fonts; + uint items; + uint capacity; + +public: + FontList() : fonts(NULL), items(0), capacity(0) { }; + + ~FontList() { + if (this->fonts == NULL) return; + + for (uint i = 0; i < this->items; i++) { + free(this->fonts[i]); + } + + free(this->fonts); + } + + bool Add(const TCHAR *font) { + for (uint i = 0; i < this->items; i++) { + if (_tcscmp(this->fonts[i], font) == 0) return false; + } + + if (this->items == this->capacity) { + this->capacity += 10; + this->fonts = ReallocT(this->fonts, this->capacity); + } + + this->fonts[this->items++] = _tcsdup(font); + + return true; + } +}; + +struct EFCParam { + FreeTypeSettings *settings; + LOCALESIGNATURE locale; + MissingGlyphSearcher *callback; + FontList fonts; +}; + +static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXTMETRICEX *metric, DWORD type, LPARAM lParam) +{ + EFCParam *info = (EFCParam *)lParam; + + /* Skip duplicates */ + if (!info->fonts.Add((const TCHAR*)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 != 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; + } + + char font_name[MAX_PATH]; +#if defined(UNICODE) + WIDE_TO_MB_BUFFER((const TCHAR*)logfont->elfFullName, font_name, lengthof(font_name)); +#else + strecpy(font_name, (const TCHAR*)logfont->elfFullName, lastof(font_name)); +#endif + + /* Add english name after font name */ + const char *english_name = GetEnglishFontName(logfont); + strecpy(font_name + strlen(font_name) + 1, english_name, lastof(font_name)); + + /* Check whether we can actually load the font. */ + bool ft_init = _library != NULL; + 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 = NULL; + } + + if (!found) return 1; + + info->callback->SetFontNames(info->settings, font_name); + if (info->callback->FindMissingGlyphs(NULL)) return 1; + DEBUG(freetype, 1, "Fallback font: %s (%s)", font_name, english_name); + return 0; // stop enumerating +} + +bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid, MissingGlyphSearcher *callback) +{ + DEBUG(freetype, 1, "Trying fallback fonts"); + EFCParam langInfo; + if (GetLocaleInfo(MAKELCID(winlangid, SORT_DEFAULT), LOCALE_FONTSIGNATURE, (LPTSTR)&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; + langInfo.callback = callback; + + 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(__APPLE__) /* end ifdef Win32 */ +/* ======================================================================================== + * OSX support + * ======================================================================================== */ + +#include "os/macosx/macos.h" + +FT_Error GetFontByFaceName(const char *font_name, FT_Face *face) +{ + FT_Error err = FT_Err_Cannot_Open_Resource; + + /* Get font reference from name. */ + CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, font_name, kCFStringEncodingUTF8); + ATSFontRef font = ATSFontFindFromName(name, kATSOptionFlagsDefault); + CFRelease(name); + if (font == kInvalidFont) return err; + + /* Get a file system reference for the font. */ + FSRef ref; + OSStatus os_err = -1; +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + if (MacOSVersionIsAtLeast(10, 5, 0)) { + os_err = ATSFontGetFileReference(font, &ref); + } else +#endif + { +#if (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5) && !__LP64__ + /* This type was introduced with the 10.5 SDK. */ +#if (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5) + #define ATSFSSpec FSSpec +#endif + FSSpec spec; + os_err = ATSFontGetFileSpecification(font, (ATSFSSpec *)&spec); + if (os_err == noErr) os_err = FSpMakeFSRef(&spec, &ref); +#endif + } + + if (os_err == noErr) { + /* Get unix path for file. */ + UInt8 file_path[PATH_MAX]; + if (FSRefMakePath(&ref, file_path, sizeof(file_path)) == 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) +{ + const char *str; + bool result = false; + + callback->FindMissingGlyphs(&str); + +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + if (MacOSVersionIsAtLeast(10, 5, 0)) { + /* 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 if (strncmp(language_isocode, "ur", 2) == 0) { + /* The urdu alphabet is variant of persian. As OS X has no default + * font that advertises an urdu language code, search for persian + * support instead. */ + strecpy(lang, "fa", lastof(lang)); + } else { + /* Just copy the first part of the isocode. */ + strecpy(lang, language_isocode, lastof(lang)); + char *sep = strchr(lang, '_'); + if (sep != NULL) *sep = '\0'; + } + + CFStringRef lang_code; + lang_code = CFStringCreateWithCString(kCFAllocatorDefault, lang, kCFStringEncodingUTF8); + + /* Create a font iterator and iterate over all fonts that + * are available to the application. */ + ATSFontIterator itr; + ATSFontRef font; + ATSFontIteratorCreate(kATSFontContextLocal, NULL, NULL, kATSOptionFlagsUnRestrictedScope, &itr); + while (!result && ATSFontIteratorNext(itr, &font) == noErr) { + /* Get CoreText font handle. */ + CTFontRef font_ref = CTFontCreateWithPlatformFont(font, 0.0, NULL, NULL); + CFArrayRef langs = CTFontCopySupportedLanguages(font_ref); + if (langs != NULL) { + /* Font has a list of supported languages. */ + for (CFIndex i = 0; i < CFArrayGetCount(langs); i++) { + CFStringRef lang = (CFStringRef)CFArrayGetValueAtIndex(langs, i); + if (CFStringCompare(lang, lang_code, kCFCompareAnchored) == kCFCompareEqualTo) { + /* Lang code is supported by font, get full font name. */ + CFStringRef font_name = CTFontCopyFullName(font_ref); + char name[128]; + CFStringGetCString(font_name, name, lengthof(name), kCFStringEncodingUTF8); + CFRelease(font_name); + /* Skip some inappropriate or ugly looking fonts that have better alternatives. */ + if (strncmp(name, "Courier", 7) == 0 || strncmp(name, "Apple Symbols", 13) == 0 || + strncmp(name, ".Aqua", 5) == 0 || strncmp(name, "LastResort", 10) == 0 || + strncmp(name, "GB18030 Bitmap", 14) == 0) continue; + + /* Save result. */ + callback->SetFontNames(settings, name); + DEBUG(freetype, 2, "CT-Font for %s: %s", language_isocode, name); + result = true; + break; + } + } + CFRelease(langs); + } + CFRelease(font_ref); + } + ATSFontIteratorRelease(&itr); + CFRelease(lang_code); + } else +#endif + { +#if (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5) && !__LP64__ + /* Determine fallback font using ATSUI. This uses a string sample with + * missing characters. This is not failure-proof, but a better way like + * using the isocode as in the CoreText code path is not available. + * ATSUI was deprecated with 10.6 and is only partially available in + * 64-bit mode. */ + + /* Remove all control characters in the range from SCC_CONTROL_START to + * SCC_CONTROL_END as well as all ASCII < 0x20 from the string as it will + * mess with the automatic font detection */ + char buff[256]; // This length is enough to find a suitable replacement font + strecpy(buff, str, lastof(buff)); + str_validate(buff, lastof(buff), SVS_ALLOW_NEWLINE); + + /* Extract a UniChar representation of the sample string. */ + CFStringRef cf_str = CFStringCreateWithCString(kCFAllocatorDefault, buff, kCFStringEncodingUTF8); + if (cf_str == NULL) { + /* Something went wrong. Corrupt/invalid sample string? */ + return false; + } + CFIndex str_len = CFStringGetLength(cf_str); + UniChar string[str_len]; + CFStringGetCharacters(cf_str, CFRangeMake(0, str_len), string); + + /* Create a default text style with the default font. */ + ATSUStyle style; + ATSUCreateStyle(&style); + + /* Create a text layout object from the sample string using the text style. */ + UniCharCount run_len = kATSUToTextEnd; + ATSUTextLayout text_layout; + ATSUCreateTextLayoutWithTextPtr(string, kATSUFromTextBeginning, kATSUToTextEnd, str_len, 1, &run_len, &style, &text_layout); + + /* Try to match a font for the sample text. ATSUMatchFontsToText stops after + * it finds the first continuous character run not renderable with the currently + * selected font starting at offset. The matching needs to be repeated until + * the end of the string is reached to make sure the fallback font matches for + * all characters in the string and not only the first run. */ + UniCharArrayOffset offset = kATSUFromTextBeginning; + OSStatus os_err; + do { + ATSUFontID font; + UniCharCount run_len; + os_err = ATSUMatchFontsToText(text_layout, offset, kATSUToTextEnd, &font, &offset, &run_len); + if (os_err == kATSUFontsMatched) { + /* Found a better fallback font. Update the text layout + * object with the new font. */ + ATSUAttributeTag tag = kATSUFontTag; + ByteCount size = sizeof(font); + ATSUAttributeValuePtr val = &font; + ATSUSetAttributes(style, 1, &tag, &size, &val); + offset += run_len; + } + /* Exit if the end of the string is reached or some other error occurred. */ + } while (os_err == kATSUFontsMatched && offset < (UniCharArrayOffset)str_len); + + if (os_err == noErr || os_err == kATSUFontsMatched) { + /* ATSUMatchFontsToText exited normally. Extract font + * out of the text layout object. */ + ATSUFontID font; + ByteCount act_len; + ATSUGetAttribute(style, kATSUFontTag, sizeof(font), &font, &act_len); + + /* Get unique font name. The result is not a c-string, we have + * to leave space for a \0 and terminate it ourselves. */ + char name[128]; + ATSUFindFontName(font, kFontUniqueName, kFontNoPlatformCode, kFontNoScriptCode, kFontNoLanguageCode, 127, name, &act_len, NULL); + name[act_len > 127 ? 127 : act_len] = '\0'; + + /* Save Result. */ + callback->SetFontNames(settings, name); + DEBUG(freetype, 2, "ATSUI-Font for %s: %s", language_isocode, name); + result = true; + } + + ATSUDisposeTextLayout(text_layout); + ATSUDisposeStyle(style); + CFRelease(cf_str); +#endif + } + + if (result && strncmp(settings->medium.font, "Geeza Pro", 9) == 0) { + /* The font 'Geeza Pro' is often found for arabic characters, but + * it has the 'tiny' problem of not having any latin characters. + * 'Arial Unicode MS' on the other hand has arabic and latin glyphs, + * but seems to 'forget' to inform the OS about this fact. Manually + * substitute the latter for the former if it is loadable. */ + bool ft_init = _library != NULL; + FT_Face face; + /* Init FreeType if needed. */ + if ((ft_init || FT_Init_FreeType(&_library) == FT_Err_Ok) && GetFontByFaceName("Arial Unicode MS", &face) == FT_Err_Ok) { + FT_Done_Face(face); + callback->SetFontNames(settings, "Arial Unicode MS"); + DEBUG(freetype, 1, "Replacing font 'Geeza Pro' with 'Arial Unicode MS'"); + } + if (!ft_init) { + /* Uninit FreeType if we did the init. */ + FT_Done_FreeType(_library); + _library = NULL; + } + } + + callback->FindMissingGlyphs(NULL); + return result; +} + +#elif defined(WITH_FONTCONFIG) /* end ifdef __APPLE__ */ + +#include + +/* ======================================================================================== + * 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 = strdup(font_name); + font_style = strchr(font_family, ','); + if (font_style != NULL) { + 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 != NULL) FcPatternAddString(pat, FC_STYLE, (FcChar8*)font_style); + FcConfigSubstitute(0, pat, FcMatchPattern); + FcDefaultSubstitute(pat); + fs = FcFontSetCreate(); + match = FcFontMatch(0, pat, &result); + + if (fs != NULL && match != NULL) { + 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 != NULL && 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 != NULL) *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, NULL); + /* Get the list of filenames matching the wanted language. */ + FcFontSet *fs = FcFontList(NULL, pat, os); + + /* We don't need these anymore. */ + FcObjectSetDestroy(os); + FcPatternDestroy(pat); + + if (fs != NULL) { + int best_weight = -1; + const char *best_font = NULL; + + for (int i = 0; i < fs->nfont; i++) { + FcPattern *font = fs->fonts[i]; + + FcChar8 *file = NULL; + FcResult res = FcPatternGetString(font, FC_FILE, 0, &file); + if (res != FcResultMatch || file == NULL) { + 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(NULL); + DEBUG(freetype, 1, "Font \"%s\" misses%s glyphs", file, missing ? "" : " no"); + + if (!missing) { + best_weight = value; + best_font = (const char *)file; + } + } + + if (best_font != NULL) { + ret = true; + callback->SetFontNames(settings, best_font); + InitFreeType(callback->Monospace()); + } + + /* Clean up the list of filenames. */ + FcFontSetDestroy(fs); + } + + 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, int winlangid, MissingGlyphSearcher *callback) { return false; } +#endif /* WITH_FONTCONFIG */ + +#endif /* WITH_FREETYPE */ diff --git a/src/fontdetection.h b/src/fontdetection.h new file mode 100644 index 0000000000..edb961e6d3 --- /dev/null +++ b/src/fontdetection.h @@ -0,0 +1,44 @@ +/* $Id$ */ + +/* + * 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.h Functions related to detecting/finding the right font. */ + +#ifndef FONTDETECTION_H +#define FONTDETECTION_H + +#include "fontcache.h" + +#ifdef WITH_FREETYPE + +#include +#include FT_FREETYPE_H + +/** + * Load a freetype font face with the given font name. + * @param font_name The name of the font to load. + * @param face The face that has been found. + * @return The error we encountered. + */ +FT_Error GetFontByFaceName(const char *font_name, FT_Face *face); + +/** + * 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. + * @param callback The function to call to check for missing glyphs. + * @return true if a font has been set, false otherwise. + */ +bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid, class MissingGlyphSearcher *callback); + +#endif /* WITH_FREETYPE */ + +#endif diff --git a/src/gfx.cpp b/src/gfx.cpp index 9e4d07fcc6..f99c21baac 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -10,8 +10,7 @@ /** @file gfx.cpp Handling of drawing text and other gfx related stuff. */ #include "stdafx.h" -#include "gfx_func.h" -#include "fontcache.h" +#include "gfx_layout.h" #include "progress.h" #include "zoom_func.h" #include "blitter/factory.hpp" @@ -45,9 +44,6 @@ SwitchMode _switch_mode; ///< The next mainloop command. PauseModeByte _pause_mode; Palette _cur_palette; -static Dimension _max_char_size[FS_END]; ///< Cache of the maximum size of any character of a font. -static int _max_char_height; ///< Cache of the height of the largest font -static int _max_char_width; ///< Cache of the width of the largest font static byte _stringwidth_table[FS_END][224]; ///< Cache containing width of often used characters. @see GetCharacterWidth() DrawPixelInfo *_cur_dpi; byte _colour_gradient[COLOUR_END][8]; @@ -296,198 +292,74 @@ static void SetColourRemap(TextColour colour) _colour_remap_ptr = _string_colourremap; } -#if !defined(WITH_ICU) -static WChar *HandleBiDiAndArabicShapes(WChar *text) { return text; } -#else -#include -#include -#include - /** - * Function to be able to handle right-to-left text and Arabic chars properly. - * - * First: right-to-left (RTL) is stored 'logically' in almost all applications - * and so do we. This means that their text is stored from right to the - * left in memory and any non-RTL text (like numbers or English) are - * then stored from left-to-right. When we want to actually draw the - * text we need to reverse the RTL text in memory, which is what - * happens in ubidi_writeReordered. - * Second: Arabic characters "differ" based on their context. To draw the - * correct variant we pass it through u_shapeArabic. This function can - * add or remove some characters. This is the reason for the lastof - * so we know till where we can fill the output. - * - * Sadly enough these functions work with a custom character format, UChar, - * which isn't the same size as WChar. Because of that we need to transform - * our text first to UChars and then back to something we can use. - * - * To be able to truncate strings properly you must truncate before passing to - * this function. This way the logical begin of the string remains and the end - * gets chopped of instead of the other way around. - * - * The reshaping of Arabic characters might increase or decrease the width of - * the characters/string. So it might still overflow after truncation, though - * the chance is fairly slim as most characters get shorter instead of longer. - * @param buffer the buffer to read from/to - * @param lastof the end of the buffer - * @return the buffer to draw from - */ -static WChar *HandleBiDiAndArabicShapes(WChar *buffer) -{ - UChar input[DRAW_STRING_BUFFER]; - UChar intermediate[DRAW_STRING_BUFFER]; - static WChar output[DRAW_STRING_BUFFER]; - - /* Transform from UTF-32 to internal ICU format of UTF-16. */ - UErrorCode err = U_ZERO_ERROR; - int32_t length = 0; - u_strFromUTF32(input, lengthof(input), &length, (UChar32 *)buffer, -1, &err); - if (U_FAILURE(err)) return buffer; - - UBiDi *para = ubidi_openSized(length, 0, &err); - if (para == NULL) return buffer; - - ubidi_setPara(para, input, length, _current_text_dir == TD_RTL ? UBIDI_DEFAULT_RTL : UBIDI_DEFAULT_LTR, NULL, &err); - length = ubidi_writeReordered(para, intermediate, lengthof(intermediate), UBIDI_REMOVE_BIDI_CONTROLS, &err); - length = u_shapeArabic(intermediate, length, input, lengthof(input), U_SHAPE_TEXT_DIRECTION_VISUAL_LTR | U_SHAPE_LETTERS_SHAPE, &err); - ubidi_close(para); - if (U_FAILURE(err)) return buffer; - - /* Transform back to UTF-32. */ - u_strToUTF32((UChar32 *)output, lengthof(output), NULL, input, length, &err); - if (U_FAILURE(err)) return buffer; - - /* u_strToUTF32 doesn't add a NUL charcter if the buffer is too small, be safe. */ - output[lengthof(output) - 1] = '\0'; - return output; -} -#endif /* WITH_ICU */ - - -/** - * Truncate a given string to a maximum width if necessary. - * If the string is truncated, add three dots ('...') to show this. - * @param *str string that is checked and possibly truncated - * @param maxw maximum width in pixels of the string - * @param start_fontsize Fontsize to start the text with - * @return new width of (truncated) string - */ -static int TruncateString(char *str, int maxw, FontSize start_fontsize) -{ - int w = 0; - FontSize size = start_fontsize; - int ddd, ddd_w; - - WChar c; - char *ddd_pos; - - ddd_w = ddd = GetCharacterWidth(size, '.') * 3; - - for (ddd_pos = str; (c = Utf8Consume(const_cast(&str))) != '\0'; ) { - if (IsPrintable(c) && !IsTextDirectionChar(c)) { - w += GetCharacterWidth(size, c); - - if (w > maxw) { - /* string got too big... insert dotdotdot, but make sure we do not - * print anything beyond the string termination character. */ - for (int i = 0; *ddd_pos != '\0' && i < 3; i++, ddd_pos++) *ddd_pos = '.'; - *ddd_pos = '\0'; - return ddd_w; - } - } else { - if (c == SCC_TINYFONT) { - size = FS_SMALL; - ddd = GetCharacterWidth(size, '.') * 3; - } else if (c == SCC_BIGFONT) { - size = FS_LARGE; - ddd = GetCharacterWidth(size, '.') * 3; - } else if (c == '\n') { - DEBUG(misc, 0, "Drawing string using newlines with DrawString instead of DrawStringMultiLine. Please notify the developers of this: [%s]", str); - } - } - - /* Remember the last position where three dots fit. */ - if (w + ddd < maxw) { - ddd_w = w + ddd; - ddd_pos = str; - } - } - - return w; -} - -static int ReallyDoDrawString(const WChar *string, int x, int y, DrawStringParams ¶ms, bool parse_string_also_when_clipped = false); - -/** - * Get the real width of the string. - * @param str the string to draw - * @param start_fontsize Fontsize to start the text with - * @return the width. - */ -static int GetStringWidth(const WChar *str, FontSize start_fontsize) -{ - FontSize size = start_fontsize; - int max_width; - int width; - WChar c; - - width = max_width = 0; - for (;;) { - c = *str++; - if (c == 0) break; - if (IsPrintable(c) && !IsTextDirectionChar(c)) { - width += GetCharacterWidth(size, c); - } else { - switch (c) { - case SCC_TINYFONT: size = FS_SMALL; break; - case SCC_BIGFONT: size = FS_LARGE; break; - case '\n': - max_width = max(max_width, width); - break; - } - } - } - - return max(max_width, width); -} - -/** - * Draw string, possibly truncated to make it fit in its allocated space - * - * @param left The left most position to draw on. - * @param right The right most position to draw on. - * @param top The top most position to draw on. - * @param str String to draw. - * @param last The end of the string buffer to draw. - * @param params Text drawing parameters. - * @param align The alignment of the string when drawing left-to-right. In the - * case a right-to-left language is chosen this is inverted so it - * will be drawn in the right direction. + * Drawing routine for drawing a laid out line of text. + * @param line String to draw. + * @param y The top most position to draw on. + * @param left The left most position to draw on. + * @param right The right most position to draw on. + * @param align The alignment of the string when drawing left-to-right. In the + * case a right-to-left language is chosen this is inverted so it + * will be drawn in the right direction. * @param underline Whether to underline what has been drawn or not. - * @param truncate Whether to truncate the string or not. * * @return In case of left or center alignment the right most pixel we have drawn to. * In case of right alignment the left most pixel we have drawn to. */ -static int DrawString(int left, int right, int top, char *str, const char *last, DrawStringParams ¶ms, StringAlignment align, bool underline = false, bool truncate = true) +static int DrawLayoutLine(ParagraphLayout::Line *line, int y, int left, int right, StringAlignment align, bool underline) { - if (truncate) TruncateString(str, right - left + 1, params.fontsize); + if (line->countRuns() == 0) return 0; - WChar draw_buffer[DRAW_STRING_BUFFER]; - WChar *p = draw_buffer; + int w = line->getWidth(); + int h = line->getLeading(); - const char *text = str; - for (WChar c = Utf8Consume(&text); c != '\0'; c = Utf8Consume(&text)) { - *p++ = c; + /* + * The following is needed for truncation. + * Depending on the text direction, we either remove bits at the rear + * or the front. For this we shift the entire area to draw so it fits + * within the left/right bounds and the side we do not truncate it on. + * Then we determine the truncation location, i.e. glyphs that fall + * outside of the range min_x - max_x will not be drawn; they are thus + * the truncated glyphs. + * + * At a later step we insert the dots. + */ + + int max_w = right - left + 1; // The maximum width. + + int offset_x = 0; // The offset we need for positioning the glyphs + int min_x = left; // The minimum x position to draw normal glyphs on. + int max_x = right; // The maximum x position to draw normal glyphs on. + + bool truncation = max_w < w; // Whether we need to do truncation. + int dot_width = 0; // Cache for the width of the dot. + const Sprite *dot_sprite = NULL; // Cache for the sprite of the dot. + + if (truncation) { + /* + * Assumption may be made that all fonts of a run are of the same size. + * In any case, we'll use these dots for the abbreviation, so even if + * another size would be chosen it won't have truncated too little for + * the truncation dots. + */ + FontCache *fc = ((const Font*)line->getVisualRun(0)->getFont())->fc; + GlyphID dot_glyph = fc->MapCharToGlyph('.'); + dot_width = fc->GetGlyphWidth(dot_glyph); + dot_sprite = fc->GetGlyph(dot_glyph); + + if (_current_text_dir == TD_RTL) { + min_x += 3 * dot_width; + offset_x = w - 3 * dot_width - max_w; + } else { + max_x -= 3 * dot_width; + } + + w = max_w; } - *p++ = '\0'; /* In case we have a RTL language we swap the alignment. */ if (!(align & SA_FORCE) && _current_text_dir == TD_RTL && (align & SA_HOR_MASK) != SA_HOR_CENTER) align ^= SA_RIGHT; - WChar *to_draw = HandleBiDiAndArabicShapes(draw_buffer); - int w = GetStringWidth(to_draw, params.fontsize); - /* right is the right most position to draw on. In this case we want to do * calculations with the width of the string. In comparison right can be * seen as lastof(todraw) and width as lengthof(todraw). They differ by 1. @@ -513,9 +385,55 @@ static int DrawString(int left, int right, int top, char *str, const char *last, NOT_REACHED(); } - ReallyDoDrawString(to_draw, left, top, params, !truncate); + for (int run_index = 0; run_index < line->countRuns(); run_index++) { + const ParagraphLayout::VisualRun *run = line->getVisualRun(run_index); + const Font *f = (const Font*)run->getFont(); + + FontCache *fc = f->fc; + TextColour colour = f->colour; + SetColourRemap(colour); + + DrawPixelInfo *dpi = _cur_dpi; + int dpi_left = dpi->left; + int dpi_right = dpi->left + dpi->width - 1; + + bool draw_shadow = fc->GetDrawGlyphShadow() && colour != TC_BLACK; + + for (int i = 0; i < run->getGlyphCount(); i++) { + GlyphID glyph = run->getGlyphs()[i]; + + /* Not a valid glyph (empty) */ + if (glyph == 0xFFFF) continue; + + int begin_x = run->getPositions()[i * 2] + left - offset_x; + int end_x = run->getPositions()[i * 2 + 2] + left - offset_x - 1; + int top = run->getPositions()[i * 2 + 1] + y; + + /* Truncated away. */ + if (truncation && (begin_x < min_x || end_x > max_x)) continue; + + const Sprite *sprite = fc->GetGlyph(glyph); + /* Check clipping (the "+ 1" is for the shadow). */ + if (begin_x + sprite->x_offs > dpi_right || begin_x + sprite->x_offs + sprite->width /* - 1 + 1 */ < dpi_left) continue; + + if (draw_shadow && (glyph & SPRITE_GLYPH) == 0) { + SetColourRemap(TC_BLACK); + GfxMainBlitter(sprite, begin_x + 1, top + 1, BM_COLOUR_REMAP); + SetColourRemap(colour); + } + GfxMainBlitter(sprite, begin_x, top, BM_COLOUR_REMAP); + } + } + + if (truncation) { + int x = (_current_text_dir == TD_RTL) ? left : (right - 3 * dot_width); + for (int i = 0; i < 3; i++, x += dot_width) { + GfxMainBlitter(dot_sprite, x, y, BM_COLOUR_REMAP); + } + } + if (underline) { - GfxFillRect(left, top + FONT_HEIGHT_NORMAL, right, top + FONT_HEIGHT_NORMAL, _string_colourremap[1]); + GfxFillRect(left, y + h, right, y + h, _string_colourremap[1]); } return (align & SA_HOR_MASK) == SA_RIGHT ? left : right; @@ -537,10 +455,10 @@ static int DrawString(int left, int right, int top, char *str, const char *last, */ int DrawString(int left, int right, int top, const char *str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize) { - char buffer[DRAW_STRING_BUFFER]; - strecpy(buffer, str, lastof(buffer)); - DrawStringParams params(colour, fontsize); - return DrawString(left, right, top, buffer, lastof(buffer), params, align, underline); + Layouter layout(str, INT32_MAX, colour, fontsize); + if (layout.Length() == 0) return 0; + + return DrawLayoutLine(*layout.Begin(), top, left, right, align, underline); } /** @@ -561,145 +479,21 @@ int DrawString(int left, int right, int top, StringID str, TextColour colour, St { char buffer[DRAW_STRING_BUFFER]; GetString(buffer, str, lastof(buffer)); - DrawStringParams params(colour, fontsize); - return DrawString(left, right, top, buffer, lastof(buffer), params, align, underline); + return DrawString(left, right, top, buffer, colour, align, underline, fontsize); } /** - * 'Correct' a string to a maximum length. Longer strings will be cut into - * additional lines at whitespace characters if possible. The string parameter - * is modified with terminating characters mid-string which are the - * placeholders for the newlines. - * The string WILL be truncated if there was no whitespace for the current - * line's maximum width. - * - * @note To know if the terminating '\0' is the string end or just a - * newline, the returned 'num' value should be consulted. The num'th '\0', - * starting with index 0 is the real string end. - * - * @param str string to check and correct for length restrictions - * @param last the last valid location (for '\0') in the buffer of str - * @param maxw the maximum width the string can have on one line - * @param size Fontsize to start the text with - * @return return a 32bit wide number consisting of 2 packed values: - * 0 - 15 the number of lines ADDED to the string - * 16 - 31 the fontsize in which the length calculation was done at - */ -uint32 FormatStringLinebreaks(char *str, const char *last, int maxw, FontSize size) -{ - int num = 0; - - assert(maxw > 0); - - for (;;) { - /* The character *after* the last space. */ - char *last_space = NULL; - int w = 0; - - for (;;) { - WChar c = Utf8Consume(const_cast(&str)); - /* whitespace is where we will insert the line-break */ - if (IsWhitespace(c)) last_space = str; - - if (IsPrintable(c) && !IsTextDirectionChar(c)) { - int char_w = GetCharacterWidth(size, c); - w += char_w; - if (w > maxw) { - /* The string is longer than maximum width so we need to decide - * what to do with it. */ - if (w == char_w) { - /* The character is wider than allowed width; don't know - * what to do with this case... bail out! */ - return num + (size << 16); - } - if (last_space == NULL) { - /* No space has been found. Just terminate at our current - * location. This usually happens for languages that do not - * require spaces in strings, like Chinese, Japanese and - * Korean. For other languages terminating mid-word might - * not be the best, but terminating the whole string instead - * of continuing the word at the next line is worse. */ - str = Utf8PrevChar(str); - size_t len = strlen(str); - char *terminator = str + len; - - /* The string location + length of the string + 1 for '\0' - * always fits; otherwise there's no trailing '\0' and it - * it not a valid string. */ - assert(terminator <= last); - assert(*terminator == '\0'); - - /* If the string is too long we have to terminate it earlier. */ - if (terminator == last) { - /* Get the 'begin' of the previous character and make that - * the terminator of the string; we truncate it 'early'. */ - *Utf8PrevChar(terminator) = '\0'; - len = strlen(str); - } - /* Also move the terminator! */ - memmove(str + 1, str, len + 1); - *str = '\0'; - /* str needs to point to the character *after* the last space */ - str++; - } else { - /* A space is found; perfect place to terminate */ - str = last_space; - } - break; - } - } else { - switch (c) { - case '\0': return num + (size << 16); - case SCC_TINYFONT: size = FS_SMALL; break; - case SCC_BIGFONT: size = FS_LARGE; break; - case '\n': goto end_of_inner_loop; - } - } - } -end_of_inner_loop: - /* String didn't fit on line (or a '\n' was encountered), so 'dummy' terminate - * and increase linecount. We use Utf8PrevChar() as also non 1 char long - * whitespace separators are supported */ - num++; - char *s = Utf8PrevChar(str); - *s++ = '\0'; - - /* In which case (see above) we will shift remainder to left and close the gap */ - if (str - s >= 1) { - for (; str[-1] != '\0';) *s++ = *str++; - } - } -} - - -/** - * Calculates height of string (in pixels). Accepts multiline string with '\0' as separators. - * @param src string to check - * @param num number of extra lines (output of FormatStringLinebreaks()) - * @param start_fontsize Fontsize to start the text with - * @note assumes text won't be truncated. FormatStringLinebreaks() is a good way to ensure that. + * Calculates height of string (in pixels). The string is changed to a multiline string if needed. + * @param str string to check + * @param maxw maximum string width * @return height of pixels of string when it is drawn */ -static int GetMultilineStringHeight(const char *src, int num, FontSize start_fontsize) +static int GetStringHeight(const char *str, int maxw) { - int maxy = 0; - int y = 0; - int fh = GetCharacterHeight(start_fontsize); - - for (;;) { - WChar c = Utf8Consume(&src); - - switch (c) { - case 0: y += fh; if (--num < 0) return maxy; break; - case '\n': y += fh; break; - case SCC_TINYFONT: fh = GetCharacterHeight(FS_SMALL); break; - case SCC_BIGFONT: fh = GetCharacterHeight(FS_LARGE); break; - default: maxy = max(maxy, y + fh); break; - } - } + Layouter layout(str, maxw); + return layout.GetBounds().height; } - /** * Calculates height of string (in pixels). The string is changed to a multiline string if needed. * @param str string to check @@ -709,12 +503,23 @@ static int GetMultilineStringHeight(const char *src, int num, FontSize start_fon int GetStringHeight(StringID str, int maxw) { char buffer[DRAW_STRING_BUFFER]; + GetString(buffer, str, lastof(buffer)); + return GetStringHeight(buffer, maxw); +} +/** + * Calculates number of lines of string. The string is changed to a multiline string if needed. + * @param str string to check + * @param maxw maximum string width + * @return number of lines of string when it is drawn + */ +int GetStringLineCount(StringID str, int maxw) +{ + char buffer[DRAW_STRING_BUFFER]; GetString(buffer, str, lastof(buffer)); - uint32 tmp = FormatStringLinebreaks(buffer, lastof(buffer), maxw); - - return GetMultilineStringHeight(buffer, GB(tmp, 0, 16), FS_NORMAL); + Layouter layout(buffer, maxw); + return layout.Length(); } /** @@ -729,24 +534,6 @@ Dimension GetStringMultiLineBoundingBox(StringID str, const Dimension &suggestio return box; } - -/** - * Calculates height of string (in pixels). The string is changed to a multiline string if needed. - * @param str string to check - * @param maxw maximum string width - * @return height of pixels of string when it is drawn - */ -int GetStringHeight(const char *str, int maxw) -{ - char buffer[DRAW_STRING_BUFFER]; - - strecpy(buffer, str, lastof(buffer)); - - uint32 tmp = FormatStringLinebreaks(buffer, lastof(buffer), maxw); - - return GetMultilineStringHeight(buffer, GB(tmp, 0, 16), FS_NORMAL); -} - /** * Calculate string bounding box for multi-line strings. * @param str String to check. @@ -767,7 +554,6 @@ Dimension GetStringMultiLineBoundingBox(const char *str, const Dimension &sugges * @param top The top most position to draw on. * @param bottom The bottom most position to draw on. * @param str String to draw. - * @param last The end of the string buffer to draw. * @param colour Colour used for drawing the string, see DoDrawString() for details * @param align The horizontal and vertical alignment of the string. * @param underline Whether to underline all strings @@ -775,7 +561,7 @@ Dimension GetStringMultiLineBoundingBox(const char *str, const Dimension &sugges * * @return If \a align is #SA_BOTTOM, the top to where we have written, else the bottom to where we have written. */ -static int DrawStringMultiLine(int left, int right, int top, int bottom, char *str, const char *last, TextColour colour, StringAlignment align, bool underline, FontSize fontsize) +int DrawStringMultiLine(int left, int right, int top, int bottom, const char *str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize) { int maxw = right - left + 1; int maxh = bottom - top + 1; @@ -784,25 +570,8 @@ static int DrawStringMultiLine(int left, int right, int top, int bottom, char *s * do we really want to support fonts of 0 or less pixels high? */ if (maxh <= 0) return top; - uint32 tmp = FormatStringLinebreaks(str, last, maxw); - int num = GB(tmp, 0, 16) + 1; - - int mt = GetCharacterHeight((FontSize)GB(tmp, 16, 16)); - int total_height = num * mt; - - int skip_lines = 0; - if (total_height > maxh) { - if (maxh < mt) return top; // Not enough room for a single line. - if ((align & SA_VERT_MASK) == SA_BOTTOM) { - skip_lines = num; - num = maxh / mt; - skip_lines -= num; - } else { - num = maxh / mt; - } - total_height = num * mt; - } - + Layouter layout(str, maxw, colour, fontsize); + int total_height = layout.GetBounds().height; int y; switch (align & SA_VERT_MASK) { case SA_TOP: @@ -820,62 +589,23 @@ static int DrawStringMultiLine(int left, int right, int top, int bottom, char *s default: NOT_REACHED(); } - const char *src = str; - DrawStringParams params(colour, fontsize); - int written_top = bottom; // Uppermost position of rendering a line of text - for (;;) { - if (skip_lines == 0) { - char buf2[DRAW_STRING_BUFFER]; - strecpy(buf2, src, lastof(buf2)); - DrawString(left, right, y, buf2, lastof(buf2), params, align, underline, false); - if (written_top > y) written_top = y; - y += mt; - num--; - } + int last_line = top; + int first_line = bottom; - for (;;) { - WChar c = Utf8Consume(&src); - if (c == 0) { - break; - } else if (skip_lines > 0) { - /* Skipped drawing, so do additional processing to update params. */ - if (c >= SCC_BLUE && c <= SCC_BLACK) { - params.SetColour((TextColour)(c - SCC_BLUE)); - } else if (c == SCC_PREVIOUS_COLOUR) { // Revert to the previous colour. - params.SetPreviousColour(); - } else if (c == SCC_TINYFONT) { - params.SetFontSize(FS_SMALL); - } else if (c == SCC_BIGFONT) { - params.SetFontSize(FS_LARGE); - } + for (ParagraphLayout::Line **iter = layout.Begin(); iter != layout.End(); iter++) { + ParagraphLayout::Line *line = *iter; - } + int line_height = line->getLeading(); + if (y >= top && y < bottom) { + last_line = y + line_height; + if (first_line > y) first_line = y; + + DrawLayoutLine(line, y, left, right, align, underline); } - if (skip_lines > 0) skip_lines--; - if (num == 0) return ((align & SA_VERT_MASK) == SA_BOTTOM) ? written_top : y; + y += line_height; } -} -/** - * Draw string, possibly over multiple lines. - * - * @param left The left most position to draw on. - * @param right The right most position to draw on. - * @param top The top most position to draw on. - * @param bottom The bottom most position to draw on. - * @param str String to draw. - * @param colour Colour used for drawing the string, see DoDrawString() for details - * @param align The horizontal and vertical alignment of the string. - * @param underline Whether to underline all strings - * @param fontsize The size of the initial characters. - * - * @return If \a align is #SA_BOTTOM, the top to where we have written, else the bottom to where we have written. - */ -int DrawStringMultiLine(int left, int right, int top, int bottom, const char *str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize) -{ - char buffer[DRAW_STRING_BUFFER]; - strecpy(buffer, str, lastof(buffer)); - return DrawStringMultiLine(left, right, top, bottom, buffer, lastof(buffer), colour, align, underline, fontsize); + return ((align & SA_VERT_MASK) == SA_BOTTOM) ? first_line : last_line; } /** @@ -897,7 +627,7 @@ int DrawStringMultiLine(int left, int right, int top, int bottom, StringID str, { char buffer[DRAW_STRING_BUFFER]; GetString(buffer, str, lastof(buffer)); - return DrawStringMultiLine(left, right, top, bottom, buffer, lastof(buffer), colour, align, underline, fontsize); + return DrawStringMultiLine(left, right, top, bottom, buffer, colour, align, underline, fontsize); } /** @@ -912,33 +642,8 @@ int DrawStringMultiLine(int left, int right, int top, int bottom, StringID str, */ Dimension GetStringBoundingBox(const char *str, FontSize start_fontsize) { - FontSize size = start_fontsize; - Dimension br; - uint max_width; - WChar c; - - br.width = br.height = max_width = 0; - for (;;) { - c = Utf8Consume(&str); - if (c == 0) break; - if (IsPrintable(c) && !IsTextDirectionChar(c)) { - br.width += GetCharacterWidth(size, c); - } else { - switch (c) { - case SCC_TINYFONT: size = FS_SMALL; break; - case SCC_BIGFONT: size = FS_LARGE; break; - case '\n': - br.height += GetCharacterHeight(size); - if (br.width > max_width) max_width = br.width; - br.width = 0; - break; - } - } - } - br.height += GetCharacterHeight(size); - - br.width = max(br.width, max_width); - return br; + Layouter layout(str, INT32_MAX, TC_FROMSTRING, start_fontsize); + return layout.GetBounds(); } /** @@ -968,86 +673,6 @@ void DrawCharCentered(WChar c, int x, int y, TextColour colour) GfxMainBlitter(GetGlyph(FS_NORMAL, c), x - GetCharacterWidth(FS_NORMAL, c) / 2, y, BM_COLOUR_REMAP); } -/** - * Draw a string at the given coordinates with the given colour. - * While drawing the string, parse it in case some formatting is specified, - * like new colour, new size or even positioning. - * @param string The string to draw. This is already bidi reordered. - * @param x Offset from left side of the screen - * @param y Offset from top side of the screen - * @param params Text drawing parameters - * @param parse_string_also_when_clipped - * By default, always test the available space where to draw the string. - * When in multiline drawing, it would already be done, - * so no need to re-perform the same kind (more or less) of verifications. - * It's not only an optimisation, it's also a way to ensures the string will be parsed - * (as there are certain side effects on global variables, which are important for the next line) - * @return the x-coordinates where the drawing has finished. - * If nothing is drawn, the originally passed x-coordinate is returned - */ -static int ReallyDoDrawString(const WChar *string, int x, int y, DrawStringParams ¶ms, bool parse_string_also_when_clipped) -{ - DrawPixelInfo *dpi = _cur_dpi; - bool draw_shadow = GetDrawGlyphShadow(); - WChar c; - int xo = x; - - if (!parse_string_also_when_clipped) { - /* in "mode multiline", the available space have been verified. Not in regular one. - * So if the string cannot be drawn, return the original start to say so.*/ - if (x >= dpi->left + dpi->width || y >= dpi->top + dpi->height) return x; - } - -switch_colour:; - SetColourRemap(params.cur_colour); - -check_bounds: - if (y + _max_char_height <= dpi->top || dpi->top + dpi->height <= y) { -skip_char:; - for (;;) { - c = *string++; - if (!IsPrintable(c)) goto skip_cont; - } - } - - for (;;) { - c = *string++; -skip_cont:; - if (c == 0) { - return x; // Nothing more to draw, get out. And here is the new x position - } - if (IsPrintable(c) && !IsTextDirectionChar(c)) { - if (x >= dpi->left + dpi->width) goto skip_char; - if (x + _max_char_width >= dpi->left) { - const Sprite *glyph = GetGlyph(params.fontsize, c); - if (draw_shadow && params.fontsize == FS_NORMAL && params.cur_colour != TC_BLACK && !(c >= SCC_SPRITE_START && c <= SCC_SPRITE_END)) { - SetColourRemap(TC_BLACK); - GfxMainBlitter(glyph, x + 1, y + 1, BM_COLOUR_REMAP); - SetColourRemap(params.cur_colour); - } - GfxMainBlitter(glyph, x, y, BM_COLOUR_REMAP); - } - x += GetCharacterWidth(params.fontsize, c); - } else if (c == '\n') { // newline = {} - x = xo; // We require a new line, so the x coordinate is reset - y += GetCharacterHeight(params.fontsize); - goto check_bounds; - } else if (c >= SCC_BLUE && c <= SCC_BLACK) { // change colour? - params.SetColour((TextColour)(c - SCC_BLUE)); - goto switch_colour; - } else if (c == SCC_PREVIOUS_COLOUR) { // revert to the previous colour - params.SetPreviousColour(); - goto switch_colour; - } else if (c == SCC_TINYFONT) { // {TINYFONT} - params.SetFontSize(FS_SMALL); - } else if (c == SCC_BIGFONT) { // {BIGFONT} - params.SetFontSize(FS_LARGE); - } else if (!IsTextDirectionChar(c)) { - DEBUG(misc, 0, "[utf8] unknown string command character %d", c); - } - } -} - /** * Get the size of a sprite. * @param sprid Sprite to examine. @@ -1462,23 +1087,9 @@ TextColour GetContrastColour(uint8 background) void LoadStringWidthTable(bool monospace) { for (FontSize fs = monospace ? FS_MONO : FS_BEGIN; fs < (monospace ? FS_END : FS_MONO); fs++) { - _max_char_size[fs].width = 0; - _max_char_size[fs].height = GetCharacterHeight(fs); for (uint i = 0; i != 224; i++) { _stringwidth_table[fs][i] = GetGlyphWidth(fs, i + 32); - _max_char_size[fs].width = max(_max_char_size[fs].width, _stringwidth_table[fs][i]); } - - /* Needed because they need to be 1 more than the widest. */ - _max_char_size[fs].width++; - _max_char_size[fs].height++; - } - - _max_char_width = 0; - _max_char_height = 0; - for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) { - _max_char_width = max(_max_char_width, _max_char_size[fs].width); - _max_char_height = max(_max_char_height, _max_char_size[fs].height); } ReInitAllWindows(); diff --git a/src/gfx_func.h b/src/gfx_func.h index 8b4c9eb557..6ec36ee1b1 100644 --- a/src/gfx_func.h +++ b/src/gfx_func.h @@ -121,8 +121,8 @@ void DrawBox(int x, int y, int dx1, int dy1, int dx2, int dy2, int dx3, int dy3) Dimension GetStringBoundingBox(const char *str, FontSize start_fontsize = FS_NORMAL); Dimension GetStringBoundingBox(StringID strid); -uint32 FormatStringLinebreaks(char *str, const char *last, int maxw, FontSize start_fontsize = FS_NORMAL); int GetStringHeight(StringID str, int maxw); +int GetStringLineCount(StringID str, int maxw); Dimension GetStringMultiLineBoundingBox(StringID str, const Dimension &suggestion); Dimension GetStringMultiLineBoundingBox(const char *str, const Dimension &suggestion); void LoadStringWidthTable(bool monospace = false); @@ -151,17 +151,7 @@ byte GetCharacterWidth(FontSize size, uint32 key); byte GetDigitWidth(FontSize size = FS_NORMAL); void GetBroadestDigit(uint *front, uint *next, FontSize size = FS_NORMAL); -/** - * Get height of a character for a given font size. - * @param size Font size to get height of - * @return Height of characters in the given font (pixels) - */ -static inline byte GetCharacterHeight(FontSize size) -{ - assert(size < FS_END); - extern int _font_height[FS_END]; - return _font_height[size]; -} +int GetCharacterHeight(FontSize size); /** Height of characters in the small (#FS_SMALL) font. */ #define FONT_HEIGHT_SMALL (GetCharacterHeight(FS_SMALL)) diff --git a/src/gfx_layout.cpp b/src/gfx_layout.cpp new file mode 100644 index 0000000000..69b3077f27 --- /dev/null +++ b/src/gfx_layout.cpp @@ -0,0 +1,489 @@ +/* $Id$ */ + +/* + * 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 gfx_layout.cpp Handling of laying out text. */ + +#include "stdafx.h" +#include "gfx_layout.h" +#include "string_func.h" +#include "strings_func.h" + +#include "table/control_codes.h" + +#ifdef WITH_ICU +#include +#endif /* WITH_ICU */ + +/** + * Construct a new font. + * @param size The font size to use for this font. + * @param colour The colour to draw this font in. + */ +Font::Font(FontSize size, TextColour colour) : + fc(FontCache::Get(size)), colour(colour) +{ + assert(size < FS_END); +} + +#ifdef WITH_ICU +/* Implementation details of LEFontInstance */ + +le_int32 Font::getUnitsPerEM() const +{ + return this->fc->GetUnitsPerEM(); +} + +le_int32 Font::getAscent() const +{ + return this->fc->GetAscender(); +} + +le_int32 Font::getDescent() const +{ + return -this->fc->GetDescender(); +} + +le_int32 Font::getLeading() const +{ + return this->fc->GetHeight(); +} + +float Font::getXPixelsPerEm() const +{ + return (float)this->fc->GetHeight(); +} + +float Font::getYPixelsPerEm() const +{ + return (float)this->fc->GetHeight(); +} + +float Font::getScaleFactorX() const +{ + return 1.0f; +} + +float Font::getScaleFactorY() const +{ + return 1.0f; +} + +const void *Font::getFontTable(LETag tableTag) const +{ + size_t length; + return this->getFontTable(tableTag, length); +} + +const void *Font::getFontTable(LETag tableTag, size_t &length) const +{ + return this->fc->GetFontTable(tableTag, length); +} + +LEGlyphID Font::mapCharToGlyph(LEUnicode32 ch) const +{ + if (IsTextDirectionChar(ch)) return 0; + return this->fc->MapCharToGlyph(ch); +} + +void Font::getGlyphAdvance(LEGlyphID glyph, LEPoint &advance) const +{ + advance.fX = glyph == 0xFFFF ? 0 : this->fc->GetGlyphWidth(glyph); + advance.fY = 0; +} + +le_bool Font::getGlyphPoint(LEGlyphID glyph, le_int32 pointNumber, LEPoint &point) const +{ + return FALSE; +} + +size_t Layouter::AppendToBuffer(UChar *buff, const UChar *buffer_last, WChar c) +{ + /* Transform from UTF-32 to internal ICU format of UTF-16. */ + int32 length = 0; + UErrorCode err = U_ZERO_ERROR; + u_strFromUTF32(buff, buffer_last - buff, &length, (UChar32*)&c, 1, &err); + return length; +} + +ParagraphLayout *Layouter::GetParagraphLayout(UChar *buff, UChar *buff_end, FontMap &fontMapping) +{ + int32 length = buff_end - buff; + + if (length == 0) { + /* ICU's ParagraphLayout cannot handle empty strings, so fake one. */ + buff[0] = ' '; + length = 1; + fontMapping.End()[-1].first++; + } + + /* Fill ICU's FontRuns with the right data. */ + FontRuns runs(fontMapping.Length()); + for (FontMap::iterator iter = fontMapping.Begin(); iter != fontMapping.End(); iter++) { + runs.add(iter->second, iter->first); + } + + LEErrorCode status = LE_NO_ERROR; + return new ParagraphLayout(buff, length, &runs, NULL, NULL, NULL, _current_text_dir == TD_RTL ? UBIDI_DEFAULT_RTL : UBIDI_DEFAULT_LTR, false, status); +} + +#else /* WITH_ICU */ + +/*** Paragraph layout ***/ + +/** + * Create the visual run. + * @param font The font to use for this run. + * @param chars The characters to use for this run. + * @param char_count The number of characters in this run. + * @param x The initial x position for this run. + */ +ParagraphLayout::VisualRun::VisualRun(Font *font, const WChar *chars, int char_count, int x) : + font(font), glyph_count(char_count) +{ + this->glyphs = MallocT(this->glyph_count); + + /* Positions contains the location of the begin of each of the glyphs, and the end of the last one. */ + this->positions = MallocT(this->glyph_count * 2 + 2); + this->positions[0] = x; + this->positions[1] = 0; + + for (int i = 0; i < this->glyph_count; i++) { + this->glyphs[i] = font->fc->MapCharToGlyph(chars[i]); + this->positions[2 * i + 2] = this->positions[2 * i] + font->fc->GetGlyphWidth(this->glyphs[i]); + this->positions[2 * i + 3] = 0; + } +} + +/** Free all data. */ +ParagraphLayout::VisualRun::~VisualRun() +{ + free(this->positions); + free(this->glyphs); +} + +/** + * Get the font associated with this run. + * @return The font. + */ +Font *ParagraphLayout::VisualRun::getFont() const +{ + return this->font; +} + +/** + * Get the number of glyhps in this run. + * @return The number of glyphs. + */ +int ParagraphLayout::VisualRun::getGlyphCount() const +{ + return this->glyph_count; +} + +/** + * Get the glyhps of this run. + * @return The glyphs. + */ +const GlyphID *ParagraphLayout::VisualRun::getGlyphs() const +{ + return this->glyphs; +} + +/** + * Get the positions of this run. + * @return The positions. + */ +float *ParagraphLayout::VisualRun::getPositions() const +{ + return this->positions; +} + +/** + * Get the height of this font. + * @return The height of the font. + */ +int ParagraphLayout::VisualRun::getLeading() const +{ + return this->getFont()->fc->GetHeight(); +} + +/** + * Get the height of the line. + * @return The maximum height of the line. + */ +int ParagraphLayout::Line::getLeading() const +{ + int leading = 0; + for (const VisualRun * const *run = this->Begin(); run != this->End(); run++) { + leading = max(leading, (*run)->getLeading()); + } + + return leading; +} + +/** + * Get the width of this line. + * @return The width of the line. + */ +int ParagraphLayout::Line::getWidth() const +{ + if (this->Length() == 0) return 0; + + /* + * The last X position of a run contains is the end of that run. + * Since there is no left-to-right support, taking this value of + * the last run gives us the end of the line and thus the width. + */ + const VisualRun *run = this->getVisualRun(this->countRuns() - 1); + return run->getPositions()[run->getGlyphCount() * 2]; +} + +/** + * Get the number of runs in this line. + * @return The number of runs. + */ +int ParagraphLayout::Line::countRuns() const +{ + return this->Length(); +} + +/** + * Get a specific visual run. + * @return The visual run. + */ +ParagraphLayout::VisualRun *ParagraphLayout::Line::getVisualRun(int run) const +{ + return *this->Get(run); +} + +/** + * Create a new paragraph layouter. + * @param buffer The characters of the paragraph. + * @param length The length of the paragraph. + * @param runs The font mapping of this paragraph. + */ +ParagraphLayout::ParagraphLayout(WChar *buffer, int length, FontMap &runs) : buffer_begin(buffer), buffer(buffer), runs(runs) +{ + assert(runs.End()[-1].first == length); +} + +/** + * Construct a new line with a maximum width. + * @param max_width The maximum width of the string. + * @return A Line, or NULL when at the end of the paragraph. + */ +ParagraphLayout::Line *ParagraphLayout::nextLine(int max_width) +{ + /* Simple idea: + * - split a line at a newline character, or at a space where we can break a line. + * - split for a visual run whenever a new line happens, or the font changes. + */ + if (this->buffer == NULL) return NULL; + + Line *l = new Line(); + + if (*this->buffer == '\0') { + /* Only a newline. */ + this->buffer = NULL; + *l->Append() = new VisualRun(this->runs.Begin()->second, this->buffer, 0, 0); + return l; + } + + const WChar *begin = this->buffer; + WChar *last_space = NULL; + const WChar *last_char = begin; + int width = 0; + + int offset = this->buffer - this->buffer_begin; + FontMap::iterator iter = this->runs.Begin(); + while (iter->first <= offset) { + iter++; + assert(iter != this->runs.End()); + } + + const FontCache *fc = iter->second->fc; + const WChar *next_run = this->buffer_begin + iter->first + 1; + + for (;;) { + WChar c = *this->buffer++; + + if (c == '\0') { + this->buffer = NULL; + break; + } + + if (this->buffer == next_run) { + *l->Append() = new VisualRun(iter->second, begin, this->buffer - begin, l->getWidth()); + iter++; + assert(iter != this->runs.End()); + + next_run = this->buffer_begin + iter->first + 1; + begin = this->buffer; + } + + if (IsWhitespace(c)) last_space = this->buffer; + + last_char = this->buffer; + + if (IsPrintable(c) && !IsTextDirectionChar(c)) { + int char_width = GetCharacterWidth(fc->GetSize(), c); + width += char_width; + if (width > max_width) { + /* The string is longer than maximum width so we need to decide + * what to do with it. */ + if (width == char_width) { + /* The character is wider than allowed width; don't know + * what to do with this case... bail out! */ + this->buffer = NULL; + return l; + } + + if (last_space == NULL) { + /* No space has been found. Just terminate at our current + * location. This usually happens for languages that do not + * require spaces in strings, like Chinese, Japanese and + * Korean. For other languages terminating mid-word might + * not be the best, but terminating the whole string instead + * of continuing the word at the next line is worse. */ + this->buffer--; + last_char = this->buffer; + } else { + /* A space is found; perfect place to terminate */ + this->buffer = last_space; + last_char = last_space - 1; + } + break; + } + } + } + + if (l->Length() == 0 || last_char - begin != 0) { + *l->Append() = new VisualRun(iter->second, begin, last_char - begin, l->getWidth()); + } + return l; +} + +/** + * Appand a wide character to the internal buffer. + * @param buff The buffer to append to. + * @param buffer_last The end of the buffer. + * @param c The character to add. + * @return The number of buffer spaces that were used. + */ +size_t Layouter::AppendToBuffer(WChar *buff, const WChar *buffer_last, WChar c) +{ + *buff = c; + return 1; +} + +/** + * Get the actual ParagraphLayout for the given buffer. + * @param buff The begin of the buffer. + * @param buff_end The location after the last element in the buffer. + * @param fontMapping THe mapping of the fonts. + * @return The ParagraphLayout instance. + */ +ParagraphLayout *Layouter::GetParagraphLayout(WChar *buff, WChar *buff_end, FontMap &fontMapping) +{ + return new ParagraphLayout(buff, buff_end - buff, fontMapping); +} +#endif /* !WITH_ICU */ + +/** + * Create a new layouter. + * @param str The string to create the layout for. + * @param maxw The maximum width. + * @param colour The colour of the font. + * @param fontsize The size of font to use. + */ +Layouter::Layouter(const char *str, int maxw, TextColour colour, FontSize fontsize) +{ + const CharType *buffer_last = lastof(this->buffer); + CharType *buff = this->buffer; + + TextColour cur_colour = colour, prev_colour = colour; + WChar c = 0; + + do { + Font *f = new Font(fontsize, cur_colour); + CharType *buff_begin = buff; + FontMap fontMapping; + + /* + * Go through the whole string while adding Font instances to the font map + * whenever the font changes, and convert the wide characters into a format + * usable by ParagraphLayout. + */ + for (; buff < buffer_last;) { + c = Utf8Consume(const_cast(&str)); + if (c == '\0' || c == '\n') { + break; + } else if (c >= SCC_BLUE && c <= SCC_BLACK) { + prev_colour = cur_colour; + cur_colour = (TextColour)(c - SCC_BLUE); + } else if (c == SCC_PREVIOUS_COLOUR) { // Revert to the previous colour. + Swap(prev_colour, cur_colour); + } else if (c == SCC_TINYFONT) { + fontsize = FS_SMALL; + } else if (c == SCC_BIGFONT) { + fontsize = FS_LARGE; + } else { + buff += AppendToBuffer(buff, buffer_last, c); + continue; + } + + if (!fontMapping.Contains(buff - buff_begin)) { + fontMapping.Insert(buff - buff_begin, f); + *this->fonts.Append() = f; + } else { + delete f; + } + f = new Font(fontsize, cur_colour); + } + + /* Better safe than sorry. */ + *buff = '\0'; + + if (!fontMapping.Contains(buff - buff_begin)) { + fontMapping.Insert(buff - buff_begin, f); + *this->fonts.Append() = f; + } + ParagraphLayout *p = GetParagraphLayout(buff_begin, buff, fontMapping); + + /* Copy all lines into a local cache so we can reuse them later on more easily. */ + ParagraphLayout::Line *l; + while ((l = p->nextLine(maxw)) != NULL) { + *this->Append() = l; + } + + delete p; + + } while (c != '\0' && buff < buffer_last); +} + +/** Free everything we allocated. */ +Layouter::~Layouter() +{ + for (Font **iter = this->fonts.Begin(); iter != this->fonts.End(); iter++) { + delete *iter; + } +} + +/** + * Get the boundaries of this paragraph. + * @return The boundaries. + */ +Dimension Layouter::GetBounds() +{ + Dimension d = { 0, 0 }; + for (ParagraphLayout::Line **l = this->Begin(); l != this->End(); l++) { + d.width = max(d.width, (*l)->getWidth()); + d.height += (*l)->getLeading(); + } + return d; +} diff --git a/src/gfx_layout.h b/src/gfx_layout.h new file mode 100644 index 0000000000..0fadd51263 --- /dev/null +++ b/src/gfx_layout.h @@ -0,0 +1,140 @@ +/* $Id$ */ + +/* + * 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 gfx_layout.h Functions related to laying out the texts. */ + +#ifndef GFX_LAYOUT_H +#define GFX_LAYOUT_H + +#include "fontcache.h" +#include "gfx_func.h" +#include "core/smallmap_type.hpp" + +#ifdef WITH_ICU +#include "layout/ParagraphLayout.h" +#define ICU_FONTINSTANCE : public LEFontInstance +#else /* WITH_ICU */ +#define ICU_FONTINSTANCE +#endif /* WITH_ICU */ + +/** + * Container with information about a font. + */ +class Font ICU_FONTINSTANCE { +public: + FontCache *fc; ///< The font we are using. + TextColour colour; ///< The colour this font has to be. + + Font(FontSize size, TextColour colour); + +#ifdef WITH_ICU + /* Implementation details of LEFontInstance */ + + le_int32 getUnitsPerEM() const; + le_int32 getAscent() const; + le_int32 getDescent() const; + le_int32 getLeading() const; + float getXPixelsPerEm() const; + float getYPixelsPerEm() const; + float getScaleFactorX() const; + float getScaleFactorY() const; + const void *getFontTable(LETag tableTag) const; + const void *getFontTable(LETag tableTag, size_t &length) const; + LEGlyphID mapCharToGlyph(LEUnicode32 ch) const; + void getGlyphAdvance(LEGlyphID glyph, LEPoint &advance) const; + le_bool getGlyphPoint(LEGlyphID glyph, le_int32 pointNumber, LEPoint &point) const; +#endif /* WITH_ICU */ +}; + +/** Mapping from index to font. */ +typedef SmallMap FontMap; + +#ifndef WITH_ICU +/** + * Class handling the splitting of a paragraph of text into lines and + * visual runs. + * + * One constructs this class with the text that needs to be split into + * lines. Then nextLine is called with the maximum width until NULL is + * returned. Each nextLine call creates VisualRuns which contain the + * length of text that are to be drawn with the same font. In other + * words, the result of this class is a list of sub strings with their + * font. The sub strings are then already fully laid out, and only + * need actual drawing. + * + * The positions in a visual run are sequential pairs of X,Y of the + * begin of each of the glyphs plus an extra pair to mark the end. + * + * @note This variant does not handle left-to-right properly. This + * is supported in the one ParagraphLayout coming from ICU. + * @note Does not conform to function naming style as it provides a + * fallback for the ICU class. + */ +class ParagraphLayout { +public: + /** Visual run contains data about the bit of text with the same font. */ + class VisualRun { + Font *font; ///< The font used to layout these. + GlyphID *glyphs; ///< The glyphs we're drawing. + float *positions; ///< The positions of the glyphs. + int glyph_count; ///< The number of glyphs. + + public: + VisualRun(Font *font, const WChar *chars, int glyph_count, int x); + ~VisualRun(); + Font *getFont() const; + int getGlyphCount() const; + const GlyphID *getGlyphs() const; + float *getPositions() const; + int getLeading() const; + }; + + /** A single line worth of VisualRuns. */ + class Line : public AutoDeleteSmallVector { + public: + int getLeading() const; + int getWidth() const; + int countRuns() const; + VisualRun *getVisualRun(int run) const; + }; + + const WChar *buffer_begin; ///< Begin of the buffer. + WChar *buffer; ///< The current location in the buffer. + FontMap &runs; ///< The fonts we have to use for this paragraph. + + ParagraphLayout(WChar *buffer, int length, FontMap &runs); + Line *nextLine(int max_width); +}; +#endif /* !WITH_ICU */ + +/** + * The layouter performs all the layout work. + * + * It also accounts for the memory allocations and frees. + */ +class Layouter : public AutoDeleteSmallVector { +#ifdef WITH_ICU + typedef UChar CharType; ///< The type of character used within the layouter. +#else /* WITH_ICU */ + typedef WChar CharType; ///< The type of character used within the layouter. +#endif /* WITH_ICU */ + + size_t AppendToBuffer(CharType *buff, const CharType *buffer_last, WChar c); + ParagraphLayout *GetParagraphLayout(CharType *buff, CharType *buff_end, FontMap &fontMapping); + + CharType buffer[DRAW_STRING_BUFFER]; ///< Buffer for the text that is going to be drawn. + SmallVector fonts; ///< The fonts needed for drawing. + +public: + Layouter(const char *str, int maxw = INT32_MAX, TextColour colour = TC_FROMSTRING, FontSize fontsize = FS_NORMAL); + ~Layouter(); + Dimension GetBounds(); +}; + +#endif /* GFX_LAYOUT_H */ diff --git a/src/network/network_chat_gui.cpp b/src/network/network_chat_gui.cpp index 062858e11d..ae59995c7c 100644 --- a/src/network/network_chat_gui.cpp +++ b/src/network/network_chat_gui.cpp @@ -81,10 +81,7 @@ static inline uint GetChatMessageCount() void CDECL NetworkAddChatMessage(TextColour colour, uint duration, const char *message, ...) { char buf[DRAW_STRING_BUFFER]; - const char *bufp; va_list va; - uint msg_count; - uint16 lines; va_start(va, message); vsnprintf(buf, lengthof(buf), message, va); @@ -92,29 +89,16 @@ void CDECL NetworkAddChatMessage(TextColour colour, uint duration, const char *m Utf8TrimString(buf, DRAW_STRING_BUFFER); - /* Force linebreaks for strings that are too long */ - lines = GB(FormatStringLinebreaks(buf, lastof(buf), _chatmsg_box.width - 8), 0, 16) + 1; - if (lines >= MAX_CHAT_MESSAGES) return; - - msg_count = GetChatMessageCount(); - /* We want to add more chat messages than there is free space for, remove 'old' */ - if (lines > MAX_CHAT_MESSAGES - msg_count) { - int i = lines - (MAX_CHAT_MESSAGES - msg_count); - memmove(&_chatmsg_list[0], &_chatmsg_list[i], sizeof(_chatmsg_list[0]) * (msg_count - i)); - msg_count = MAX_CHAT_MESSAGES - lines; + uint msg_count = GetChatMessageCount(); + if (MAX_CHAT_MESSAGES == msg_count) { + memmove(&_chatmsg_list[0], &_chatmsg_list[1], sizeof(_chatmsg_list[0]) * (msg_count - 1)); + msg_count = MAX_CHAT_MESSAGES - 1; } - for (bufp = buf; lines != 0; lines--) { - ChatMessage *cmsg = &_chatmsg_list[msg_count++]; - strecpy(cmsg->message, bufp, lastof(cmsg->message)); - - /* The default colour for a message is company colour. Replace this with - * white for any additional lines */ - cmsg->colour = (bufp == buf && (colour & TC_IS_PALETTE_COLOUR)) ? colour : TC_WHITE; - cmsg->remove_time = _realtime_tick + duration * 1000; - - bufp += strlen(bufp) + 1; // jump to 'next line' in the formatted string - } + ChatMessage *cmsg = &_chatmsg_list[msg_count++]; + strecpy(cmsg->message, buf, lastof(cmsg->message)); + cmsg->colour = (colour & TC_IS_PALETTE_COLOUR) ? colour : TC_WHITE; + cmsg->remove_time = _realtime_tick + duration * 1000; _chatmessage_dirty = true; } @@ -246,18 +230,27 @@ void NetworkDrawChatMessage() _cur_dpi = &_screen; // switch to _screen painting + int string_height = 0; + for (uint i = 0; i < count; i++) { + SetDParamStr(0, _chatmsg_list[i].message); + string_height += GetStringLineCount(STR_JUST_RAW_STRING, width - 1) * FONT_HEIGHT_NORMAL + NETWORK_CHAT_LINE_SPACING; + } + + string_height = min(string_height, MAX_CHAT_MESSAGES * (FONT_HEIGHT_NORMAL + NETWORK_CHAT_LINE_SPACING)); + + int top = _screen.height - _chatmsg_box.y - string_height - 2; + int bottom = _screen.height - _chatmsg_box.y - 2; /* Paint a half-transparent box behind the chat messages */ - GfxFillRect( - _chatmsg_box.x, - _screen.height - _chatmsg_box.y - count * (FONT_HEIGHT_NORMAL + NETWORK_CHAT_LINE_SPACING) - 2, - _chatmsg_box.x + _chatmsg_box.width - 1, - _screen.height - _chatmsg_box.y - 2, + GfxFillRect(_chatmsg_box.x, top - 2, _chatmsg_box.x + _chatmsg_box.width - 1, bottom, PALETTE_TO_TRANSPARENT, FILLRECT_RECOLOUR // black, but with some alpha for background ); /* Paint the chat messages starting with the lowest at the bottom */ - for (uint y = FONT_HEIGHT_NORMAL + NETWORK_CHAT_LINE_SPACING; count-- != 0; y += (FONT_HEIGHT_NORMAL + NETWORK_CHAT_LINE_SPACING)) { - DrawString(_chatmsg_box.x + 3, _chatmsg_box.x + _chatmsg_box.width - 1, _screen.height - _chatmsg_box.y - y + 1, _chatmsg_list[count].message, _chatmsg_list[count].colour); + int ypos = bottom - 2; + + for (int i = count - 1; i >= 0; i--) { + ypos = DrawStringMultiLine(_chatmsg_box.x + 3, _chatmsg_box.x + _chatmsg_box.width - 1, top, ypos, _chatmsg_list[i].message, _chatmsg_list[i].colour, SA_LEFT | SA_BOTTOM | SA_FORCE) - NETWORK_CHAT_LINE_SPACING; + if (ypos < top) break; } /* Make sure the data is updated next flush */ diff --git a/src/os/macosx/osx_stdafx.h b/src/os/macosx/osx_stdafx.h index decd0ba6c2..ad9c5220c1 100644 --- a/src/os/macosx/osx_stdafx.h +++ b/src/os/macosx/osx_stdafx.h @@ -44,6 +44,7 @@ #define WindowClass OTTDWindowClass #define ScriptOrder OTTDScriptOrder #define Palette OTTDPalette +#define GlyphID OTTDGlyphID #include #include @@ -53,6 +54,7 @@ #undef WindowClass #undef ScriptOrder #undef Palette +#undef GlyphID /* remove the variables that CoreServices defines, but we define ourselves too */ #undef bool diff --git a/src/strings.cpp b/src/strings.cpp index ec243fbffd..5ea914035f 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -20,7 +20,7 @@ #include "newgrf_text.h" #include "fileio_func.h" #include "signs_base.h" -#include "fontcache.h" +#include "fontdetection.h" #include "error.h" #include "strings_func.h" #include "rev.h" @@ -2073,9 +2073,9 @@ class LanguagePackGlyphSearcher : public MissingGlyphSearcher { /* virtual */ void SetFontNames(FreeTypeSettings *settings, const char *font_name) { #ifdef WITH_FREETYPE - strecpy(settings->small_font, font_name, lastof(settings->small_font)); - strecpy(settings->medium_font, font_name, lastof(settings->medium_font)); - strecpy(settings->large_font, font_name, lastof(settings->large_font)); + strecpy(settings->small.font, font_name, lastof(settings->small.font)); + strecpy(settings->medium.font, font_name, lastof(settings->medium.font)); + strecpy(settings->large.font, font_name, lastof(settings->large.font)); #endif /* WITH_FREETYPE */ } }; diff --git a/src/table/misc_settings.ini b/src/table/misc_settings.ini index 6d9e7bbea0..1a2b5ef6b8 100644 --- a/src/table/misc_settings.ini +++ b/src/table/misc_settings.ini @@ -135,36 +135,36 @@ def = false ifdef = WITH_FREETYPE name = ""small_font"" type = SLE_STRB -var = _freetype.small_font +var = _freetype.small.font def = NULL [SDTG_STR] ifdef = WITH_FREETYPE name = ""medium_font"" type = SLE_STRB -var = _freetype.medium_font +var = _freetype.medium.font def = NULL [SDTG_STR] ifdef = WITH_FREETYPE name = ""large_font"" type = SLE_STRB -var = _freetype.large_font +var = _freetype.large.font def = NULL [SDTG_STR] ifdef = WITH_FREETYPE name = ""mono_font"" type = SLE_STRB -var = _freetype.mono_font +var = _freetype.mono.font def = NULL [SDTG_VAR] ifdef = WITH_FREETYPE name = ""small_size"" type = SLE_UINT -var = _freetype.small_size -def = 8 +var = _freetype.small.size +def = 0 min = 0 max = 72 @@ -172,8 +172,8 @@ max = 72 ifdef = WITH_FREETYPE name = ""medium_size"" type = SLE_UINT -var = _freetype.medium_size -def = 10 +var = _freetype.medium.size +def = 0 min = 0 max = 72 @@ -181,8 +181,8 @@ max = 72 ifdef = WITH_FREETYPE name = ""large_size"" type = SLE_UINT -var = _freetype.large_size -def = 16 +var = _freetype.large.size +def = 0 min = 0 max = 72 @@ -190,33 +190,33 @@ max = 72 ifdef = WITH_FREETYPE name = ""mono_size"" type = SLE_UINT -var = _freetype.mono_size -def = 10 +var = _freetype.mono.size +def = 0 min = 0 max = 72 [SDTG_BOOL] ifdef = WITH_FREETYPE name = ""small_aa"" -var = _freetype.small_aa +var = _freetype.small.aa def = false [SDTG_BOOL] ifdef = WITH_FREETYPE name = ""medium_aa"" -var = _freetype.medium_aa +var = _freetype.medium.aa def = false [SDTG_BOOL] ifdef = WITH_FREETYPE name = ""large_aa"" -var = _freetype.large_aa +var = _freetype.large.aa def = false [SDTG_BOOL] ifdef = WITH_FREETYPE name = ""mono_aa"" -var = _freetype.mono_aa +var = _freetype.mono.aa def = false [SDTG_VAR] diff --git a/src/textfile_gui.cpp b/src/textfile_gui.cpp index 91adc12393..9e86fb0bba 100644 --- a/src/textfile_gui.cpp +++ b/src/textfile_gui.cpp @@ -135,7 +135,7 @@ TextfileWindow::TextfileWindow(TextfileType file_type) : Window(), file_type(fil /* virtual */ void TextfileWindow::SetFontNames(FreeTypeSettings *settings, const char *font_name) { #ifdef WITH_FREETYPE - strecpy(settings->mono_font, font_name, lastof(settings->mono_font)); + strecpy(settings->mono.font, font_name, lastof(settings->mono.font)); #endif /* WITH_FREETYPE */ } diff --git a/src/widget.cpp b/src/widget.cpp index 72612a48ef..17644fe538 100644 --- a/src/widget.cpp +++ b/src/widget.cpp @@ -501,7 +501,7 @@ static inline void DrawCloseBox(const Rect &r, Colours colour, StringID str) { assert(str == STR_BLACK_CROSS || str == STR_SILVER_CROSS); // black or silver cross DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, FR_NONE); - DrawString(r.left + WD_CLOSEBOX_LEFT, r.right - WD_CLOSEBOX_RIGHT, r.top + WD_CLOSEBOX_TOP, str, TC_FROMSTRING, SA_HOR_CENTER); + DrawString(r.left, r.right, r.top + WD_CLOSEBOX_TOP, str, TC_FROMSTRING, SA_HOR_CENTER); } /**