00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016 #include "global.hpp"
00017
00018 #include "config.hpp"
00019 #include "filesystem.hpp"
00020 #include "font.hpp"
00021 #include "game_config.hpp"
00022 #include "log.hpp"
00023 #include "sdl_utils.hpp"
00024 #include "tooltips.hpp"
00025 #include "util.hpp"
00026 #include "video.hpp"
00027 #include "serialization/parser.hpp"
00028 #include "serialization/preprocessor.hpp"
00029 #include "serialization/string_utils.hpp"
00030 #include "marked-up_text.hpp"
00031
00032 #include <algorithm>
00033 #include <cstdio>
00034 #include <iostream>
00035 #include <list>
00036 #include <map>
00037 #include <set>
00038 #include <sstream>
00039 #include <stack>
00040 #include <string>
00041
00042 #define DBG_FT LOG_STREAM(debug, display)
00043 #define LOG_FT LOG_STREAM(info, display)
00044 #define WRN_FT LOG_STREAM(warn, display)
00045 #define ERR_FT LOG_STREAM(err, display)
00046
00047 #ifdef HAVE_FRIBIDI
00048 #include <fribidi/fribidi.h>
00049
00050 #else
00051
00052 #endif
00053
00054 namespace {
00055
00056
00057 typedef int subset_id;
00058
00059 struct font_id
00060 {
00061 font_id(subset_id subset, int size) : subset(subset), size(size) {};
00062 bool operator==(const font_id& o) const
00063 {
00064 return subset == o.subset && size == o.size;
00065 };
00066 bool operator<(const font_id& o) const
00067 {
00068 return subset < o.subset || (subset == o.subset && size < o.size);
00069 };
00070
00071 subset_id subset;
00072 int size;
00073 };
00074
00075 std::map<font_id, TTF_Font*> font_table;
00076 std::vector<std::string> font_names;
00077
00078 struct text_chunk
00079 {
00080 text_chunk(subset_id subset) : subset(subset) {}
00081 text_chunk(subset_id subset, std::string const & text) : subset(subset), text(text) {}
00082 text_chunk(subset_id subset, ucs2_string const & ucs2_text) : subset(subset), ucs2_text(ucs2_text) {}
00083 text_chunk(subset_id subset, std::string const & text, ucs2_string const & ucs2_text) : subset(subset), text(text), ucs2_text(ucs2_text) {}
00084
00085 bool operator==(text_chunk const & t) const { return subset == t.subset && ucs2_text == t.ucs2_text; }
00086 bool operator!=(text_chunk const & t) const { return !operator==(t); }
00087
00088 subset_id subset;
00089
00090 std::string text;
00091 ucs2_string ucs2_text;
00092 };
00093
00094 std::vector<subset_id> font_map;
00095
00096
00097 typedef std::map<std::string,SDL_Rect> line_size_cache_map;
00098
00099
00100 std::map<int,std::map<int,line_size_cache_map> > line_size_cache;
00101
00102 }
00103
00104
00105 static std::vector<text_chunk> split_text(std::string const & utf8_text) {
00106 text_chunk current_chunk(0);
00107 std::vector<text_chunk> chunks;
00108
00109 if (utf8_text.empty())
00110 return chunks;
00111
00112 try {
00113 utils::utf8_iterator ch(utf8_text);
00114 if(size_t(*ch) < font_map.size() && font_map[size_t(*ch)] >= 0) {
00115 current_chunk.subset = font_map[size_t(*ch)];
00116 }
00117 for(utils::utf8_iterator end = utils::utf8_iterator::end(utf8_text); ch != end; ++ch) {
00118 if(size_t(*ch) < font_map.size() &&
00119 font_map[size_t(*ch)] >= 0 &&
00120 font_map[size_t(*ch)] != current_chunk.subset) {
00121
00122 current_chunk.ucs2_text.push_back(0);
00123 chunks.push_back(current_chunk);
00124 current_chunk.text = "";
00125 current_chunk.ucs2_text.clear();
00126 current_chunk.subset = font_map[size_t(*ch)];
00127 }
00128 current_chunk.ucs2_text.push_back(static_cast<Uint16>(*ch));
00129 current_chunk.text.append(ch.substr().first, ch.substr().second);
00130 }
00131 if (!current_chunk.text.empty()) {
00132 chunks.push_back(current_chunk);
00133 }
00134 }
00135 catch(utils::invalid_utf8_exception e) {
00136 WRN_FT << "Invalid UTF-8 string: \"" << utf8_text << "\"\n";
00137 }
00138 return chunks;
00139 }
00140
00141 static TTF_Font* open_font(const std::string& fname, int size)
00142 {
00143 std::string name;
00144 if(!game_config::path.empty()) {
00145 name = game_config::path + "/fonts/" + fname;
00146 if(!file_exists(name)) {
00147 name = "fonts/" + fname;
00148 if(!file_exists(name)) {
00149 name = fname;
00150 if(!file_exists(name)) {
00151 ERR_FT << "Failed opening font: '" << name << "': No such file or directory\n";
00152 return NULL;
00153 }
00154 }
00155 }
00156
00157 } else {
00158 name = "fonts/" + fname;
00159 if(!file_exists(name)) {
00160 if(!file_exists(fname)) {
00161 ERR_FT << "Failed opening font: '" << name << "': No such file or directory\n";
00162 return NULL;
00163 }
00164 name = fname;
00165 }
00166 }
00167
00168 TTF_Font* font = TTF_OpenFont(name.c_str(),size);
00169 if(font == NULL) {
00170 ERR_FT << "Failed opening font: TTF_OpenFont: " << TTF_GetError() << "\n";
00171 return NULL;
00172 }
00173
00174 return font;
00175 }
00176
00177 static TTF_Font* get_font(font_id id)
00178 {
00179 const std::map<font_id, TTF_Font*>::iterator it = font_table.find(id);
00180 if(it != font_table.end())
00181 return it->second;
00182
00183 if(id.subset < 0 || size_t(id.subset) >= font_names.size())
00184 return NULL;
00185
00186 TTF_Font* font = open_font(font_names[id.subset], id.size);
00187
00188 if(font == NULL)
00189 return NULL;
00190
00191 TTF_SetFontStyle(font,TTF_STYLE_NORMAL);
00192
00193 LOG_FT << "Inserting font...\n";
00194 font_table.insert(std::pair<font_id,TTF_Font*>(id, font));
00195 return font;
00196 }
00197
00198 static void clear_fonts()
00199 {
00200 for(std::map<font_id,TTF_Font*>::iterator i = font_table.begin(); i != font_table.end(); ++i) {
00201 TTF_CloseFont(i->second);
00202 }
00203
00204 font_table.clear();
00205 font_names.clear();
00206 font_map.clear();
00207 line_size_cache.clear();
00208 }
00209
00210 namespace {
00211
00212 struct font_style_setter
00213 {
00214 font_style_setter(TTF_Font* font, int style) : font_(font), old_style_(0)
00215 {
00216 if(style == 0) {
00217 style = TTF_STYLE_NORMAL;
00218 }
00219
00220 old_style_ = TTF_GetFontStyle(font_);
00221
00222
00223
00224
00225 #if 0
00226
00227
00228
00229
00230 if((style&TTF_STYLE_UNDERLINE) != 0) {
00231
00232 style = TTF_STYLE_UNDERLINE;
00233 } else if((style&TTF_STYLE_BOLD) != 0) {
00234 style = TTF_STYLE_BOLD;
00235 } else if((style&TTF_STYLE_ITALIC) != 0) {
00236
00237 style = TTF_STYLE_ITALIC;
00238 }
00239 #endif
00240
00241 TTF_SetFontStyle(font_, style);
00242 }
00243
00244 ~font_style_setter()
00245 {
00246 TTF_SetFontStyle(font_,old_style_);
00247 }
00248
00249 private:
00250 TTF_Font* font_;
00251 int old_style_;
00252 };
00253
00254 }
00255
00256 namespace font {
00257
00258 manager::manager()
00259 {
00260 const int res = TTF_Init();
00261 if(res == -1) {
00262 ERR_FT << "Could not initialize true type fonts\n";
00263 throw error();
00264 } else {
00265 LOG_FT << "Initialized true type fonts\n";
00266 }
00267 }
00268
00269 manager::~manager()
00270 {
00271 clear_fonts();
00272 TTF_Quit();
00273 }
00274
00275
00276
00277 struct subset_descriptor
00278 {
00279 std::string name;
00280 std::vector<std::pair<size_t, size_t> > present_codepoints;
00281 };
00282
00283
00284 static void set_font_list(const std::vector<subset_descriptor>& fontlist)
00285 {
00286 clear_fonts();
00287 font_map.reserve(0x10000);
00288
00289 std::vector<subset_descriptor>::const_iterator itor;
00290 for(itor = fontlist.begin(); itor != fontlist.end(); ++itor) {
00291
00292 if(game_config::path.empty() == false) {
00293 if(!file_exists(game_config::path + "/fonts/" + itor->name)) {
00294 if(!file_exists("fonts/" + itor->name)) {
00295 if(!file_exists(itor->name)) {
00296 WRN_FT << "Failed opening font file '" << itor->name << "': No such file or directory\n";
00297 continue;
00298 }
00299 }
00300 }
00301 } else {
00302 if(!file_exists("fonts/" + itor->name)) {
00303 if(!file_exists(itor->name)) {
00304 WRN_FT << "Failed opening font file '" << itor->name << "': No such file or directory\n";
00305 continue;
00306 }
00307 }
00308 }
00309 const subset_id subset = font_names.size();
00310 font_names.push_back(itor->name);
00311
00312 std::vector<std::pair<size_t,size_t> >::const_iterator cp_range;
00313 for(cp_range = itor->present_codepoints.begin();
00314 cp_range != itor->present_codepoints.end(); ++cp_range) {
00315
00316 size_t cp_max = maximum<size_t>(cp_range->first, cp_range->second);
00317 if(cp_max >= font_map.size()) {
00318 font_map.resize(cp_max+1, -1);
00319 }
00320 for(size_t cp = cp_range->first; cp <= cp_range->second; ++cp) {
00321 if(font_map[cp] < 0)
00322 font_map[cp] = subset;
00323 }
00324 }
00325 }
00326 }
00327
00328 const SDL_Color NORMAL_COLOUR = {0xDD,0xDD,0xDD,0},
00329 GRAY_COLOUR = {0x77,0x77,0x77,0},
00330 LOBBY_COLOUR = {0xBB,0xBB,0xBB,0},
00331 GOOD_COLOUR = {0x00,0xFF,0x00,0},
00332 BAD_COLOUR = {0xFF,0x00,0x00,0},
00333 BLACK_COLOUR = {0x00,0x00,0x00,0},
00334 YELLOW_COLOUR = {0xFF,0xFF,0x00,0},
00335 BUTTON_COLOUR = {0xBC,0xB0,0x88,0},
00336 STONED_COLOUR = {0xA0,0xA0,0xA0,0},
00337 TITLE_COLOUR = {0xBC,0xB0,0x88,0},
00338 LABEL_COLOUR = {0x6B,0x8C,0xFF,0},
00339 BIGMAP_COLOUR = {0xFF,0xFF,0xFF,0};
00340 const SDL_Color DISABLED_COLOUR = inverse(STONED_COLOUR);
00341
00342 namespace {
00343
00344 static const size_t max_text_line_width = 4096;
00345
00346 class text_surface
00347 {
00348 public:
00349 text_surface(std::string const &str, int size, SDL_Color color, int style);
00350 text_surface(int size, SDL_Color color, int style);
00351 void set_text(std::string const &str);
00352
00353 void measure() const;
00354 size_t width() const;
00355 size_t height() const;
00356 #ifdef HAVE_FRIBIDI
00357 bool is_rtl() const { return is_rtl_; }
00358 #endif
00359 std::vector<surface> const & get_surfaces() const;
00360
00361 bool operator==(text_surface const &t) const {
00362 return hash_ == t.hash_ && font_size_ == t.font_size_
00363 && color_ == t.color_ && style_ == t.style_ && str_ == t.str_;
00364 }
00365 bool operator!=(text_surface const &t) const { return !operator==(t); }
00366 private:
00367 int hash_;
00368 int font_size_;
00369 SDL_Color color_;
00370 int style_;
00371 mutable int w_, h_;
00372 std::string str_;
00373 mutable bool initialized_;
00374 mutable std::vector<text_chunk> chunks_;
00375 mutable std::vector<surface> surfs_;
00376 #ifdef HAVE_FRIBIDI
00377 bool is_rtl_;
00378 void bidi_cvt();
00379 #endif
00380 void hash();
00381 };
00382
00383 #ifdef HAVE_FRIBIDI
00384 void text_surface::bidi_cvt()
00385 {
00386 char *c_str = const_cast<char *>(str_.c_str());
00387 int len = str_.length();
00388 FriBidiChar *bidi_logical = new FriBidiChar[len + 2];
00389 FriBidiChar *bidi_visual = new FriBidiChar[len + 2];
00390 char *utf8str = new char[4*len + 1];
00391 FriBidiCharType base_dir = FRIBIDI_TYPE_ON;
00392 int n;
00393
00394 n = fribidi_utf8_to_unicode (c_str, len, bidi_logical);
00395 fribidi_log2vis(bidi_logical, n, &base_dir, bidi_visual, NULL, NULL, NULL);
00396 fribidi_unicode_to_utf8 (bidi_visual, n, utf8str);
00397 is_rtl_ = base_dir == FRIBIDI_TYPE_RTL;
00398 str_ = std::string(utf8str);
00399 delete[] bidi_logical;
00400 delete[] bidi_visual;
00401 delete[] utf8str;
00402 }
00403 #endif
00404
00405 text_surface::text_surface(std::string const &str, int size, SDL_Color color, int style)
00406 : font_size_(size), color_(color), style_(style), w_(-1), h_(-1), str_(str),
00407 initialized_(false)
00408 {
00409 #ifdef HAVE_FRIBIDI
00410 bidi_cvt();
00411 #endif
00412 hash();
00413 }
00414
00415 text_surface::text_surface(int size, SDL_Color color, int style)
00416 : hash_(0), font_size_(size), color_(color), style_(style), w_(-1), h_(-1), initialized_(false)
00417 {
00418 }
00419
00420 void text_surface::set_text(std::string const &str)
00421 {
00422 initialized_ = false;
00423 w_ = -1;
00424 h_ = -1;
00425 str_ = str;
00426 #ifdef HAVE_FRIBIDI
00427 bidi_cvt();
00428 #endif
00429 hash();
00430 }
00431
00432 void text_surface::hash()
00433 {
00434 int h = 0;
00435 for(std::string::const_iterator it = str_.begin(), it_end = str_.end(); it != it_end; ++it)
00436 h = ((h << 9) | (h >> (sizeof(int) * 8 - 9))) ^ (*it);
00437 hash_ = h;
00438 }
00439
00440 void text_surface::measure() const
00441 {
00442 w_ = 0;
00443 h_ = 0;
00444
00445 for(std::vector<text_chunk>::iterator itor = chunks_.begin();
00446 itor != chunks_.end(); ++itor) {
00447
00448 TTF_Font* ttfont = get_font(font_id(itor->subset, font_size_));
00449 if(ttfont == NULL)
00450 continue;
00451 font_style_setter const style_setter(ttfont, style_);
00452
00453 int w;
00454 int h;
00455
00456 if(itor->ucs2_text.back() != 0) {
00457 itor->ucs2_text.push_back(0);
00458 }
00459
00460 TTF_SizeUNICODE(ttfont,
00461 static_cast<Uint16 const *>(&(itor->ucs2_text.front())), &w, &h);
00462 w_ += w;
00463 h_ = maximum<int>(h_, h);
00464 }
00465 }
00466
00467 size_t text_surface::width() const
00468 {
00469 if (w_ == -1) {
00470 if(chunks_.empty())
00471 chunks_ = split_text(str_);
00472 measure();
00473 }
00474 return w_;
00475 }
00476
00477 size_t text_surface::height() const
00478 {
00479 if (h_ == -1) {
00480 if(chunks_.empty())
00481 chunks_ = split_text(str_);
00482 measure();
00483 }
00484 return h_;
00485 }
00486
00487 std::vector<surface> const &text_surface::get_surfaces() const
00488 {
00489 if(initialized_)
00490 return surfs_;
00491
00492 initialized_ = true;
00493
00494
00495
00496 if(width() > max_text_line_width)
00497 return surfs_;
00498
00499 for(std::vector<text_chunk>::const_iterator itor = chunks_.begin();
00500 itor != chunks_.end(); ++itor) {
00501 TTF_Font* ttfont = get_font(font_id(itor->subset, font_size_));
00502 if (ttfont == NULL)
00503 continue;
00504 font_style_setter const style_setter(ttfont, style_);
00505
00506 surface s = surface(TTF_RenderUNICODE_Blended(ttfont,
00507 static_cast<Uint16 const *>(&(itor->ucs2_text.front())), color_));
00508 if(!s.null())
00509 surfs_.push_back(s);
00510 }
00511
00512 return surfs_;
00513 }
00514
00515 class text_cache
00516 {
00517 public:
00518 static text_surface &find(text_surface const &t);
00519 static void resize(unsigned int size);
00520 private:
00521 typedef std::list< text_surface > text_list;
00522 static text_list cache_;
00523 static unsigned int max_size_;
00524 };
00525
00526 text_cache::text_list text_cache::cache_;
00527 unsigned int text_cache::max_size_ = 50;
00528
00529 void text_cache::resize(unsigned int size)
00530 {
00531 DBG_FT << "Text cache: resize from: " << max_size_ << " to: "
00532 << size << " items in cache: " << cache_.size() << '\n';
00533
00534 while(size < cache_.size()) {
00535 cache_.pop_back();
00536 }
00537 max_size_ = size;
00538 }
00539
00540
00541 text_surface &text_cache::find(text_surface const &t)
00542 {
00543 static size_t lookup_ = 0, hit_ = 0;
00544 text_list::iterator it_bgn = cache_.begin(), it_end = cache_.end();
00545 text_list::iterator it = std::find(it_bgn, it_end, t);
00546 if (it != it_end) {
00547 cache_.splice(it_bgn, cache_, it);
00548 ++hit_;
00549 } else {
00550 if (cache_.size() >= max_size_)
00551 cache_.pop_back();
00552 cache_.push_front(t);
00553 }
00554 if (++lookup_ % 1000 == 0) {
00555 DBG_FT << "Text cache: " << lookup_ << " lookups, " << (hit_ / 10) << "% hits\n";
00556 hit_ = 0;
00557 }
00558 return cache_.front();
00559 }
00560
00561 }
00562
00563 static surface render_text(const std::string& text, int fontsize, const SDL_Color& colour, int style)
00564 {
00565 const std::vector<std::string> lines = utils::split(text, '\n', utils::REMOVE_EMPTY);
00566 std::vector<std::vector<surface> > surfaces;
00567 surfaces.reserve(lines.size());
00568 size_t width = 0, height = 0;
00569 text_surface txt_surf(fontsize, colour, style);
00570
00571 for(std::vector< std::string >::const_iterator ln = lines.begin(), ln_end = lines.end(); ln != ln_end; ++ln) {
00572 if (!ln->empty()) {
00573 txt_surf.set_text(*ln);
00574 const text_surface& cached_surf = text_cache::find(txt_surf);
00575 const std::vector<surface>&res = cached_surf.get_surfaces();
00576
00577 if (!res.empty()) {
00578 surfaces.push_back(res);
00579 width = maximum<size_t>(cached_surf.width(), width);
00580 height += cached_surf.height();
00581 }
00582 }
00583 }
00584
00585 if (surfaces.empty()) {
00586 return surface();
00587 } else if (surfaces.size() == 1 && surfaces.front().size() == 1) {
00588 surface surf = surfaces.front().front();
00589 SDL_SetAlpha(surf, SDL_SRCALPHA | SDL_RLEACCEL, SDL_ALPHA_OPAQUE);
00590 return surf;
00591 } else {
00592
00593 surface res(create_compatible_surface(surfaces.front().front(),width,height));
00594 if (res.null())
00595 return res;
00596
00597 size_t ypos = 0;
00598 for(std::vector< std::vector<surface> >::const_iterator i = surfaces.begin(),
00599 i_end = surfaces.end(); i != i_end; ++i) {
00600 size_t xpos = 0;
00601 size_t height = 0;
00602
00603 for(std::vector<surface>::const_iterator j = i->begin(),
00604 j_end = i->end(); j != j_end; ++j) {
00605 SDL_SetAlpha(*j, 0, 0);
00606 SDL_Rect dstrect = {xpos, ypos, 0, 0};
00607 SDL_BlitSurface(*j, NULL, res, &dstrect);
00608 xpos += (*j)->w;
00609 height = maximum<size_t>((*j)->h, height);
00610 }
00611 ypos += height;
00612 }
00613
00614 return res;
00615 }
00616 }
00617
00618
00619 surface get_rendered_text(const std::string& str, int size, const SDL_Color& colour, int style)
00620 {
00621 return render_text(str, size, colour, style);
00622 }
00623
00624 SDL_Rect draw_text_line(surface gui_surface, const SDL_Rect& area, int size,
00625 const SDL_Color& colour, const std::string& text,
00626 int x, int y, bool use_tooltips, int style)
00627 {
00628 const std::string etext = make_text_ellipsis(text, size, area.w);
00629
00630 if (gui_surface.null()) {
00631 text_surface const &u = text_cache::find(text_surface(text, size, colour, style));
00632 SDL_Rect res = {0, 0, u.width(), u.height()};
00633 return res;
00634 }
00635
00636 surface surface(render_text(etext,size,colour,style));
00637 if(surface == NULL) {
00638 SDL_Rect res = {0,0,0,0};
00639 return res;
00640 }
00641
00642 SDL_Rect dest;
00643 if(x!=-1) {
00644 dest.x = x;
00645 #ifdef HAVE_FRIBIDI
00646
00647 if(getenv("NO_RTL") == NULL) {
00648 bool is_rtl = text_cache::find(text_surface(text, size, colour, style)).is_rtl();
00649 if(is_rtl)
00650 dest.x = area.x + area.w - surface->w - (x - area.x);
00651 }
00652 #endif
00653 } else
00654 dest.x = (area.w/2)-(surface->w/2);
00655 if(y!=-1)
00656 dest.y = y;
00657 else
00658 dest.y = (area.h/2)-(surface->h/2);
00659 dest.w = surface->w;
00660 dest.h = surface->h;
00661
00662 if(line_width(text, size) > area.w) {
00663 tooltips::add_tooltip(dest,text);
00664 }
00665
00666 if(dest.x + dest.w > area.x + area.w) {
00667 dest.w = area.x + area.w - dest.x;
00668 }
00669
00670 if(dest.y + dest.h > area.y + area.h) {
00671 dest.h = area.y + area.h - dest.y;
00672 }
00673
00674 if(gui_surface != NULL) {
00675 SDL_Rect src = dest;
00676 src.x = 0;
00677 src.y = 0;
00678 SDL_BlitSurface(surface,&src,gui_surface,&dest);
00679 }
00680
00681 if(use_tooltips) {
00682 tooltips::add_tooltip(dest,text);
00683 }
00684
00685 return dest;
00686 }
00687
00688 SDL_Rect draw_text_line(CVideo* gui, const SDL_Rect& area, int size,
00689 const SDL_Color& colour, const std::string& text,
00690 int x, int y, bool use_tooltips, int style)
00691 {
00692 surface surface;
00693
00694 if(gui == NULL) {
00695 surface = NULL;
00696 } else {
00697 surface = gui->getSurface();
00698 }
00699
00700 return draw_text_line(surface, area, size, colour, text, x, y, use_tooltips, style);
00701 }
00702
00703 int get_max_height(int size)
00704 {
00705
00706 TTF_Font* const font = get_font(font_id(0, size));
00707 if(font == NULL)
00708 return 0;
00709 return TTF_FontHeight(font);
00710 }
00711
00712 int line_width(const std::string& line, int font_size, int style)
00713 {
00714 return line_size(line,font_size,style).w;
00715 }
00716
00717 SDL_Rect line_size(const std::string& line, int font_size, int style)
00718 {
00719 line_size_cache_map& cache = line_size_cache[style][font_size];
00720
00721 const line_size_cache_map::const_iterator i = cache.find(line);
00722 if(i != cache.end()) {
00723 return i->second;
00724 }
00725
00726 SDL_Rect res;
00727
00728 const SDL_Color col = { 0, 0, 0, 0 };
00729 text_surface s(line, font_size, col, style);
00730
00731 res.w = s.width();
00732 res.h = s.height();
00733 res.x = res.y = 0;
00734
00735 cache.insert(std::pair<std::string,SDL_Rect>(line,res));
00736 return res;
00737 }
00738
00739 std::string make_text_ellipsis(const std::string &text, int font_size, int max_width, bool with_tags)
00740 {
00741 static const std::string ellipsis = "...";
00742
00743 if(line_width(with_tags ? text : del_tags(text), font_size) <= max_width)
00744 return text;
00745 if(line_width(ellipsis, font_size) > max_width)
00746 return "";
00747
00748 std::string current_substring;
00749
00750 utils::utf8_iterator itor(text);
00751
00752 for(; itor != utils::utf8_iterator::end(text); ++itor) {
00753 std::string tmp = current_substring;
00754 tmp.append(itor.substr().first, itor.substr().second);
00755 tmp += ellipsis;
00756
00757 if (line_width(with_tags ? tmp : del_tags(tmp), font_size) > max_width) {
00758 return current_substring + ellipsis;
00759 }
00760
00761 current_substring.append(itor.substr().first, itor.substr().second);
00762 }
00763
00764 return text;
00765 }
00766
00767 }
00768
00769
00770 namespace {
00771
00772 class floating_label
00773 {
00774 public:
00775 floating_label(const std::string& text, int font_size, const SDL_Color& colour, const SDL_Color& bgcolour,
00776 double xpos, double ypos, double xmove, double ymove, int lifetime, const SDL_Rect& clip_rect,
00777 font::ALIGN align, int border_size, bool scroll_with_map)
00778 : surf_(NULL), buf_(NULL), foreground_(NULL), text_(text), font_size_(font_size), colour_(colour),
00779 bgcolour_(bgcolour), bgalpha_(bgcolour.unused), xpos_(xpos), ypos_(ypos),
00780 xmove_(xmove), ymove_(ymove), lifetime_(lifetime), clip_rect_(clip_rect),
00781 alpha_change_(-255/lifetime), visible_(true), align_(align), border_(border_size), scroll_(scroll_with_map)
00782 {}
00783
00784 void move(double xmove, double ymove);
00785
00786 void draw(surface screen);
00787 void undraw(surface screen);
00788
00789 surface create_surface();
00790
00791 bool expired() const { return lifetime_ == 0; }
00792
00793
00794 void show(const bool value) { visible_ = value; }
00795
00796 bool scroll() const { return scroll_; }
00797
00798 private:
00799
00800 int xpos(size_t width) const;
00801
00802 surface surf_, buf_, foreground_;
00803 std::string text_;
00804 int font_size_;
00805 SDL_Color colour_, bgcolour_;
00806 int bgalpha_;
00807 double xpos_, ypos_, xmove_, ymove_;
00808 int lifetime_;
00809 SDL_Rect clip_rect_;
00810 int alpha_change_;
00811 bool visible_;
00812 font::ALIGN align_;
00813 int border_;
00814 bool scroll_;
00815 };
00816
00817 typedef std::map<int,floating_label> label_map;
00818 label_map labels;
00819 int label_id = 1;
00820
00821 std::stack<std::set<int> > label_contexts;
00822
00823 void floating_label::move(double xmove, double ymove)
00824 {
00825 xpos_ += xmove;
00826 ypos_ += ymove;
00827 }
00828
00829 int floating_label::xpos(size_t width) const
00830 {
00831 int xpos = int(xpos_);
00832 if(align_ == font::CENTER_ALIGN) {
00833 xpos -= width/2;
00834 } else if(align_ == font::RIGHT_ALIGN) {
00835 xpos -= width;
00836 }
00837
00838 return xpos;
00839 }
00840
00841 surface floating_label::create_surface()
00842 {
00843 if (surf_.null()) {
00844 foreground_ = font::render_text(text_, font_size_, colour_, 0);
00845
00846 if(foreground_ == NULL) {
00847 return NULL;
00848 }
00849
00850
00851 if(bgalpha_ != 0) {
00852 surface tmp(create_compatible_surface(foreground_,foreground_->w+border_*2,foreground_->h+border_*2));
00853 if(tmp == NULL) {
00854 return NULL;
00855 }
00856
00857 SDL_FillRect(tmp,NULL,SDL_MapRGBA(tmp->format,bgcolour_.r,bgcolour_.g,bgcolour_.b, bgalpha_));
00858
00859 surf_.assign(tmp);
00860 } else {
00861 surface background = font::render_text(text_, font_size_, font::BLACK_COLOUR, 0);
00862 background = blur_alpha_surface(background,2,false);
00863 background = adjust_surface_alpha(background, ftofxp(4.0));
00864
00865 surf_.assign(background);
00866 }
00867
00868 }
00869
00870 return surf_;
00871 }
00872
00873 void floating_label::draw(surface screen)
00874 {
00875 if(!visible_) {
00876 buf_.assign(NULL);
00877 return;
00878 }
00879
00880 create_surface();
00881 if(surf_ == NULL) {
00882 return;
00883 }
00884
00885 if(buf_ == NULL) {
00886 buf_.assign(create_compatible_surface(surf_));
00887 if(buf_ == NULL) {
00888 return;
00889 }
00890 }
00891
00892 if(screen == NULL) {
00893 return;
00894 }
00895
00896 SDL_Rect rect = {xpos(surf_->w),int(ypos_),surf_->w,surf_->h};
00897 const clip_rect_setter clip_setter(screen,clip_rect_);
00898 SDL_BlitSurface(screen,&rect,buf_,NULL);
00899 SDL_BlitSurface(surf_,NULL,screen,&rect);
00900
00901 if(foreground_ != NULL) {
00902 SDL_Rect rect = {xpos(surf_->w)+border_,int(ypos_)+border_,foreground_->w,foreground_->h};
00903 SDL_BlitSurface(foreground_,NULL,screen,&rect);
00904 }
00905
00906 update_rect(rect);
00907 }
00908
00909 void floating_label::undraw(surface screen)
00910 {
00911 if(screen == NULL || buf_ == NULL) {
00912 return;
00913 }
00914
00915 SDL_Rect rect = {xpos(surf_->w),int(ypos_),surf_->w,surf_->h};
00916 const clip_rect_setter clip_setter(screen,clip_rect_);
00917 SDL_BlitSurface(buf_,NULL,screen,&rect);
00918
00919 update_rect(rect);
00920
00921 move(xmove_,ymove_);
00922 if(lifetime_ > 0) {
00923 --lifetime_;
00924 if(alpha_change_ != 0 && (xmove_ != 0.0 || ymove_ != 0.0)) {
00925
00926 if (!surf_.null()) {
00927 surf_.assign(adjust_surface_alpha_add(surf_,alpha_change_,false));
00928 }
00929 if (!foreground_.null()) {
00930 foreground_.assign(adjust_surface_alpha_add(foreground_,alpha_change_,false));
00931 }
00932 }
00933 }
00934 }
00935
00936 }
00937
00938 namespace font {
00939 int add_floating_label(const std::string& text, int font_size, const SDL_Color& colour,
00940 double xpos, double ypos, double xmove, double ymove, int lifetime, const SDL_Rect& clip_rect, ALIGN align,
00941 const SDL_Color* bg_colour, int border_size, LABEL_SCROLL_MODE scroll_mode)
00942 {
00943 if(label_contexts.empty()) {
00944 return 0;
00945 }
00946
00947 if(lifetime <= 0) {
00948 lifetime = -1;
00949 }
00950
00951 SDL_Color bg = {0,0,0,0};
00952 if(bg_colour != NULL) {
00953 bg = *bg_colour;
00954 }
00955
00956 ++label_id;
00957 labels.insert(std::pair<int,floating_label>(label_id,floating_label(text,font_size,colour,bg,xpos,ypos,xmove,ymove,lifetime,clip_rect,align,border_size,scroll_mode == ANCHOR_LABEL_MAP)));
00958 label_contexts.top().insert(label_id);
00959 return label_id;
00960 }
00961
00962 void move_floating_label(int handle, double xmove, double ymove)
00963 {
00964 const label_map::iterator i = labels.find(handle);
00965 if(i != labels.end()) {
00966 i->second.move(xmove,ymove);
00967 }
00968 }
00969
00970 void scroll_floating_labels(double xmove, double ymove)
00971 {
00972 for(label_map::iterator i = labels.begin(); i != labels.end(); ++i) {
00973 if(i->second.scroll()) {
00974 i->second.move(xmove,ymove);
00975 }
00976 }
00977 }
00978
00979 void remove_floating_label(int handle)
00980 {
00981 const label_map::iterator i = labels.find(handle);
00982 if(i != labels.end()) {
00983 if(label_contexts.empty() == false) {
00984 label_contexts.top().erase(i->first);
00985 }
00986
00987 labels.erase(i);
00988 }
00989 }
00990
00991 void show_floating_label(int handle, bool value)
00992 {
00993 const label_map::iterator i = labels.find(handle);
00994 if(i != labels.end()) {
00995 i->second.show(value);
00996 }
00997 }
00998
00999 SDL_Rect get_floating_label_rect(int handle)
01000 {
01001 const label_map::iterator i = labels.find(handle);
01002 if(i != labels.end()) {
01003 const surface surf = i->second.create_surface();
01004 if(surf != NULL) {
01005 SDL_Rect rect = {0,0,surf->w,surf->h};
01006 return rect;
01007 }
01008 }
01009
01010 return empty_rect;
01011 }
01012
01013 floating_label_context::floating_label_context()
01014 {
01015 surface const screen = SDL_GetVideoSurface();
01016 if(screen != NULL) {
01017 draw_floating_labels(screen);
01018 }
01019
01020 label_contexts.push(std::set<int>());
01021 }
01022
01023 floating_label_context::~floating_label_context()
01024 {
01025 const std::set<int>& labels = label_contexts.top();
01026 for(std::set<int>::const_iterator i = labels.begin(); i != labels.end(); ) {
01027 remove_floating_label(*i++);
01028 }
01029
01030 label_contexts.pop();
01031
01032 surface const screen = SDL_GetVideoSurface();
01033 if(screen != NULL) {
01034 undraw_floating_labels(screen);
01035 }
01036 }
01037
01038 void draw_floating_labels(surface screen)
01039 {
01040 if(label_contexts.empty()) {
01041 return;
01042 }
01043
01044 const std::set<int>& context = label_contexts.top();
01045
01046
01047
01048 for(label_map::iterator i = labels.begin(); i != labels.end(); ++i) {
01049 if(context.count(i->first) > 0) {
01050 i->second.draw(screen);
01051 }
01052 }
01053 }
01054
01055 void undraw_floating_labels(surface screen)
01056 {
01057 if(label_contexts.empty()) {
01058 return;
01059 }
01060
01061 std::set<int>& context = label_contexts.top();
01062
01063
01064
01065 for(label_map::reverse_iterator i = labels.rbegin(); i != labels.rend(); ++i) {
01066 if(context.count(i->first) > 0) {
01067 i->second.undraw(screen);
01068 }
01069 }
01070
01071
01072 for(label_map::iterator j = labels.begin(); j != labels.end(); ) {
01073 if(context.count(j->first) > 0 && j->second.expired()) {
01074 context.erase(j->first);
01075 labels.erase(j++);
01076 } else {
01077 j++;
01078 }
01079 }
01080 }
01081
01082 }
01083
01084 static bool add_font_to_fontlist(config* fonts_config, std::vector<font::subset_descriptor>& fontlist, const std::string& name)
01085 {
01086 config* font = fonts_config->find_child("font", "name", name);
01087 if(font == NULL)
01088 return false;
01089
01090 fontlist.push_back(font::subset_descriptor());
01091 fontlist.back().name = name;
01092 std::vector<std::string> ranges = utils::split((*font)["codepoints"]);
01093
01094 for(std::vector<std::string>::const_iterator itor = ranges.begin();
01095 itor != ranges.end(); ++itor) {
01096
01097 std::vector<std::string> r = utils::split(*itor, '-');
01098 if(r.size() == 1) {
01099 size_t r1 = lexical_cast_default<size_t>(r[0], 0);
01100 fontlist.back().present_codepoints.push_back(std::pair<size_t, size_t>(r1, r1));
01101 } else if(r.size() == 2) {
01102 size_t r1 = lexical_cast_default<size_t>(r[0], 0);
01103 size_t r2 = lexical_cast_default<size_t>(r[1], 0);
01104
01105 fontlist.back().present_codepoints.push_back(std::pair<size_t, size_t>(r1, r2));
01106 }
01107 }
01108
01109 return true;
01110 }
01111
01112 namespace font {
01113
01114 bool load_font_config()
01115 {
01116
01117
01118 config cfg;
01119 try {
01120 scoped_istream stream = preprocess_file("data/hardwired/fonts.cfg");
01121 read(cfg, *stream);
01122 } catch(config::error &e) {
01123 ERR_FT << "could not read fonts.cfg:\n"
01124 << e.message << '\n';
01125 return false;
01126 }
01127
01128 config* fonts_config = cfg.child("fonts");
01129 if(fonts_config == NULL)
01130 return false;
01131
01132 std::set<std::string> known_fonts;
01133 const config::child_list fonts = fonts_config->get_children("font");
01134 for (config::child_list::const_iterator child = fonts.begin(); child != fonts.end(); ++child) {
01135 known_fonts.insert((**child)["name"]);
01136 }
01137
01138 const std::vector<std::string> font_order = utils::split((*fonts_config)["order"]);
01139 std::vector<font::subset_descriptor> fontlist;
01140 std::vector<std::string>::const_iterator font;
01141 for(font = font_order.begin(); font != font_order.end(); ++font) {
01142 add_font_to_fontlist(fonts_config, fontlist, *font);
01143 known_fonts.erase(*font);
01144 }
01145 std::set<std::string>::const_iterator kfont;
01146 for(kfont = known_fonts.begin(); kfont != known_fonts.end(); ++kfont) {
01147 add_font_to_fontlist(fonts_config, fontlist, *kfont);
01148 }
01149
01150 if(fontlist.empty())
01151 return false;
01152
01153 font::set_font_list(fontlist);
01154 return true;
01155 }
01156
01157 void cache_mode(CACHE mode)
01158 {
01159 if(mode == CACHE_LOBBY) {
01160 text_cache::resize(1000);
01161 } else {
01162 text_cache::resize(50);
01163 }
01164 }
01165
01166
01167 }