00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
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
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
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
00113 if (i != cbmap.begin()) {
00114 cbmap_t::iterator j = i;
00115 --j;
00116 if (first <= j->second.first ) {
00117 insert(j->second.first + 1, last, id);
00118 return;
00119 }
00120 }
00121 if (i != cbmap.end()) {
00122 if ( 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
00131
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
00153 if (i != cbmap.begin()) {
00154 --i;
00155 if (ch <= i->second.first )
00156 return i->second.second;
00157 }
00158 return -1;
00159 }
00160 };
00161
00162 static char_block_map char_blocks;
00163
00164
00165 typedef std::map<std::string,SDL_Rect> line_size_cache_map;
00166
00167
00168 static std::map<int,std::map<int,line_size_cache_map> > line_size_cache;
00169
00170
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
00284
00285
00286 #if 0
00287
00288
00289
00290
00291 if((style&TTF_STYLE_UNDERLINE) != 0) {
00292
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
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
00386
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
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
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_; }
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());
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];
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
00627
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
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
00713
00714 txt_surf.set_text(" ");
00715 } else if (after_markup == ln->begin()) {
00716
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);
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
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) {
00783 return create_rect(0, 0, 0, 0);
00784 }
00785
00786 const std::string etext = make_text_ellipsis(text, size, area.w);
00787
00788
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
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
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;
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
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
00972 if(bgalpha_ != 0) {
00973
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
00986
00987
00988
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
00997
00998 SDL_SetAlpha(surf_,SDL_SRCALPHA,SDL_ALPHA_OPAQUE);
00999 }
01000 else {
01001
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
01071
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
01173
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
01190
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
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 }
01244
01245 bool load_font_config()
01246 {
01247
01248
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 }