diff --git a/data/hardwired/fonts.cfg b/data/hardwired/fonts.cfg index 8322a335725..ec9b3051b8c 100644 --- a/data/hardwired/fonts.cfg +++ b/data/hardwired/fonts.cfg @@ -9,6 +9,8 @@ [font] name="DejaVuSans.ttf" + bold_name="DejaVuSans-Bold.ttf" + italic_name="DejaVuSans-Oblique.ttf" codepoints="32-126,160-745,748-750,755,759,768-847,849-851,855-856,858,860-866,880-887,890-894,900-906,908,910-929,931-1317,1329-1366,1369-1375,1377-1415,1417-1418,1456-1475,1478-1479,1488-1514,1520-1524,1542-1543,1545-1546,1548,1557,1563,1567,1569-1594,1600-1621,1623,1626,1632-1648,1652,1657-1727,1734,1740,1742,1749,1776-1785,1984-2023,2027-2037,2040-2042,3647,3713-3714,3716,3719-3720,3722,3725,3732-3735,3737-3743,3745-3747,3749,3751,3754-3755,3757-3769,3771-3773,3776-3780,3782,3784-3789,3792-3801,3804-3805,4256-4293,4304-4348,5121-5127,5129-5147,5149-5173,5175-5194,5196-5202,5204-5309,5312-5354,5356-5383,5392-5438,5440-5456,5458-5482,5492-5509,5514-5526,5536-5551,5598,5601,5702-5703,5742-5750,5760-5788,7424-7531,7543-7544,7547,7549,7557,7579-7615,7620-7625,7680-7931,7936-7957,7960-7965,7968-8005,8008-8013,8016-8023,8025,8027,8029,8031-8061,8064-8116,8118-8132,8134-8147,8150-8155,8157-8175,8178-8180,8182-8190,8192-8292,8298-8305,8308-8334,8336-8348,8352-8373,8376-8378,8400-8401,8406-8407,8411-8412,8417,8448-8457,8459-8521,8523,8526,8528-8581,8585,8592-8981,8984-8985,8988-8993,8996-9004,9013,9015-9022,9025-9028,9031-9033,9035-9037,9040,9042-9044,9047-9052,9054-9056,9059-9061,9064-9065,9067-9072,9075-9082,9085,9088-9091,9095-9099,9108-9109,9115-9134,9166-9167,9187,9189,9192,9250-9251,9312-9321,9472-9884,9888-9912,9920-9923,9954,9985-9988,9990-9993,9996-10023,10025-10059,10061,10063-10066,10070,10072-10078,10081-10132,10136-10159,10161-10175,10181-10182,10208,10214-10219,10224-10623,10627-10628,10702-10709,10731,10746-10747,10752-10754,10764-10780,10799,10858-10859,10877-10912,10926-10938,11001-11002,11008-11034,11039-11044,11091-11092,11360-11383,11385-11391,11520-11557,11568-11621,11631,11800,11807,11810-11813,11822,19904-19967,42564-42567,42572-42573,42576-42577,42580-42583,42594-42606,42634-42637,42644-42645,42760-42774,42779-42783,42786-42817,42822-42827,42830-42835,42838-42839,42852-42857,42875-42876,42880-42887,42889-42894,42896-42897,42912-42922,43002-43007,57344-57373,61184-61209,61440-61443,61960,61962,61973-61975,61978-61979,62047,62464-62502,62504,62917,63172-63176,63185,63188,64256-64262,64275-64279,64285-64335,64338-64419,64426-64429,64467-64470,64473-64474,64488-64489,64508-64511,65024-65039,65056-65059,65136-65140,65142-65276,65279,65529-65533,66304-66334,66336-66339,119552-119638,119808-119892,119894-119963,120120-120121,120123-120126,120128-120132,120134,120138-120144,120146-120171,120224-120485,120488-120779,120782-120831,127024-127123,127136-127150,127153-127166,127169-127183,127185-127199,128045-128046,128049,128053,128512-128547,128549-128555,128557-128576" [/font] [font] diff --git a/fonts/DejaVuSans-Bold.ttf b/fonts/DejaVuSans-Bold.ttf new file mode 100644 index 00000000000..08695f23a96 Binary files /dev/null and b/fonts/DejaVuSans-Bold.ttf differ diff --git a/fonts/DejaVuSans-Oblique.ttf b/fonts/DejaVuSans-Oblique.ttf new file mode 100644 index 00000000000..e33ab144d7a Binary files /dev/null and b/fonts/DejaVuSans-Oblique.ttf differ diff --git a/src/font.cpp b/src/font.cpp index 30ca5288d15..9949fc2db58 100644 --- a/src/font.cpp +++ b/src/font.cpp @@ -34,6 +34,7 @@ #include "serialization/unicode.hpp" #include +#include #include #include @@ -64,24 +65,41 @@ static lg::log_domain log_font("font"); // Signed int. Negative values mean "no subset". typedef int subset_id; +// Used as a key in the font table, which caches the get_font results. struct font_id { - font_id(subset_id subset, int size) : subset(subset), size(size) {} + font_id(subset_id subset, int size) : subset(subset), size(size), style(TTF_STYLE_NORMAL) {} + font_id(subset_id subset, int size, int style) : subset(subset), size(size), style(style) {} bool operator==(const font_id& o) const { - return subset == o.subset && size == o.size; + return subset == o.subset && size == o.size && style == o.style; } bool operator<(const font_id& o) const { - return subset < o.subset || (subset == o.subset && size < o.size); + return subset < o.subset || (subset == o.subset && size < o.size) || (subset == o.subset && size == o.size && style < o.style); } subset_id subset; int size; + int style; }; -static std::map font_table; +// Record stored in the font table. +// If the record for font_id (FOO, Bold + Underline) is a record (BAR, Bold), +// it means that BAR is a Bold-styled version of FOO which we shipped with the +// game, and now SDL_TTF should be used to style BAR as underline for the final results. +struct ttf_record +{ + TTF_Font* font; + int style; +}; + +typedef std::map tfont_table; + +static tfont_table font_table; static std::vector font_names; +static std::vector bold_names; +static std::vector italic_names; struct text_chunk { @@ -203,8 +221,28 @@ static std::vector split_text(std::string const & utf8_text) { return chunks; } +typedef std::map, TTF_Font*> topen_font_cache; +topen_font_cache open_fonts; + +static TTF_Font* open_font_impl(const std::string & , int); + +// A wrapper which caches the results of open_font_impl. +// Note that clear_fonts() is responsible to clean up all of these font pointers, +// so to avoid memory leaks fonts should only be opened from this function. static TTF_Font* open_font(const std::string& fname, int size) { + const std::pair key = std::make_pair(fname, size); + const topen_font_cache::iterator it = open_fonts.find(key); + if (it != open_fonts.end()) { + return it->second; + } + + TTF_Font* result = open_font_impl(fname, size); + open_fonts.insert(std::make_pair(key, result)); + return result; +} + +static TTF_Font* open_font_impl(const std::string & fname, int size) { std::string name; if(!game_config::path.empty()) { name = game_config::path + "/fonts/" + fname; @@ -233,92 +271,91 @@ static TTF_Font* open_font(const std::string& fname, int size) SDL_RWops *rwops = filesystem::load_RWops(name); TTF_Font* font = TTF_OpenFontRW(rwops, true, size); // SDL takes ownership of rwops if(font == NULL) { - ERR_FT << "Failed opening font: TTF_OpenFont: " << TTF_GetError() << std::endl; + ERR_FT << "Failed opening font: '" << fname << "'\n"; + ERR_FT << "TTF_OpenFont: " << TTF_GetError() << std::endl; return NULL; } + DBG_FT << "Opened a font: " << fname << std::endl; + return font; } +// Gets an appropriately configured TTF Font, for this font size and style. +// Loads fonts if necessary. For styled fonts, we search for a ``shipped'' +// version of the font which is prestyled. If this fails we find the closest +// thing which we did ship, and store a record of this, which allows to +// rapidly correct the remaining styling using SDL_TTF. +// +// Uses the font table for caching. static TTF_Font* get_font(font_id id) { - const std::map::iterator it = font_table.find(id); - if(it != font_table.end()) - return it->second; + const std::map::iterator it = font_table.find(id); + if(it != font_table.end()) { + if (it->second.font != NULL) { + // If we found a valid record, use SDL_TTF to add in the difference + // between its intrinsic style and the desired style. + TTF_SetFontStyle(it->second.font, it->second.style ^ id.style); + } + return it->second.font; + } - if(id.subset < 0 || size_t(id.subset) >= font_names.size()) + // There's no record, so we need to try to find a solution for this font + // and make a record of it. If the indices are out of bounds don't bother though. + if(id.subset < 0 || size_t(id.subset) >= font_names.size()) { return NULL; + } - TTF_Font* font = open_font(font_names[id.subset], id.size); + // Favor to use the shipped Italic font over bold if both are present and are needed. + if ((id.style & TTF_STYLE_ITALIC) && italic_names[id.subset].size()) { + if (TTF_Font* font = open_font(italic_names[id.subset], id.size)) { + ttf_record rec = {font, TTF_STYLE_ITALIC}; + font_table.insert(std::make_pair(id, rec)); + return get_font(id); + } + } - if(font == NULL) - return NULL; + // Now see if the shipped Bold font is useful and available. + if ((id.style & TTF_STYLE_BOLD) && bold_names[id.subset].size()) { + if (TTF_Font* font = open_font(bold_names[id.subset], id.size)) { + ttf_record rec = {font, TTF_STYLE_BOLD}; + font_table.insert(std::make_pair(id, rec)); + return get_font(id); + } + } - TTF_SetFontStyle(font,TTF_STYLE_NORMAL); + // Try just to use the basic version of the font then. + if (font_names[id.subset].size()) { + if(TTF_Font* font = open_font(font_names[id.subset], id.size)) { + ttf_record rec = {font, TTF_STYLE_NORMAL}; + font_table.insert(std::make_pair(id, rec)); + return get_font(id); + } + } - LOG_FT << "Inserting font...\n"; - font_table.insert(std::pair(id, font)); - return font; + // Failed to find a font. + ttf_record rec = {NULL, TTF_STYLE_NORMAL}; + font_table.insert(std::make_pair(id, rec)); + return NULL; } static void clear_fonts() { - for(std::map::iterator i = font_table.begin(); i != font_table.end(); ++i) { + for(topen_font_cache::iterator i = open_fonts.begin(); i != open_fonts.end(); ++i) { TTF_CloseFont(i->second); } + open_fonts.clear(); font_table.clear(); + font_names.clear(); + bold_names.clear(); + italic_names.clear(); + char_blocks.cbmap.clear(); line_size_cache.clear(); } -namespace { - -struct font_style_setter -{ - font_style_setter(TTF_Font* font, int style) : font_(font), old_style_(0) - { - if(style == 0) { - style = TTF_STYLE_NORMAL; - } - - old_style_ = TTF_GetFontStyle(font_); - - // I thought I had killed this. Now that we ship SDL_TTF, we - // should fix the bug directly in SDL_ttf instead of disabling - // features. -- Ayin 25/2/2005 -#if 0 - //according to the SDL_ttf documentation, combinations of - //styles may cause SDL_ttf to segfault. We work around this - //here by disallowing combinations of styles - - if((style&TTF_STYLE_UNDERLINE) != 0) { - //style = TTF_STYLE_NORMAL; //TTF_STYLE_UNDERLINE; - style = TTF_STYLE_UNDERLINE; - } else if((style&TTF_STYLE_BOLD) != 0) { - style = TTF_STYLE_BOLD; - } else if((style&TTF_STYLE_ITALIC) != 0) { - //style = TTF_STYLE_NORMAL; //TTF_STYLE_ITALIC; - style = TTF_STYLE_ITALIC; - } -#endif - - TTF_SetFontStyle(font_, style); - } - - ~font_style_setter() - { - TTF_SetFontStyle(font_,old_style_); - } - -private: - TTF_Font* font_; - int old_style_; -}; - -} - namespace font { std::string describe_versions() @@ -422,11 +459,67 @@ struct subset_descriptor { } + subset_descriptor(const config &); + std::string name; + boost::optional bold_name; //If we are using another font for styled characters in this font, rather than SDL TTF method + boost::optional italic_name; + typedef std::pair range; std::vector present_codepoints; }; +font::subset_descriptor::subset_descriptor(const config & font) + : name(font["name"].str()) + , bold_name() + , italic_name() + , present_codepoints() +{ + if (font.has_attribute("bold_name")) { + bold_name = font["bold_name"].str(); + } + + if (font.has_attribute("italic_name")) { + italic_name = font["italic_name"].str(); + } + + std::vector ranges = utils::split(font["codepoints"]); + + BOOST_FOREACH(const std::string & i, ranges) { + std::vector r = utils::split(i, '-'); + if(r.size() == 1) { + size_t r1 = lexical_cast_default(r[0], 0); + present_codepoints.push_back(std::pair(r1, r1)); + } else if(r.size() == 2) { + size_t r1 = lexical_cast_default(r[0], 0); + size_t r2 = lexical_cast_default(r[1], 0); + + present_codepoints.push_back(std::pair(r1, r2)); + } + } +} + +static bool check_font_file(std::string name) { + if(game_config::path.empty() == false) { + if(!filesystem::file_exists(game_config::path + "/fonts/" + name)) { + if(!filesystem::file_exists("fonts/" + name)) { + if(!filesystem::file_exists(name)) { + WRN_FT << "Failed opening font file '" << name << "': No such file or directory" << std::endl; + return false; + } + } + } + } else { + if(!filesystem::file_exists("fonts/" + name)) { + if(!filesystem::file_exists(name)) { + WRN_FT << "Failed opening font file '" << name << "': No such file or directory" << std::endl; + return false; + } + } + } + return true; +} + //sets the font list to be used. static void set_font_list(const std::vector& fontlist) { @@ -434,32 +527,37 @@ static void set_font_list(const std::vector& fontlist) std::vector::const_iterator itor; for(itor = fontlist.begin(); itor != fontlist.end(); ++itor) { + if (!check_font_file(itor->name)) continue; // Insert fonts only if the font file exists - if(game_config::path.empty() == false) { - if(!filesystem::file_exists(game_config::path + "/fonts/" + itor->name)) { - if(!filesystem::file_exists("fonts/" + itor->name)) { - if(!filesystem::file_exists(itor->name)) { - WRN_FT << "Failed opening font file '" << itor->name << "': No such file or directory" << std::endl; - continue; - } - } - } - } else { - if(!filesystem::file_exists("fonts/" + itor->name)) { - if(!filesystem::file_exists(itor->name)) { - WRN_FT << "Failed opening font file '" << itor->name << "': No such file or directory" << std::endl; - continue; - } - } - } const subset_id subset = font_names.size(); font_names.push_back(itor->name); + if (itor->bold_name && check_font_file(*itor->bold_name)) { + bold_names.push_back(*itor->bold_name); + } else { + bold_names.push_back(""); + } + + if (itor->italic_name && check_font_file(*itor->italic_name)) { + italic_names.push_back(*itor->italic_name); + } else { + italic_names.push_back(""); + } + BOOST_FOREACH(const subset_descriptor::range &cp_range, itor->present_codepoints) { char_blocks.insert(cp_range.first, cp_range.second, subset); } } char_blocks.compress(); + + assert(font_names.size() == bold_names.size()); + assert(font_names.size() == italic_names.size()); + + DBG_FT << "Set the font list. The styled font families are:\n"; + + for (size_t i = 0; i < font_names.size(); ++i) { + DBG_FT << "[" << i << "]:\t\tbase:\t'" << font_names[i] << "'\tbold:\t'" << bold_names[i] << "'\titalic:\t'" << italic_names[i] << "'\n"; + } } const SDL_Color NORMAL_COLOR = {0xDD,0xDD,0xDD,0}, @@ -614,10 +712,10 @@ void text_surface::measure() const BOOST_FOREACH(text_chunk const &chunk, chunks_) { - TTF_Font* ttfont = get_font(font_id(chunk.subset, font_size_)); - if(ttfont == NULL) + TTF_Font* ttfont = get_font(font_id(chunk.subset, font_size_, style_)); + if(ttfont == NULL) { continue; - font_style_setter const style_setter(ttfont, style_); + } int w, h; TTF_SizeUTF8(ttfont, chunk.text.c_str(), &w, &h); @@ -660,10 +758,7 @@ std::vector const &text_surface::get_surfaces() const BOOST_FOREACH(text_chunk const &chunk, chunks_) { - TTF_Font* ttfont = get_font(font_id(chunk.subset, font_size_)); - if (ttfont == NULL) - continue; - font_style_setter const style_setter(ttfont, style_); + TTF_Font* ttfont = get_font(font_id(chunk.subset, font_size_, style_)); surface s = surface(TTF_RenderUTF8_Blended(ttfont, chunk.text.c_str(), color_)); if(!s.null()) @@ -1416,30 +1511,15 @@ static bool add_font_to_fontlist(const config &fonts_config, std::vector& fontlist, const std::string& name) { const config &font = fonts_config.find_child("font", "name", name); - if (!font) + if (!font) { return false; - - fontlist.push_back(font::subset_descriptor()); - fontlist.back().name = name; - std::vector ranges = utils::split(font["codepoints"]); - - for(std::vector::const_iterator itor = ranges.begin(); - itor != ranges.end(); ++itor) { - - std::vector r = utils::split(*itor, '-'); - if(r.size() == 1) { - size_t r1 = lexical_cast_default(r[0], 0); - fontlist.back().present_codepoints.push_back(std::pair(r1, r1)); - } else if(r.size() == 2) { - size_t r1 = lexical_cast_default(r[0], 0); - size_t r2 = lexical_cast_default(r[1], 0); - - fontlist.back().present_codepoints.push_back(std::pair(r1, r2)); - } - } - - return true; } + //DBG_FT << "Adding a font record: " << font.debug() << std::endl; + + fontlist.push_back(font::subset_descriptor(font)); + + return true; +} namespace font { @@ -1474,6 +1554,12 @@ bool load_font_config() std::set known_fonts; BOOST_FOREACH(const config &font, fonts_config.child_range("font")) { known_fonts.insert(font["name"]); + if (font.has_attribute("bold_name")) { + known_fonts.insert(font["bold_name"]); + } + if (font.has_attribute("italic_name")) { + known_fonts.insert(font["italic_name"]); + } } family_order = fonts_config["family_order"];