font.cpp

Go to the documentation of this file.
00001 /* $Id: font.cpp 52533 2012-01-07 02:35:17Z shadowmaster $ */
00002 /* vim:set encoding=utf-8: */
00003 /*
00004    Copyright (C) 2003 - 2012 by David White <dave@whitevine.net>
00005    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00006 
00007    This program is free software; you can redistribute it and/or modify
00008    it under the terms of the GNU General Public License as published by
00009    the Free Software Foundation; either version 2 of the License, or
00010    (at your option) any later version.
00011    This program is distributed in the hope that it will be useful,
00012    but WITHOUT ANY WARRANTY.
00013 
00014    See the COPYING file for more details.
00015 */
00016 
00017 #define GETTEXT_DOMAIN "wesnoth-lib"
00018 
00019 #include "global.hpp"
00020 
00021 #include "config.hpp"
00022 #include "filesystem.hpp"
00023 #include "font.hpp"
00024 #include "foreach.hpp"
00025 #include "game_config.hpp"
00026 #include "log.hpp"
00027 #include "marked-up_text.hpp"
00028 #include "text.hpp"
00029 #include "tooltips.hpp"
00030 #include "video.hpp"
00031 #include "serialization/parser.hpp"
00032 #include "serialization/preprocessor.hpp"
00033 #include "serialization/string_utils.hpp"
00034 
00035 #include <list>
00036 #include <set>
00037 #include <stack>
00038 
00039 #include <cairo-features.h>
00040 
00041 #ifdef CAIRO_HAS_WIN32_FONT
00042 #include <windows.h>
00043 #undef CAIRO_HAS_FT_FONT
00044 #endif
00045 
00046 #ifdef CAIRO_HAS_FT_FONT
00047 #include <fontconfig/fontconfig.h>
00048 #endif
00049 
00050 static lg::log_domain log_font("font");
00051 #define DBG_FT LOG_STREAM(debug, log_font)
00052 #define LOG_FT LOG_STREAM(info, log_font)
00053 #define WRN_FT LOG_STREAM(warn, log_font)
00054 #define ERR_FT LOG_STREAM(err, log_font)
00055 
00056 #ifdef  HAVE_FRIBIDI
00057 #include <fribidi.h>
00058 #endif
00059 
00060 // Signed int. Negative values mean "no subset".
00061 typedef int subset_id;
00062 
00063 struct font_id
00064 {
00065     font_id(subset_id subset, int size) : subset(subset), size(size) {};
00066     bool operator==(const font_id& o) const
00067     {
00068         return subset == o.subset && size == o.size;
00069     };
00070     bool operator<(const font_id& o) const
00071     {
00072         return subset < o.subset || (subset == o.subset && size < o.size);
00073     };
00074 
00075     subset_id subset;
00076     int size;
00077 };
00078 
00079 static std::map<font_id, TTF_Font*> font_table;
00080 static std::vector<std::string> font_names;
00081 
00082 struct text_chunk
00083 {
00084     text_chunk(subset_id subset) :
00085         subset(subset),
00086         text()
00087     {
00088     }
00089 
00090     bool operator==(text_chunk const & t) const { return subset == t.subset && text == t.text; }
00091     bool operator!=(text_chunk const & t) const { return !operator==(t); }
00092 
00093     subset_id subset;
00094     std::string text;
00095 };
00096 
00097 struct char_block_map
00098 {
00099     char_block_map()
00100         : cbmap()
00101     {
00102     }
00103 
00104     typedef std::pair<int, subset_id> block_t;
00105     typedef std::map<int, block_t> cbmap_t;
00106     cbmap_t cbmap;
00107     /** Associates not-associated parts of a range with a new font. */
00108     void insert(int first, int last, subset_id id)
00109     {
00110         if (first > last) return;
00111         cbmap_t::iterator i = cbmap.lower_bound(first);
00112         // At this point, either first <= i->first or i is past the end.
00113         if (i != cbmap.begin()) {
00114             cbmap_t::iterator j = i;
00115             --j;
00116             if (first <= j->second.first /* prev.last */) {
00117                 insert(j->second.first + 1, last, id);
00118                 return;
00119             }
00120         }
00121         if (i != cbmap.end()) {
00122             if (/* next.first */ i->first <= last) {
00123                 insert(first, i->first - 1, id);
00124                 return;
00125             }
00126         }
00127         cbmap.insert(std::make_pair(first, block_t(last, id)));
00128     }
00129     /**
00130      * Compresses map by merging consecutive ranges with the same font, even
00131      * if there is some unassociated ranges inbetween.
00132      */
00133     void compress()
00134     {
00135         LOG_FT << "Font map size before compression: " << cbmap.size() << " ranges\n";
00136         cbmap_t::iterator i = cbmap.begin(), e = cbmap.end();
00137         while (i != e) {
00138             cbmap_t::iterator j = i;
00139             ++j;
00140             if (j == e || i->second.second != j->second.second) {
00141                 i = j;
00142                 continue;
00143             }
00144             i->second.first = j->second.first;
00145             cbmap.erase(j);
00146         }
00147         LOG_FT << "Font map size after compression: " << cbmap.size() << " ranges\n";
00148     }
00149     subset_id get_id(int ch)
00150     {
00151         cbmap_t::iterator i = cbmap.upper_bound(ch);
00152         // At this point, either ch < i->first or i is past the end.
00153         if (i != cbmap.begin()) {
00154             --i;
00155             if (ch <= i->second.first /* prev.last */)
00156                 return i->second.second;
00157         }
00158         return -1;
00159     }
00160 };
00161 
00162 static char_block_map char_blocks;
00163 
00164 //cache sizes of small text
00165 typedef std::map<std::string,SDL_Rect> line_size_cache_map;
00166 
00167 //map of styles -> sizes -> cache
00168 static std::map<int,std::map<int,line_size_cache_map> > line_size_cache;
00169 
00170 //Splits the UTF-8 text into text_chunks using the same font.
00171 static std::vector<text_chunk> split_text(std::string const & utf8_text) {
00172     text_chunk current_chunk(0);
00173     std::vector<text_chunk> chunks;
00174 
00175     if (utf8_text.empty())
00176         return chunks;
00177 
00178     try {
00179         utils::utf8_iterator ch(utf8_text);
00180         int sub = char_blocks.get_id(*ch);
00181         if (sub >= 0) current_chunk.subset = sub;
00182         for(utils::utf8_iterator end = utils::utf8_iterator::end(utf8_text); ch != end; ++ch)
00183         {
00184             sub = char_blocks.get_id(*ch);
00185             if (sub >= 0 && sub != current_chunk.subset) {
00186                 chunks.push_back(current_chunk);
00187                 current_chunk.text.clear();
00188                 current_chunk.subset = sub;
00189             }
00190             current_chunk.text.append(ch.substr().first, ch.substr().second);
00191         }
00192         if (!current_chunk.text.empty()) {
00193             chunks.push_back(current_chunk);
00194         }
00195     }
00196     catch(utils::invalid_utf8_exception&) {
00197         WRN_FT << "Invalid UTF-8 string: \"" << utf8_text << "\"\n";
00198     }
00199     return chunks;
00200 }
00201 
00202 static TTF_Font* open_font(const std::string& fname, int size)
00203 {
00204     std::string name;
00205     if(!game_config::path.empty()) {
00206         name = game_config::path + "/fonts/" + fname;
00207         if(!file_exists(name)) {
00208             name = "fonts/" + fname;
00209             if(!file_exists(name)) {
00210                 name = fname;
00211                 if(!file_exists(name)) {
00212                     ERR_FT << "Failed opening font: '" << name << "': No such file or directory\n";
00213                     return NULL;
00214                 }
00215             }
00216         }
00217 
00218     } else {
00219         name = "fonts/" + fname;
00220         if(!file_exists(name)) {
00221             if(!file_exists(fname)) {
00222                 ERR_FT << "Failed opening font: '" << name << "': No such file or directory\n";
00223                 return NULL;
00224             }
00225             name = fname;
00226         }
00227     }
00228 
00229     TTF_Font* font = TTF_OpenFont(name.c_str(),size);
00230     if(font == NULL) {
00231         ERR_FT << "Failed opening font: TTF_OpenFont: " << TTF_GetError() << "\n";
00232         return NULL;
00233     }
00234 
00235     return font;
00236 }
00237 
00238 static TTF_Font* get_font(font_id id)
00239 {
00240     const std::map<font_id, TTF_Font*>::iterator it = font_table.find(id);
00241     if(it != font_table.end())
00242         return it->second;
00243 
00244     if(id.subset < 0 || size_t(id.subset) >= font_names.size())
00245         return NULL;
00246 
00247     TTF_Font* font = open_font(font_names[id.subset], id.size);
00248 
00249     if(font == NULL)
00250         return NULL;
00251 
00252     TTF_SetFontStyle(font,TTF_STYLE_NORMAL);
00253 
00254     LOG_FT << "Inserting font...\n";
00255     font_table.insert(std::pair<font_id,TTF_Font*>(id, font));
00256     return font;
00257 }
00258 
00259 static void clear_fonts()
00260 {
00261     for(std::map<font_id,TTF_Font*>::iterator i = font_table.begin(); i != font_table.end(); ++i) {
00262         TTF_CloseFont(i->second);
00263     }
00264 
00265     font_table.clear();
00266     font_names.clear();
00267     char_blocks.cbmap.clear();
00268     line_size_cache.clear();
00269 }
00270 
00271 namespace {
00272 
00273 struct font_style_setter
00274 {
00275     font_style_setter(TTF_Font* font, int style) : font_(font), old_style_(0)
00276     {
00277         if(style == 0) {
00278             style = TTF_STYLE_NORMAL;
00279         }
00280 
00281         old_style_ = TTF_GetFontStyle(font_);
00282 
00283         // I thought I had killed this. Now that we ship SDL_TTF, we
00284         // should fix the bug directly in SDL_ttf instead of disabling
00285         // features. -- Ayin 25/2/2005
00286 #if 0
00287         //according to the SDL_ttf documentation, combinations of
00288         //styles may cause SDL_ttf to segfault. We work around this
00289         //here by disallowing combinations of styles
00290 
00291         if((style&TTF_STYLE_UNDERLINE) != 0) {
00292             //style = TTF_STYLE_NORMAL; //TTF_STYLE_UNDERLINE;
00293             style = TTF_STYLE_UNDERLINE;
00294         } else if((style&TTF_STYLE_BOLD) != 0) {
00295             style = TTF_STYLE_BOLD;
00296         } else if((style&TTF_STYLE_ITALIC) != 0) {
00297             //style = TTF_STYLE_NORMAL; //TTF_STYLE_ITALIC;
00298             style = TTF_STYLE_ITALIC;
00299         }
00300 #endif
00301 
00302         TTF_SetFontStyle(font_, style);
00303     }
00304 
00305     ~font_style_setter()
00306     {
00307         TTF_SetFontStyle(font_,old_style_);
00308     }
00309 
00310 private:
00311     TTF_Font* font_;
00312     int old_style_;
00313 };
00314 
00315 }
00316 
00317 namespace font {
00318 
00319 manager::manager()
00320 {
00321     const int res = TTF_Init();
00322     if(res == -1) {
00323         ERR_FT << "Could not initialize true type fonts\n";
00324         throw error();
00325     } else {
00326         LOG_FT << "Initialized true type fonts\n";
00327     }
00328 
00329     init();
00330 }
00331 
00332 manager::~manager()
00333 {
00334     deinit();
00335 
00336     clear_fonts();
00337     TTF_Quit();
00338 }
00339 
00340 void manager::update_font_path() const
00341 {
00342     deinit();
00343     init();
00344 }
00345 
00346 void manager::init() const
00347 {
00348 #ifdef CAIRO_HAS_FT_FONT
00349     if (!FcConfigAppFontAddDir(FcConfigGetCurrent(),
00350         reinterpret_cast<const FcChar8 *>((game_config::path + "/fonts").c_str())))
00351     {
00352         ERR_FT << "Could not load the true type fonts\n";
00353         throw error();
00354     }
00355 #endif
00356 
00357 #if CAIRO_HAS_WIN32_FONT
00358     foreach(const std::string& path, get_binary_paths("fonts")) {
00359         std::vector<std::string> files;
00360         get_files_in_dir(path, &files, NULL, ENTIRE_FILE_PATH);
00361         foreach(const std::string& file, files)
00362             if(file.substr(file.length() - 4) == ".ttf" || file.substr(file.length() - 4) == ".ttc")
00363                 AddFontResource(file.c_str());
00364     }
00365 #endif
00366 }
00367 
00368 void manager::deinit() const
00369 {
00370 #ifdef CAIRO_HAS_FT_FONT
00371     FcConfigAppFontClear(FcConfigGetCurrent());
00372 #endif
00373 
00374 #if CAIRO_HAS_WIN32_FONT
00375     foreach(const std::string& path, get_binary_paths("fonts")) {
00376         std::vector<std::string> files;
00377         get_files_in_dir(path, &files, NULL, ENTIRE_FILE_PATH);
00378         foreach(const std::string& file, files)
00379             if(file.substr(file.length() - 4) == ".ttf" || file.substr(file.length() - 4) == ".ttc")
00380                 RemoveFontResource(file.c_str());
00381     }
00382 #endif
00383 }
00384 
00385 //structure used to describe a font, and the subset of the Unicode character
00386 //set it covers.
00387 struct subset_descriptor
00388 {
00389     subset_descriptor() :
00390         name(),
00391         present_codepoints()
00392     {
00393     }
00394 
00395     std::string name;
00396     typedef std::pair<int, int> range;
00397     std::vector<range> present_codepoints;
00398 };
00399 
00400 //sets the font list to be used.
00401 static void set_font_list(const std::vector<subset_descriptor>& fontlist)
00402 {
00403     clear_fonts();
00404 
00405     std::vector<subset_descriptor>::const_iterator itor;
00406     for(itor = fontlist.begin(); itor != fontlist.end(); ++itor) {
00407         // Insert fonts only if the font file exists
00408         if(game_config::path.empty() == false) {
00409             if(!file_exists(game_config::path + "/fonts/" + itor->name)) {
00410                 if(!file_exists("fonts/" + itor->name)) {
00411                     if(!file_exists(itor->name)) {
00412                     WRN_FT << "Failed opening font file '" << itor->name << "': No such file or directory\n";
00413                     continue;
00414                     }
00415                 }
00416             }
00417         } else {
00418             if(!file_exists("fonts/" + itor->name)) {
00419                 if(!file_exists(itor->name)) {
00420                     WRN_FT << "Failed opening font file '" << itor->name << "': No such file or directory\n";
00421                     continue;
00422                 }
00423             }
00424         }
00425         const subset_id subset = font_names.size();
00426         font_names.push_back(itor->name);
00427 
00428         foreach (const subset_descriptor::range &cp_range, itor->present_codepoints) {
00429             char_blocks.insert(cp_range.first, cp_range.second, subset);
00430         }
00431     }
00432     char_blocks.compress();
00433 }
00434 
00435 const SDL_Color NORMAL_COLOR = {0xDD,0xDD,0xDD,0},
00436                 GRAY_COLOR   = {0x77,0x77,0x77,0},
00437                 LOBBY_COLOR  = {0xBB,0xBB,0xBB,0},
00438                 GOOD_COLOR   = {0x00,0xFF,0x00,0},
00439                 BAD_COLOR    = {0xFF,0x00,0x00,0},
00440                 BLACK_COLOR  = {0x00,0x00,0x00,0},
00441                 YELLOW_COLOR = {0xFF,0xFF,0x00,0},
00442                 BUTTON_COLOR = {0xBC,0xB0,0x88,0},
00443                 PETRIFIED_COLOR = {0xA0,0xA0,0xA0,0},
00444                 TITLE_COLOR  = {0xBC,0xB0,0x88,0},
00445                 LABEL_COLOR  = {0x6B,0x8C,0xFF,0},
00446                 BIGMAP_COLOR = {0xFF,0xFF,0xFF,0};
00447 const SDL_Color DISABLED_COLOR = inverse(PETRIFIED_COLOR);
00448 
00449 namespace {
00450 
00451 static const size_t max_text_line_width = 4096;
00452 
00453 class text_surface
00454 {
00455 public:
00456     text_surface(std::string const &str, int size, SDL_Color color, int style);
00457     text_surface(int size, SDL_Color color, int style);
00458     void set_text(std::string const &str);
00459 
00460     void measure() const;
00461     size_t width() const;
00462     size_t height() const;
00463 #ifdef  HAVE_FRIBIDI
00464     bool is_rtl() const { return is_rtl_; } // Right-To-Left alignment
00465 #endif
00466     std::vector<surface> const & get_surfaces() const;
00467 
00468     bool operator==(text_surface const &t) const {
00469         return hash_ == t.hash_ && font_size_ == t.font_size_
00470             && color_ == t.color_ && style_ == t.style_ && str_ == t.str_;
00471     }
00472     bool operator!=(text_surface const &t) const { return !operator==(t); }
00473 private:
00474     int hash_;
00475     int font_size_;
00476     SDL_Color color_;
00477     int style_;
00478     mutable int w_, h_;
00479     std::string str_;
00480     mutable bool initialized_;
00481     mutable std::vector<text_chunk> chunks_;
00482     mutable std::vector<surface> surfs_;
00483 #ifdef  HAVE_FRIBIDI
00484     bool is_rtl_;
00485     void bidi_cvt();
00486 #endif
00487     void hash();
00488 };
00489 
00490 #ifdef  HAVE_FRIBIDI
00491 void text_surface::bidi_cvt()
00492 {
00493     char        *c_str = const_cast<char *>(str_.c_str());  // fribidi forgot const...
00494     FriBidiStrIndex len = str_.length();
00495     FriBidiChar *bidi_logical = new FriBidiChar[len + 2];
00496     FriBidiChar *bidi_visual = new FriBidiChar[len + 2];
00497     char        *utf8str = new char[4*len + 1]; //assume worst case here (all 4 Byte characters)
00498     FriBidiCharType base_dir = FRIBIDI_TYPE_ON;
00499     FriBidiStrIndex n;
00500 
00501 
00502 #ifdef  OLD_FRIBIDI
00503     n = fribidi_utf8_to_unicode (c_str, len, bidi_logical);
00504 #else
00505     n = fribidi_charset_to_unicode(FRIBIDI_CHAR_SET_UTF8, c_str, len, bidi_logical);
00506 #endif
00507     fribidi_log2vis(bidi_logical, n, &base_dir, bidi_visual, NULL, NULL, NULL);
00508 #ifdef  OLD_FRIBIDI
00509     fribidi_unicode_to_utf8 (bidi_visual, n, utf8str);
00510 #else
00511     fribidi_unicode_to_charset(FRIBIDI_CHAR_SET_UTF8, bidi_visual, n, utf8str);
00512 #endif
00513     is_rtl_ = base_dir == FRIBIDI_TYPE_RTL;
00514     str_ = std::string(utf8str);
00515     delete[] bidi_logical;
00516     delete[] bidi_visual;
00517     delete[] utf8str;
00518 }
00519 #endif
00520 
00521 text_surface::text_surface(std::string const &str, int size,
00522         SDL_Color color, int style) :
00523     hash_(0),
00524     font_size_(size),
00525     color_(color),
00526     style_(style),
00527     w_(-1),
00528     h_(-1),
00529     str_(str),
00530     initialized_(false),
00531     chunks_(),
00532     surfs_()
00533 #ifdef  HAVE_FRIBIDI
00534     ,is_rtl_(false)
00535 #endif
00536 {
00537 #ifdef  HAVE_FRIBIDI
00538     bidi_cvt();
00539 #endif
00540     hash();
00541 }
00542 
00543 text_surface::text_surface(int size, SDL_Color color, int style) :
00544     hash_(0),
00545     font_size_(size),
00546     color_(color),
00547     style_(style),
00548     w_(-1),
00549     h_(-1),
00550     str_(),
00551     initialized_(false),
00552     chunks_(),
00553     surfs_()
00554 #ifdef  HAVE_FRIBIDI
00555     ,is_rtl_(false)
00556 #endif
00557 {
00558 }
00559 
00560 void text_surface::set_text(std::string const &str)
00561 {
00562     initialized_ = false;
00563     w_ = -1;
00564     h_ = -1;
00565     str_ = str;
00566 #ifdef  HAVE_FRIBIDI
00567     bidi_cvt();
00568 #endif
00569     hash();
00570 }
00571 
00572 void text_surface::hash()
00573 {
00574     int h = 0;
00575     for(std::string::const_iterator it = str_.begin(), it_end = str_.end(); it != it_end; ++it)
00576         h = ((h << 9) | (h >> (sizeof(int) * 8 - 9))) ^ (*it);
00577     hash_ = h;
00578 }
00579 
00580 void text_surface::measure() const
00581 {
00582     w_ = 0;
00583     h_ = 0;
00584 
00585     foreach (text_chunk const &chunk, chunks_)
00586     {
00587         TTF_Font* ttfont = get_font(font_id(chunk.subset, font_size_));
00588         if(ttfont == NULL)
00589             continue;
00590         font_style_setter const style_setter(ttfont, style_);
00591 
00592         int w, h;
00593         TTF_SizeUTF8(ttfont, chunk.text.c_str(), &w, &h);
00594         w_ += w;
00595         h_ = std::max<int>(h_, h);
00596     }
00597 }
00598 
00599 size_t text_surface::width() const
00600 {
00601     if (w_ == -1) {
00602         if(chunks_.empty())
00603             chunks_ = split_text(str_);
00604         measure();
00605     }
00606     return w_;
00607 }
00608 
00609 size_t text_surface::height() const
00610 {
00611     if (h_ == -1) {
00612         if(chunks_.empty())
00613             chunks_ = split_text(str_);
00614         measure();
00615     }
00616     return h_;
00617 }
00618 
00619 std::vector<surface> const &text_surface::get_surfaces() const
00620 {
00621     if(initialized_)
00622         return surfs_;
00623 
00624     initialized_ = true;
00625 
00626     // Impose a maximal number of characters for a text line. Do now draw
00627     // any text longer that that, to prevent a SDL buffer overflow
00628     if(width() > max_text_line_width)
00629         return surfs_;
00630 
00631     foreach (text_chunk const &chunk, chunks_)
00632     {
00633         TTF_Font* ttfont = get_font(font_id(chunk.subset, font_size_));
00634         if (ttfont == NULL)
00635             continue;
00636         font_style_setter const style_setter(ttfont, style_);
00637 
00638         surface s = surface(TTF_RenderUTF8_Blended(ttfont, chunk.text.c_str(), color_));
00639         if(!s.null())
00640             surfs_.push_back(s);
00641     }
00642 
00643     return surfs_;
00644 }
00645 
00646 class text_cache
00647 {
00648 public:
00649     static text_surface &find(text_surface const &t);
00650     static void resize(unsigned int size);
00651 private:
00652     typedef std::list< text_surface > text_list;
00653     static text_list cache_;
00654     static unsigned int max_size_;
00655 };
00656 
00657 text_cache::text_list text_cache::cache_;
00658 unsigned int text_cache::max_size_ = 50;
00659 
00660 void text_cache::resize(unsigned int size)
00661 {
00662     DBG_FT << "Text cache: resize from: " << max_size_ << " to: "
00663         << size << " items in cache: " << cache_.size() << '\n';
00664 
00665     while(size < cache_.size()) {
00666         cache_.pop_back();
00667     }
00668     max_size_ = size;
00669 }
00670 
00671 
00672 text_surface &text_cache::find(text_surface const &t)
00673 {
00674     static size_t lookup_ = 0, hit_ = 0;
00675     text_list::iterator it_bgn = cache_.begin(), it_end = cache_.end();
00676     text_list::iterator it = std::find(it_bgn, it_end, t);
00677     if (it != it_end) {
00678         cache_.splice(it_bgn, cache_, it);
00679         ++hit_;
00680     } else {
00681         if (cache_.size() >= max_size_)
00682             cache_.pop_back();
00683         cache_.push_front(t);
00684     }
00685     if (++lookup_ % 1000 == 0) {
00686         DBG_FT << "Text cache: " << lookup_ << " lookups, " << (hit_ / 10) << "% hits\n";
00687         hit_ = 0;
00688     }
00689     return cache_.front();
00690 }
00691 
00692 }
00693 
00694 static surface render_text(const std::string& text, int fontsize, const SDL_Color& color, int style, bool use_markup)
00695 {
00696     // we keep blank lines and spaces (may be wanted for indentation)
00697     const std::vector<std::string> lines = utils::split(text, '\n', 0);
00698     std::vector<std::vector<surface> > surfaces;
00699     surfaces.reserve(lines.size());
00700     size_t width = 0, height = 0;
00701 
00702     for(std::vector< std::string >::const_iterator ln = lines.begin(), ln_end = lines.end(); ln != ln_end; ++ln) {
00703 
00704         int sz = fontsize;
00705         int text_style = style;
00706 
00707         std::string::const_iterator after_markup = use_markup ?
00708             parse_markup(ln->begin(), ln->end(), &sz, NULL, &text_style) : ln->begin();
00709         text_surface txt_surf(sz, color, text_style);
00710 
00711         if (after_markup == ln->end() && (ln+1 != ln_end || lines.begin()+1 == ln_end)) {
00712             // we replace empty line by a space (to have a line height)
00713             // except for the last line if we have several
00714             txt_surf.set_text(" ");
00715         } else if (after_markup == ln->begin()) {
00716             // simple case, no markup to skip
00717             txt_surf.set_text(*ln);
00718         } else  {
00719             const std::string line(after_markup,ln->end());
00720             txt_surf.set_text(line);
00721         }
00722 
00723         const text_surface& cached_surf = text_cache::find(txt_surf);
00724         const std::vector<surface>&res = cached_surf.get_surfaces();
00725 
00726         if (!res.empty()) {
00727             surfaces.push_back(res);
00728             width = std::max<size_t>(cached_surf.width(), width);
00729             height += cached_surf.height();
00730         }
00731     }
00732 
00733     if (surfaces.empty()) {
00734         return surface();
00735     } else if (surfaces.size() == 1 && surfaces.front().size() == 1) {
00736         surface surf = surfaces.front().front();
00737         SDL_SetAlpha(surf, SDL_SRCALPHA | SDL_RLEACCEL, SDL_ALPHA_OPAQUE);
00738         return surf;
00739     } else {
00740 
00741         surface res(create_compatible_surface(surfaces.front().front(),width,height));
00742         if (res.null())
00743             return res;
00744 
00745         size_t ypos = 0;
00746         for(std::vector< std::vector<surface> >::const_iterator i = surfaces.begin(),
00747             i_end = surfaces.end(); i != i_end; ++i) {
00748             size_t xpos = 0;
00749             size_t height = 0;
00750 
00751             for(std::vector<surface>::const_iterator j = i->begin(),
00752                     j_end = i->end(); j != j_end; ++j) {
00753                 SDL_SetAlpha(*j, 0, 0); // direct blit without alpha blending
00754                 SDL_Rect dstrect = create_rect(xpos, ypos, 0, 0);
00755                 sdl_blit(*j, NULL, res, &dstrect);
00756                 xpos += (*j)->w;
00757                 height = std::max<size_t>((*j)->h, height);
00758             }
00759             ypos += height;
00760         }
00761 
00762         return res;
00763     }
00764 }
00765 
00766 
00767 surface get_rendered_text(const std::string& str, int size, const SDL_Color& color, int style)
00768 {
00769     // TODO maybe later also to parse markup here, but a lot to check
00770     return render_text(str, size, color, style, false);
00771 }
00772 
00773 SDL_Rect draw_text_line(surface gui_surface, const SDL_Rect& area, int size,
00774            const SDL_Color& color, const std::string& text,
00775            int x, int y, bool use_tooltips, int style)
00776 {
00777     if (gui_surface.null()) {
00778         text_surface const &u = text_cache::find(text_surface(text, size, color, style));
00779         return create_rect(0, 0, u.width(), u.height());
00780     }
00781 
00782     if(area.w == 0) {  // no place to draw
00783         return create_rect(0, 0, 0, 0);
00784     }
00785 
00786     const std::string etext = make_text_ellipsis(text, size, area.w);
00787 
00788     // for the main current use, we already parsed markup
00789     surface surface(render_text(etext,size,color,style,false));
00790     if(surface == NULL) {
00791         return create_rect(0, 0, 0, 0);
00792     }
00793 
00794     SDL_Rect dest;
00795     if(x!=-1) {
00796         dest.x = x;
00797 #ifdef  HAVE_FRIBIDI
00798         // Oron -- Conditional, until all draw_text_line calls have fixed area parameter
00799         if(getenv("NO_RTL") == NULL) {
00800             bool is_rtl = text_cache::find(text_surface(text, size, color, style)).is_rtl();
00801             if(is_rtl)
00802                 dest.x = area.x + area.w - surface->w - (x - area.x);
00803         }
00804 #endif
00805     } else
00806         dest.x = (area.w/2)-(surface->w/2);
00807     if(y!=-1)
00808         dest.y = y;
00809     else
00810         dest.y = (area.h/2)-(surface->h/2);
00811     dest.w = surface->w;
00812     dest.h = surface->h;
00813 
00814     if(line_width(text, size) > area.w) {
00815         tooltips::add_tooltip(dest,text);
00816     }
00817 
00818     if(dest.x + dest.w > area.x + area.w) {
00819         dest.w = area.x + area.w - dest.x;
00820     }
00821 
00822     if(dest.y + dest.h > area.y + area.h) {
00823         dest.h = area.y + area.h - dest.y;
00824     }
00825 
00826     if(gui_surface != NULL) {
00827         SDL_Rect src = dest;
00828         src.x = 0;
00829         src.y = 0;
00830         sdl_blit(surface,&src,gui_surface,&dest);
00831     }
00832 
00833     if(use_tooltips) {
00834         tooltips::add_tooltip(dest,text);
00835     }
00836 
00837     return dest;
00838 }
00839 
00840 int get_max_height(int size)
00841 {
00842     // Only returns the maximal size of the first font
00843     TTF_Font* const font = get_font(font_id(0, size));
00844     if(font == NULL)
00845         return 0;
00846     return TTF_FontHeight(font);
00847 }
00848 
00849 int line_width(const std::string& line, int font_size, int style)
00850 {
00851     return line_size(line,font_size,style).w;
00852 }
00853 
00854 SDL_Rect line_size(const std::string& line, int font_size, int style)
00855 {
00856     line_size_cache_map& cache = line_size_cache[style][font_size];
00857 
00858     const line_size_cache_map::const_iterator i = cache.find(line);
00859     if(i != cache.end()) {
00860         return i->second;
00861     }
00862 
00863     SDL_Rect res;
00864 
00865     const SDL_Color col = { 0, 0, 0, 0 };
00866     text_surface s(line, font_size, col, style);
00867 
00868     res.w = s.width();
00869     res.h = s.height();
00870     res.x = res.y = 0;
00871 
00872     cache.insert(std::pair<std::string,SDL_Rect>(line,res));
00873     return res;
00874 }
00875 
00876 std::string make_text_ellipsis(const std::string &text, int font_size,
00877     int max_width, int style)
00878 {
00879     static const std::string ellipsis = "...";
00880 
00881     if (line_width(text, font_size, style) <= max_width)
00882         return text;
00883     if(line_width(ellipsis, font_size, style) > max_width)
00884         return "";
00885 
00886     std::string current_substring;
00887 
00888     utils::utf8_iterator itor(text);
00889 
00890     for(; itor != utils::utf8_iterator::end(text); ++itor) {
00891         std::string tmp = current_substring;
00892         tmp.append(itor.substr().first, itor.substr().second);
00893 
00894         if (line_width(tmp + ellipsis, font_size, style) > max_width) {
00895             return current_substring + ellipsis;
00896         }
00897 
00898         current_substring.append(itor.substr().first, itor.substr().second);
00899     }
00900 
00901     return text; // Should not happen
00902 }
00903 
00904 }
00905 
00906 namespace {
00907 
00908 typedef std::map<int, font::floating_label> label_map;
00909 label_map labels;
00910 int label_id = 1;
00911 
00912 std::stack<std::set<int> > label_contexts;
00913 }
00914 
00915 
00916 namespace font {
00917 
00918 floating_label::floating_label(const std::string& text)
00919         : surf_(NULL), buf_(NULL), text_(text),
00920         font_size_(SIZE_NORMAL),
00921         color_(NORMAL_COLOR),   bgcolor_(), bgalpha_(0),
00922         xpos_(0), ypos_(0),
00923         xmove_(0), ymove_(0), lifetime_(-1),
00924         width_(-1), height_(-1),
00925         clip_rect_(screen_area()),
00926         alpha_change_(0), visible_(true), align_(CENTER_ALIGN),
00927         border_(0), scroll_(ANCHOR_LABEL_SCREEN), use_markup_(true)
00928 {}
00929 
00930 void floating_label::move(double xmove, double ymove)
00931 {
00932     xpos_ += xmove;
00933     ypos_ += ymove;
00934 }
00935 
00936 int floating_label::xpos(size_t width) const
00937 {
00938     int xpos = int(xpos_);
00939     if(align_ == font::CENTER_ALIGN) {
00940         xpos -= width/2;
00941     } else if(align_ == font::RIGHT_ALIGN) {
00942         xpos -= width;
00943     }
00944 
00945     return xpos;
00946 }
00947 
00948 surface floating_label::create_surface()
00949 {
00950     if (surf_.null()) {
00951         font::ttext text;
00952         text.set_foreground_color((color_.r << 24) | (color_.g << 16) | (color_.b << 8) | 255);
00953         text.set_font_size(font_size_);
00954         text.set_maximum_width(width_ < 0 ? clip_rect_.w : width_);
00955         text.set_maximum_height(height_ < 0 ? clip_rect_.h : height_);
00956 
00957         //ignore last '\n'
00958         if(!text_.empty() && *(text_.rbegin()) == '\n'){
00959             text.set_text(std::string(text_.begin(), text_.end()-1), use_markup_);
00960         } else {
00961             text.set_text(text_, use_markup_);
00962         }
00963 
00964         surface foreground = text.render();
00965 
00966         if(foreground == NULL) {
00967             ERR_FT << "could not create floating label's text" << std::endl;
00968             return NULL;
00969         }
00970 
00971         // combine foreground text with its background
00972         if(bgalpha_ != 0) {
00973             // background is a dark tootlip box
00974             surface background = create_neutral_surface(foreground->w + border_*2, foreground->h + border_*2);
00975 
00976             if (background == NULL) {
00977                 ERR_FT << "could not create tooltip box" << std::endl;
00978                 surf_ = create_optimized_surface(foreground);
00979                 return surf_;
00980             }
00981 
00982             Uint32 color = SDL_MapRGBA(foreground->format, bgcolor_.r,bgcolor_.g, bgcolor_.b, bgalpha_);
00983             sdl_fill_rect(background,NULL, color);
00984 
00985             // we make the text less transparent, because the blitting on the
00986             // dark background will darken the anti-aliased part.
00987             // This 1.13 value seems to restore the brightness of version 1.4
00988             // (where the text was blitted directly on screen)
00989             foreground = adjust_surface_alpha(foreground, ftofxp(1.13), false);
00990 
00991             SDL_Rect r = create_rect( border_, border_, 0, 0);
00992             SDL_SetAlpha(foreground,SDL_SRCALPHA,SDL_ALPHA_OPAQUE);
00993             blit_surface(foreground, NULL, background, &r);
00994 
00995             surf_ = create_optimized_surface(background);
00996             // RLE compression seems less efficient for big semi-transparent area
00997             // so, remove it for this case, but keep the optimized display format
00998             SDL_SetAlpha(surf_,SDL_SRCALPHA,SDL_ALPHA_OPAQUE);
00999         }
01000         else {
01001             // background is blurred shadow of the text
01002             surface background = create_neutral_surface
01003                 (foreground->w + 4, foreground->h + 4);
01004             sdl_fill_rect(background, NULL, 0);
01005             SDL_Rect r = { 2, 2, 0, 0 };
01006             blit_surface(foreground, NULL, background, &r);
01007             background = shadow_image(background, false);
01008 
01009             if (background == NULL) {
01010                 ERR_FT << "could not create floating label's shadow" << std::endl;
01011                 surf_ = create_optimized_surface(foreground);
01012                 return surf_;
01013             }
01014             SDL_SetAlpha(foreground,SDL_SRCALPHA,SDL_ALPHA_OPAQUE);
01015             blit_surface(foreground, NULL, background, &r);
01016             surf_ = create_optimized_surface(background);
01017         }
01018     }
01019 
01020     return surf_;
01021 }
01022 
01023 void floating_label::draw(surface screen)
01024 {
01025     if(!visible_) {
01026         buf_.assign(NULL);
01027         return;
01028     }
01029 
01030     create_surface();
01031     if(surf_ == NULL) {
01032         return;
01033     }
01034 
01035     if(buf_ == NULL) {
01036         buf_.assign(create_compatible_surface(screen, surf_->w, surf_->h));
01037         if(buf_ == NULL) {
01038             return;
01039         }
01040     }
01041 
01042     if(screen == NULL) {
01043         return;
01044     }
01045 
01046     SDL_Rect rect = create_rect(xpos(surf_->w), ypos_, surf_->w, surf_->h);
01047     const clip_rect_setter clip_setter(screen, &clip_rect_);
01048     sdl_blit(screen,&rect,buf_,NULL);
01049     sdl_blit(surf_,NULL,screen,&rect);
01050 
01051     update_rect(rect);
01052 }
01053 
01054 void floating_label::undraw(surface screen)
01055 {
01056     if(screen == NULL || buf_ == NULL) {
01057         return;
01058     }
01059 
01060     SDL_Rect rect = create_rect(xpos(surf_->w), ypos_, surf_->w, surf_->h);
01061     const clip_rect_setter clip_setter(screen, &clip_rect_);
01062     sdl_blit(buf_,NULL,screen,&rect);
01063 
01064     update_rect(rect);
01065 
01066     move(xmove_,ymove_);
01067     if(lifetime_ > 0) {
01068         --lifetime_;
01069         if(alpha_change_ != 0 && (xmove_ != 0.0 || ymove_ != 0.0) && surf_ != NULL) {
01070             // fade out moving floating labels
01071             // note that we don't optimize these surfaces since they will always change
01072             surf_.assign(adjust_surface_alpha_add(surf_,alpha_change_,false));
01073         }
01074     }
01075 }
01076 
01077 int add_floating_label(const floating_label& flabel)
01078 {
01079     if(label_contexts.empty()) {
01080         return 0;
01081     }
01082 
01083     ++label_id;
01084     labels.insert(std::pair<int, floating_label>(label_id, flabel));
01085     label_contexts.top().insert(label_id);
01086     return label_id;
01087 }
01088 
01089 void move_floating_label(int handle, double xmove, double ymove)
01090 {
01091     const label_map::iterator i = labels.find(handle);
01092     if(i != labels.end()) {
01093         i->second.move(xmove,ymove);
01094     }
01095 }
01096 
01097 void scroll_floating_labels(double xmove, double ymove)
01098 {
01099     for(label_map::iterator i = labels.begin(); i != labels.end(); ++i) {
01100         if(i->second.scroll() == ANCHOR_LABEL_MAP) {
01101             i->second.move(xmove,ymove);
01102         }
01103     }
01104 }
01105 
01106 void remove_floating_label(int handle)
01107 {
01108     const label_map::iterator i = labels.find(handle);
01109     if(i != labels.end()) {
01110         if(label_contexts.empty() == false) {
01111             label_contexts.top().erase(i->first);
01112         }
01113 
01114         labels.erase(i);
01115     }
01116 }
01117 
01118 void show_floating_label(int handle, bool value)
01119 {
01120     const label_map::iterator i = labels.find(handle);
01121     if(i != labels.end()) {
01122         i->second.show(value);
01123     }
01124 }
01125 
01126 SDL_Rect get_floating_label_rect(int handle)
01127 {
01128     const label_map::iterator i = labels.find(handle);
01129     if(i != labels.end()) {
01130         const surface surf = i->second.create_surface();
01131         if(surf != NULL) {
01132             return create_rect(0, 0, surf->w, surf->h);
01133         }
01134     }
01135 
01136     return empty_rect;
01137 }
01138 
01139 floating_label_context::floating_label_context()
01140 {
01141     surface const screen = SDL_GetVideoSurface();
01142     if(screen != NULL) {
01143         draw_floating_labels(screen);
01144     }
01145 
01146     label_contexts.push(std::set<int>());
01147 }
01148 
01149 floating_label_context::~floating_label_context()
01150 {
01151     const std::set<int>& labels = label_contexts.top();
01152     for(std::set<int>::const_iterator i = labels.begin(); i != labels.end(); ) {
01153         remove_floating_label(*i++);
01154     }
01155 
01156     label_contexts.pop();
01157 
01158     surface const screen = SDL_GetVideoSurface();
01159     if(screen != NULL) {
01160         undraw_floating_labels(screen);
01161     }
01162 }
01163 
01164 void draw_floating_labels(surface screen)
01165 {
01166     if(label_contexts.empty()) {
01167         return;
01168     }
01169 
01170     const std::set<int>& context = label_contexts.top();
01171 
01172     //draw the labels in the order they were added, so later added labels (likely to be tooltips)
01173     //are displayed over earlier added labels.
01174     for(label_map::iterator i = labels.begin(); i != labels.end(); ++i) {
01175         if(context.count(i->first) > 0) {
01176             i->second.draw(screen);
01177         }
01178     }
01179 }
01180 
01181 void undraw_floating_labels(surface screen)
01182 {
01183     if(label_contexts.empty()) {
01184         return;
01185     }
01186 
01187     std::set<int>& context = label_contexts.top();
01188 
01189     //undraw labels in reverse order, so that a LIFO process occurs, and the screen is restored
01190     //into the exact state it started in.
01191     for(label_map::reverse_iterator i = labels.rbegin(); i != labels.rend(); ++i) {
01192         if(context.count(i->first) > 0) {
01193             i->second.undraw(screen);
01194         }
01195     }
01196 
01197     //remove expired labels
01198     for(label_map::iterator j = labels.begin(); j != labels.end(); ) {
01199         if(context.count(j->first) > 0 && j->second.expired()) {
01200             context.erase(j->first);
01201             labels.erase(j++);
01202         } else {
01203             ++j;
01204         }
01205     }
01206 }
01207 
01208 }
01209 
01210 static bool add_font_to_fontlist(config &fonts_config,
01211     std::vector<font::subset_descriptor>& fontlist, const std::string& name)
01212 {
01213     config &font = fonts_config.find_child("font", "name", name);
01214     if (!font)
01215         return false;
01216 
01217         fontlist.push_back(font::subset_descriptor());
01218         fontlist.back().name = name;
01219         std::vector<std::string> ranges = utils::split(font["codepoints"]);
01220 
01221         for(std::vector<std::string>::const_iterator itor = ranges.begin();
01222                 itor != ranges.end(); ++itor) {
01223 
01224             std::vector<std::string> r = utils::split(*itor, '-');
01225             if(r.size() == 1) {
01226                 size_t r1 = lexical_cast_default<size_t>(r[0], 0);
01227                 fontlist.back().present_codepoints.push_back(std::pair<size_t, size_t>(r1, r1));
01228             } else if(r.size() == 2) {
01229                 size_t r1 = lexical_cast_default<size_t>(r[0], 0);
01230                 size_t r2 = lexical_cast_default<size_t>(r[1], 0);
01231 
01232                 fontlist.back().present_codepoints.push_back(std::pair<size_t, size_t>(r1, r2));
01233             }
01234         }
01235 
01236         return true;
01237     }
01238 
01239 namespace font {
01240 
01241 namespace {
01242     t_string family_order;
01243 } // namespace
01244 
01245 bool load_font_config()
01246 {
01247     //read font config separately, so we do not have to re-read the whole
01248     //config when changing languages
01249     config cfg;
01250     try {
01251         scoped_istream stream = preprocess_file(get_wml_location("hardwired/fonts.cfg"));
01252         read(cfg, *stream);
01253     } catch(config::error &e) {
01254         ERR_FT << "could not read fonts.cfg:\n"
01255                << e.message << '\n';
01256         return false;
01257     }
01258 
01259     config &fonts_config = cfg.child("fonts");
01260     if (!fonts_config)
01261         return false;
01262 
01263     std::set<std::string> known_fonts;
01264     foreach (const config &font, fonts_config.child_range("font")) {
01265         known_fonts.insert(font["name"]);
01266     }
01267 
01268     family_order = fonts_config["family_order"];
01269     const std::vector<std::string> font_order = utils::split(fonts_config["order"]);
01270     std::vector<font::subset_descriptor> fontlist;
01271     std::vector<std::string>::const_iterator font;
01272     for(font = font_order.begin(); font != font_order.end(); ++font) {
01273         add_font_to_fontlist(fonts_config, fontlist, *font);
01274         known_fonts.erase(*font);
01275     }
01276     std::set<std::string>::const_iterator kfont;
01277     for(kfont = known_fonts.begin(); kfont != known_fonts.end(); ++kfont) {
01278         add_font_to_fontlist(fonts_config, fontlist, *kfont);
01279     }
01280 
01281     if(fontlist.empty())
01282         return false;
01283 
01284     font::set_font_list(fontlist);
01285     return true;
01286 }
01287 
01288 const t_string& get_font_families()
01289 {
01290     return family_order;
01291 }
01292 
01293 void cache_mode(CACHE mode)
01294 {
01295     if(mode == CACHE_LOBBY) {
01296         text_cache::resize(1000);
01297     } else {
01298         text_cache::resize(50);
01299     }
01300 }
01301 
01302 
01303 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines

Generated by doxygen 1.7.1 on Thu May 24 2012 01:02:36 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs