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)trueLIBCMT.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)trueLIBCMT.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)trueLIBCMT.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)trueLIBCMT.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);
}
/**