font.cpp

Go to the documentation of this file.
00001 /* $Id: font.cpp 26233 2008-04-29 18:48:23Z alink $ */
00002 /* vim:set encoding=utf-8: */
00003 /*
00004    Copyright (C) 2003 - 2008 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 version 2
00009    or at your option any later version.
00010    This program is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY.
00012 
00013    See the COPYING file for more details.
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 // Signed int. Negative values mean "no subset".
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     //FIXME if we don't need the utf8 here remove it
00090     std::string text;
00091     ucs2_string ucs2_text;
00092 };
00093 
00094 std::vector<subset_id> font_map;
00095 
00096 //cache sizes of small text
00097 typedef std::map<std::string,SDL_Rect> line_size_cache_map;
00098 
00099 //map of styles -> sizes -> cache
00100 std::map<int,std::map<int,line_size_cache_map> > line_size_cache;
00101 
00102 }
00103 
00104 //Splits the UTF-8 text into text_chunks using the same font.
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                 //null-terminate ucs2_text so we can pass it to SDL_ttf later
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         // I thought I had killed this. Now that we ship SDL_TTF, we
00223         // should fix the bug directly in SDL_ttf instead of disabling
00224         // features. -- Ayin 25/2/2005
00225 #if 0
00226         //according to the SDL_ttf documentation, combinations of
00227         //styles may cause SDL_ttf to segfault. We work around this
00228         //here by disallowing combinations of styles
00229 
00230         if((style&TTF_STYLE_UNDERLINE) != 0) {
00231             //style = TTF_STYLE_NORMAL; //TTF_STYLE_UNDERLINE;
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             //style = TTF_STYLE_NORMAL; //TTF_STYLE_ITALIC;
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 //structure used to describe a font, and the subset of the Unicode character
00276 //set it covers.
00277 struct subset_descriptor
00278 {
00279     std::string name;
00280     std::vector<std::pair<size_t, size_t> > present_codepoints;
00281 };
00282 
00283 //sets the font list to be used.
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         // Insert fonts only if the font file exists
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_; } // Right-To-Left alignment
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());  // fribidi forgot const...
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]; //assume worst case here (all 4 Byte characters)
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     // Impose a maximal number of characters for a text line. Do now draw
00495     // any text longer that that, to prevent a SDL buffer overflow
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); // direct blit without alpha blending
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         // Oron -- Conditional, until all draw_text_line calls have fixed area parameter
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     // Only returns the maximal size of the first font
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; // Should not happen
00765 }
00766 
00767 }
00768 
00769 //floating labels
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         //if the surface has to be created onto some kind of background, then do that here
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             // we don't compress these surfaces since they will always change
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     //draw the labels in the order they were added, so later added labels (likely to be tooltips)
01047     //are displayed over earlier added labels.
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     //undraw labels in reverse order, so that a LIFO process occurs, and the screen is restored
01064     //into the exact state it started in.
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     //remove expired labels
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     //read font config separately, so we do not have to re-read the whole
01117     //config when changing languages
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 }

Generated by doxygen 1.5.5 on 23 May 2008 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs