The Battle for Wesnoth  1.13.4+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
font.cpp
Go to the documentation of this file.
1 /* vim:set encoding=utf-8: */
2 /*
3  Copyright (C) 2003 - 2016 by David White <[email protected]>
4  Part of the Battle for Wesnoth Project http://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #define GETTEXT_DOMAIN "wesnoth-lib"
17 
18 #include "global.hpp"
19 
20 #include "config.hpp"
21 #include "filesystem.hpp"
22 #include "font.hpp"
23 #include "game_config.hpp"
24 #include "log.hpp"
25 #include "marked-up_text.hpp"
26 #include "text.hpp"
27 #include "tooltips.hpp"
28 #include "sdl/alpha.hpp"
29 #include "sdl/rect.hpp"
30 #include "serialization/parser.hpp"
34 #include "preferences.hpp"
35 
36 #include <boost/optional.hpp>
37 
38 #include <list>
39 #include <set>
40 #include <stack>
41 #include <sstream>
42 
43 #include <cairo-features.h>
44 
45 #ifdef CAIRO_HAS_WIN32_FONT
46 #include <windows.h>
47 #undef CAIRO_HAS_FT_FONT
48 #endif
49 
50 #ifdef CAIRO_HAS_FT_FONT
51 #include <fontconfig/fontconfig.h>
52 #endif
53 
54 #if !defined(CAIRO_HAS_FT_FONT) && !defined(CAIRO_HAS_WIN32_FONT)
55 // Is there soemthing like #warning which just gives awarnign insteads of an error?
56 #error unable to find font loading tools.
57 #endif
58 
59 static lg::log_domain log_font("font");
60 #define DBG_FT LOG_STREAM(debug, log_font)
61 #define LOG_FT LOG_STREAM(info, log_font)
62 #define WRN_FT LOG_STREAM(warn, log_font)
63 #define ERR_FT LOG_STREAM(err, log_font)
64 
65 #ifdef HAVE_FRIBIDI
66 #include <fribidi.h>
67 #endif
68 
69 // Signed int. Negative values mean "no subset".
70 typedef int subset_id;
71 
72 // Used as a key in the font table, which caches the get_font results.
73 struct font_id
74 {
75  font_id(subset_id subset, int size) : subset(subset), size(size), style(TTF_STYLE_NORMAL) {}
76  font_id(subset_id subset, int size, int style) : subset(subset), size(size), style(style) {}
77  bool operator==(const font_id& o) const
78  {
79  return subset == o.subset && size == o.size && style == o.style;
80  }
81  bool operator<(const font_id& o) const
82  {
83  return subset < o.subset || (subset == o.subset && size < o.size) || (subset == o.subset && size == o.size && style < o.style);
84  }
85 
87  int size;
88  int style;
89 };
90 
91 // Record stored in the font table.
92 // If the record for font_id (FOO, Bold + Underline) is a record (BAR, Bold),
93 // it means that BAR is a Bold-styled version of FOO which we shipped with the
94 // game, and now SDL_TTF should be used to style BAR as underline for the final results.
95 struct ttf_record
96 {
97  TTF_Font* font;
98  int style;
99 };
100 
101 typedef std::map<font_id, ttf_record> tfont_table;
102 
104 static std::vector<std::string> font_names;
105 static std::vector<std::string> bold_names;
106 static std::vector<std::string> italic_names;
107 
109 {
111  subset(subset),
112  text()
113  {
114  }
115 
116  bool operator==(text_chunk const & t) const { return subset == t.subset && text == t.text; }
117  bool operator!=(text_chunk const & t) const { return !operator==(t); }
118 
121 };
122 
124 {
126  : cbmap()
127  {
128  }
129 
130  typedef std::pair<int, subset_id> block_t;
131  typedef std::map<int, block_t> cbmap_t;
132  cbmap_t cbmap;
133  /** Associates not-associated parts of a range with a new font. */
134  void insert(int first, int last, subset_id id)
135  {
136  if (first > last) return;
137  cbmap_t::iterator i = cbmap.lower_bound(first);
138  // At this point, either first <= i->first or i is past the end.
139  if (i != cbmap.begin()) {
140  cbmap_t::iterator j = i;
141  --j;
142  if (first <= j->second.first /* prev.last */) {
143  insert(j->second.first + 1, last, id);
144  return;
145  }
146  }
147  if (i != cbmap.end()) {
148  if (/* next.first */ i->first <= last) {
149  insert(first, i->first - 1, id);
150  return;
151  }
152  }
153  cbmap.insert(std::make_pair(first, block_t(last, id)));
154  }
155  /**
156  * Compresses map by merging consecutive ranges with the same font, even
157  * if there is some unassociated ranges in-between.
158  */
159  void compress()
160  {
161  LOG_FT << "Font map size before compression: " << cbmap.size() << " ranges\n";
162  cbmap_t::iterator i = cbmap.begin(), e = cbmap.end();
163  while (i != e) {
164  cbmap_t::iterator j = i;
165  ++j;
166  if (j == e || i->second.second != j->second.second) {
167  i = j;
168  continue;
169  }
170  i->second.first = j->second.first;
171  cbmap.erase(j);
172  }
173  LOG_FT << "Font map size after compression: " << cbmap.size() << " ranges\n";
174  }
176  {
177  cbmap_t::iterator i = cbmap.upper_bound(ch);
178  // At this point, either ch < i->first or i is past the end.
179  if (i != cbmap.begin()) {
180  --i;
181  if (ch <= i->second.first /* prev.last */)
182  return i->second.second;
183  }
184  return -1;
185  }
186 };
187 
189 
190 //cache sizes of small text
191 typedef std::map<std::string,SDL_Rect> line_size_cache_map;
192 
193 //map of styles -> sizes -> cache
194 static std::map<int,std::map<int,line_size_cache_map> > line_size_cache;
195 
196 //Splits the UTF-8 text into text_chunks using the same font.
197 static std::vector<text_chunk> split_text(std::string const & utf8_text) {
198  text_chunk current_chunk(0);
199  std::vector<text_chunk> chunks;
200 
201  if (utf8_text.empty())
202  return chunks;
203 
204  try {
205  utf8::iterator ch(utf8_text);
206  int sub = char_blocks.get_id(*ch);
207  if (sub >= 0) current_chunk.subset = sub;
208  for(utf8::iterator end = utf8::iterator::end(utf8_text); ch != end; ++ch)
209  {
210  sub = char_blocks.get_id(*ch);
211  if (sub >= 0 && sub != current_chunk.subset) {
212  chunks.push_back(current_chunk);
213  current_chunk.text.clear();
214  current_chunk.subset = sub;
215  }
216  current_chunk.text.append(ch.substr().first, ch.substr().second);
217  }
218  if (!current_chunk.text.empty()) {
219  chunks.push_back(current_chunk);
220  }
221  }
223  WRN_FT << "Invalid UTF-8 string: \"" << utf8_text << "\"" << std::endl;
224  }
225  return chunks;
226 }
227 
228 typedef std::map<std::pair<std::string, int>, TTF_Font*> topen_font_cache;
230 
231 static TTF_Font* open_font_impl(const std::string & , int);
232 
233 // A wrapper which caches the results of open_font_impl.
234 // Note that clear_fonts() is responsible to clean up all of these font pointers,
235 // so to avoid memory leaks fonts should only be opened from this function.
236 static TTF_Font* open_font(const std::string& fname, int size)
237 {
238  const std::pair<std::string, int> key = std::make_pair(fname, size);
239  const topen_font_cache::iterator it = open_fonts.find(key);
240  if (it != open_fonts.end()) {
241  return it->second;
242  }
243 
244  TTF_Font* result = open_font_impl(fname, size);
245  open_fonts.insert(std::make_pair(key, result));
246  return result;
247 }
248 
249 static TTF_Font* open_font_impl(const std::string & fname, int size) {
251  if(!game_config::path.empty()) {
252  name = game_config::path + "/fonts/" + fname;
253  if(!filesystem::file_exists(name)) {
254  name = "fonts/" + fname;
255  if(!filesystem::file_exists(name)) {
256  name = fname;
257  if(!filesystem::file_exists(name)) {
258  ERR_FT << "Failed opening font: '" << name << "': No such file or directory" << std::endl;
259  return nullptr;
260  }
261  }
262  }
263 
264  } else {
265  name = "fonts/" + fname;
266  if(!filesystem::file_exists(name)) {
267  if(!filesystem::file_exists(fname)) {
268  ERR_FT << "Failed opening font: '" << name << "': No such file or directory" << std::endl;
269  return nullptr;
270  }
271  name = fname;
272  }
273  }
274 
275  SDL_RWops *rwops = filesystem::load_RWops(name);
276  TTF_Font* font = TTF_OpenFontRW(rwops, true, size); // SDL takes ownership of rwops
277  if(font == nullptr) {
278  ERR_FT << "Failed opening font: '" << fname << "'\n";
279  ERR_FT << "TTF_OpenFont: " << TTF_GetError() << std::endl;
280  return nullptr;
281  }
282 
283  DBG_FT << "Opened a font: " << fname << std::endl;
284 
285  return font;
286 }
287 
288 // Gets an appropriately configured TTF Font, for this font size and style.
289 // Loads fonts if necessary. For styled fonts, we search for a ``shipped''
290 // version of the font which is prestyled. If this fails we find the closest
291 // thing which we did ship, and store a record of this, which allows to
292 // rapidly correct the remaining styling using SDL_TTF.
293 //
294 // Uses the font table for caching.
295 static TTF_Font* get_font(font_id id)
296 {
298  if(it != font_table.end()) {
299  if (it->second.font != nullptr) {
300  // If we found a valid record, use SDL_TTF to add in the difference
301  // between its intrinsic style and the desired style.
302  TTF_SetFontStyle(it->second.font, it->second.style ^ id.style);
303  }
304  return it->second.font;
305  }
306 
307  // There's no record, so we need to try to find a solution for this font
308  // and make a record of it. If the indices are out of bounds don't bother though.
309  if(id.subset < 0 || size_t(id.subset) >= font_names.size()) {
310  return nullptr;
311  }
312 
313  // Favor to use the shipped Italic font over bold if both are present and are needed.
314  if ((id.style & TTF_STYLE_ITALIC) && italic_names[id.subset].size()) {
315  if (TTF_Font* font = open_font(italic_names[id.subset], id.size)) {
316  ttf_record rec = {font, TTF_STYLE_ITALIC};
317  font_table.insert(std::make_pair(id, rec));
318  return get_font(id);
319  }
320  }
321 
322  // Now see if the shipped Bold font is useful and available.
323  if ((id.style & TTF_STYLE_BOLD) && bold_names[id.subset].size()) {
324  if (TTF_Font* font = open_font(bold_names[id.subset], id.size)) {
325  ttf_record rec = {font, TTF_STYLE_BOLD};
326  font_table.insert(std::make_pair(id, rec));
327  return get_font(id);
328  }
329  }
330 
331  // Try just to use the basic version of the font then.
332  if (font_names[id.subset].size()) {
333  if(TTF_Font* font = open_font(font_names[id.subset], id.size)) {
334  ttf_record rec = {font, TTF_STYLE_NORMAL};
335  font_table.insert(std::make_pair(id, rec));
336  return get_font(id);
337  }
338  }
339 
340  // Failed to find a font.
341  ttf_record rec = {nullptr, TTF_STYLE_NORMAL};
342  font_table.insert(std::make_pair(id, rec));
343  return nullptr;
344 }
345 
346 static void clear_fonts()
347 {
348  for(topen_font_cache::iterator i = open_fonts.begin(); i != open_fonts.end(); ++i) {
349  TTF_CloseFont(i->second);
350  }
351  open_fonts.clear();
352 
353  font_table.clear();
354 
355  font_names.clear();
356  bold_names.clear();
357  italic_names.clear();
358 
359  char_blocks.cbmap.clear();
360  line_size_cache.clear();
361 }
362 
363 namespace font {
364 
366 {
367  const int res = TTF_Init();
368  if(res == -1) {
369  ERR_FT << "Could not initialize true type fonts" << std::endl;
370  throw error();
371  } else {
372  LOG_FT << "Initialized true type fonts\n";
373  }
374 
375  init();
376 }
377 
379 {
380  deinit();
381 
382  clear_fonts();
383  TTF_Quit();
384 }
385 
387 {
388  deinit();
389  init();
390 }
391 
392 void manager::init() const
393 {
394 #ifdef CAIRO_HAS_FT_FONT
395  if (!FcConfigAppFontAddDir(FcConfigGetCurrent(),
396  reinterpret_cast<const FcChar8 *>((game_config::path + "/fonts").c_str())))
397  {
398  ERR_FT << "Could not load the true type fonts" << std::endl;
399  throw error();
400  }
401 
402  if(!FcConfigParseAndLoad(FcConfigGetCurrent(),
403  reinterpret_cast<const FcChar8*>((game_config::path + "/fonts/fonts.conf").c_str()),
404  FcFalse))
405  {
406  ERR_FT << "Could not load local font configuration\n";
407  }
408  else
409  {
410  LOG_FT << "Local font configuration loaded\n";
411  }
412 #endif
413 
414 #if CAIRO_HAS_WIN32_FONT
415  for(const std::string& path : filesystem::get_binary_paths("fonts")) {
416  std::vector<std::string> files;
419  }
420  for(const std::string& file : files) {
421  if(file.substr(file.length() - 4) == ".ttf" || file.substr(file.length() - 4) == ".ttc")
422  {
423  const std::wstring wfile = unicode_cast<std::wstring>(file);
424  AddFontResourceExW(wfile.c_str(), FR_PRIVATE, nullptr);
425  }
426  }
427  }
428 #endif
429 }
430 
431 void manager::deinit() const
432 {
433 #ifdef CAIRO_HAS_FT_FONT
434  FcConfigAppFontClear(FcConfigGetCurrent());
435 #endif
436 
437 #if CAIRO_HAS_WIN32_FONT
438  for(const std::string& path : filesystem::get_binary_paths("fonts")) {
439  std::vector<std::string> files;
442  for(const std::string& file : files) {
443  if(file.substr(file.length() - 4) == ".ttf" || file.substr(file.length() - 4) == ".ttc")
444  {
445  const std::wstring wfile = unicode_cast<std::wstring>(file);
446  RemoveFontResourceExW(wfile.c_str(), FR_PRIVATE, nullptr);
447  }
448  }
449  }
450 #endif
451 }
452 
453 //structure used to describe a font, and the subset of the Unicode character
454 //set it covers.
456 {
458  name(),
460  {
461  }
462 
463  subset_descriptor(const config &);
464 
466  boost::optional<std::string> bold_name; //If we are using another font for styled characters in this font, rather than SDL TTF method
467  boost::optional<std::string> italic_name;
468 
469  typedef std::pair<int, int> range;
470  std::vector<range> present_codepoints;
471 };
472 
474  : name(font["name"].str())
475  , bold_name()
476  , italic_name()
477  , present_codepoints()
478 {
479  if (font.has_attribute("bold_name")) {
480  bold_name = font["bold_name"].str();
481  }
482 
483  if (font.has_attribute("italic_name")) {
484  italic_name = font["italic_name"].str();
485  }
486 
487  std::vector<std::string> ranges = utils::split(font["codepoints"]);
488 
489  for (const std::string & i : ranges) {
490  std::vector<std::string> r = utils::split(i, '-');
491  if(r.size() == 1) {
492  size_t r1 = lexical_cast_default<size_t>(r[0], 0);
493  present_codepoints.push_back(std::pair<size_t, size_t>(r1, r1));
494  } else if(r.size() == 2) {
495  size_t r1 = lexical_cast_default<size_t>(r[0], 0);
496  size_t r2 = lexical_cast_default<size_t>(r[1], 0);
497 
498  present_codepoints.push_back(std::pair<size_t, size_t>(r1, r2));
499  }
500  }
501 }
502 
504  if(game_config::path.empty() == false) {
505  if(!filesystem::file_exists(game_config::path + "/fonts/" + name)) {
506  if(!filesystem::file_exists("fonts/" + name)) {
507  if(!filesystem::file_exists(name)) {
508  WRN_FT << "Failed opening font file '" << name << "': No such file or directory" << std::endl;
509  return false;
510  }
511  }
512  }
513  } else {
514  if(!filesystem::file_exists("fonts/" + name)) {
515  if(!filesystem::file_exists(name)) {
516  WRN_FT << "Failed opening font file '" << name << "': No such file or directory" << std::endl;
517  return false;
518  }
519  }
520  }
521  return true;
522 }
523 
524 //sets the font list to be used.
525 static void set_font_list(const std::vector<subset_descriptor>& fontlist)
526 {
527  clear_fonts();
528 
529  std::vector<subset_descriptor>::const_iterator itor;
530  for(itor = fontlist.begin(); itor != fontlist.end(); ++itor) {
531  if (!check_font_file(itor->name)) continue;
532  // Insert fonts only if the font file exists
533  const subset_id subset = font_names.size();
534  font_names.push_back(itor->name);
535 
536  if (itor->bold_name && check_font_file(*itor->bold_name)) {
537  bold_names.push_back(*itor->bold_name);
538  } else {
539  bold_names.push_back("");
540  }
541 
542  if (itor->italic_name && check_font_file(*itor->italic_name)) {
543  italic_names.push_back(*itor->italic_name);
544  } else {
545  italic_names.push_back("");
546  }
547 
548  for (const subset_descriptor::range &cp_range : itor->present_codepoints) {
549  char_blocks.insert(cp_range.first, cp_range.second, subset);
550  }
551  }
552  char_blocks.compress();
553 
554  assert(font_names.size() == bold_names.size());
555  assert(font_names.size() == italic_names.size());
556 
557  DBG_FT << "Set the font list. The styled font families are:\n";
558 
559  for (size_t i = 0; i < font_names.size(); ++i) {
560  DBG_FT << "[" << i << "]:\t\tbase:\t'" << font_names[i] << "'\tbold:\t'" << bold_names[i] << "'\titalic:\t'" << italic_names[i] << "'\n";
561  }
562 }
563 
564 const SDL_Color NORMAL_COLOR = {0xDD,0xDD,0xDD,0},
565  GRAY_COLOR = {0x77,0x77,0x77,0},
566  LOBBY_COLOR = {0xBB,0xBB,0xBB,0},
567  GOOD_COLOR = {0x00,0xFF,0x00,0},
568  BAD_COLOR = {0xFF,0x00,0x00,0},
569  BLACK_COLOR = {0x00,0x00,0x00,0},
570  YELLOW_COLOR = {0xFF,0xFF,0x00,0},
571  BUTTON_COLOR = {0xBC,0xB0,0x88,0},
572  PETRIFIED_COLOR = {0xA0,0xA0,0xA0,0},
573  TITLE_COLOR = {0xBC,0xB0,0x88,0},
574  LABEL_COLOR = {0x6B,0x8C,0xFF,0},
575  BIGMAP_COLOR = {0xFF,0xFF,0xFF,0};
577 
578 namespace {
579 
580 static const size_t max_text_line_width = 4096;
581 
582 class text_surface
583 {
584 public:
585  text_surface(std::string const &str, int size, SDL_Color color, int style);
586  text_surface(int size, SDL_Color color, int style);
587  void set_text(std::string const &str);
588 
589  void measure() const;
590  size_t width() const;
591  size_t height() const;
592 #ifdef HAVE_FRIBIDI
593  bool is_rtl() const { return is_rtl_; } // Right-To-Left alignment
594 #endif
595  std::vector<surface> const & get_surfaces() const;
596 
597  bool operator==(text_surface const &t) const {
598  return hash_ == t.hash_ && font_size_ == t.font_size_
599  && color_ == t.color_ && style_ == t.style_ && str_ == t.str_;
600  }
601  bool operator!=(text_surface const &t) const { return !operator==(t); }
602 private:
603  int hash_;
605  SDL_Color color_;
606  int style_;
607  mutable int w_, h_;
609  mutable bool initialized_;
610  mutable std::vector<text_chunk> chunks_;
611  mutable std::vector<surface> surfs_;
612 #ifdef HAVE_FRIBIDI
613  bool is_rtl_;
614  void bidi_cvt();
615 #endif
616  void hash();
617 };
618 
619 #ifdef HAVE_FRIBIDI
620 void text_surface::bidi_cvt()
621 {
622  char *c_str = const_cast<char *>(str_.c_str()); // fribidi forgot const...
623  FriBidiStrIndex len = str_.length();
624  FriBidiChar *bidi_logical = new FriBidiChar[len + 2];
625  FriBidiChar *bidi_visual = new FriBidiChar[len + 2];
626  char *utf8str = new char[4*len + 1]; //assume worst case here (all 4 Byte characters)
627  FriBidiCharType base_dir = FRIBIDI_TYPE_ON;
628  FriBidiStrIndex n;
629 
630 
631  n = fribidi_charset_to_unicode(FRIBIDI_CHAR_SET_UTF8, c_str, len, bidi_logical);
632  fribidi_log2vis(bidi_logical, n, &base_dir, bidi_visual, nullptr, nullptr, nullptr);
633 
634  fribidi_unicode_to_charset(FRIBIDI_CHAR_SET_UTF8, bidi_visual, n, utf8str);
635  is_rtl_ = base_dir == FRIBIDI_TYPE_RTL;
636  str_ = std::string(utf8str);
637  delete[] bidi_logical;
638  delete[] bidi_visual;
639  delete[] utf8str;
640 }
641 #endif
642 
643 text_surface::text_surface(std::string const &str, int size,
644  SDL_Color color, int style) :
645  hash_(0),
646  font_size_(size),
647  color_(color),
648  style_(style),
649  w_(-1),
650  h_(-1),
651  str_(str),
652  initialized_(false),
653  chunks_(),
654  surfs_()
655 #ifdef HAVE_FRIBIDI
656  ,is_rtl_(false)
657 #endif
658 {
659 #ifdef HAVE_FRIBIDI
660  bidi_cvt();
661 #endif
662  hash();
663 }
664 
665 text_surface::text_surface(int size, SDL_Color color, int style) :
666  hash_(0),
667  font_size_(size),
668  color_(color),
669  style_(style),
670  w_(-1),
671  h_(-1),
672  str_(),
673  initialized_(false),
674  chunks_(),
675  surfs_()
676 #ifdef HAVE_FRIBIDI
677  ,is_rtl_(false)
678 #endif
679 {
680 }
681 
682 void text_surface::set_text(std::string const &str)
683 {
684  initialized_ = false;
685  w_ = -1;
686  h_ = -1;
687  str_ = str;
688 #ifdef HAVE_FRIBIDI
689  bidi_cvt();
690 #endif
691  hash();
692 }
693 
694 void text_surface::hash()
695 {
696  int h = 0;
697  for(std::string::const_iterator it = str_.begin(), it_end = str_.end(); it != it_end; ++it)
698  h = ((h << 9) | (h >> (sizeof(int) * 8 - 9))) ^ (*it);
699  hash_ = h;
700 }
701 
702 void text_surface::measure() const
703 {
704  w_ = 0;
705  h_ = 0;
706 
707  for(text_chunk const &chunk : chunks_)
708  {
709  TTF_Font* ttfont = get_font(font_id(chunk.subset, font_size_, style_));
710  if(ttfont == nullptr) {
711  continue;
712  }
713 
714  int w, h;
715  TTF_SizeUTF8(ttfont, chunk.text.c_str(), &w, &h);
716  w_ += w;
717  h_ = std::max<int>(h_, h);
718  }
719 }
720 
721 size_t text_surface::width() const
722 {
723  if (w_ == -1) {
724  if(chunks_.empty())
725  chunks_ = split_text(str_);
726  measure();
727  }
728  return w_;
729 }
730 
731 size_t text_surface::height() const
732 {
733  if (h_ == -1) {
734  if(chunks_.empty())
735  chunks_ = split_text(str_);
736  measure();
737  }
738  return h_;
739 }
740 
741 std::vector<surface> const &text_surface::get_surfaces() const
742 {
743  if(initialized_)
744  return surfs_;
745 
746  initialized_ = true;
747 
748  // Impose a maximal number of characters for a text line. Do now draw
749  // any text longer that that, to prevent a SDL buffer overflow
750  if(width() > max_text_line_width)
751  return surfs_;
752 
753  for(text_chunk const &chunk : chunks_)
754  {
755  TTF_Font* ttfont = get_font(font_id(chunk.subset, font_size_, style_));
756 
757  surface s = surface(TTF_RenderUTF8_Blended(ttfont, chunk.text.c_str(), color_));
758  if(!s.null())
759  surfs_.push_back(s);
760  }
761 
762  return surfs_;
763 }
764 
765 class text_cache
766 {
767 public:
768  static text_surface &find(text_surface const &t);
769  static void resize(unsigned int size);
770 private:
771  typedef std::list< text_surface > text_list;
772  static text_list cache_;
773  static unsigned int max_size_;
774 };
775 
776 text_cache::text_list text_cache::cache_;
777 unsigned int text_cache::max_size_ = 50;
778 
779 void text_cache::resize(unsigned int size)
780 {
781  DBG_FT << "Text cache: resize from: " << max_size_ << " to: "
782  << size << " items in cache: " << cache_.size() << '\n';
783 
784  while(size < cache_.size()) {
785  cache_.pop_back();
786  }
787  max_size_ = size;
788 }
789 
790 
791 text_surface &text_cache::find(text_surface const &t)
792 {
793  static size_t lookup_ = 0, hit_ = 0;
794  text_list::iterator it_bgn = cache_.begin(), it_end = cache_.end();
795  text_list::iterator it = std::find(it_bgn, it_end, t);
796  if (it != it_end) {
797  cache_.splice(it_bgn, cache_, it);
798  ++hit_;
799  } else {
800  if (cache_.size() >= max_size_)
801  cache_.pop_back();
802  cache_.push_front(t);
803  }
804  if (++lookup_ % 1000 == 0) {
805  DBG_FT << "Text cache: " << lookup_ << " lookups, " << (hit_ / 10) << "% hits\n";
806  hit_ = 0;
807  }
808  return cache_.front();
809 }
810 
811 }
812 
813 static surface render_text(const std::string& text, int fontsize, const SDL_Color& color, int style, bool use_markup)
814 {
815  // we keep blank lines and spaces (may be wanted for indentation)
816  const std::vector<std::string> lines = utils::split(text, '\n', 0);
817  std::vector<std::vector<surface> > surfaces;
818  surfaces.reserve(lines.size());
819  size_t width = 0, height = 0;
820 
821  for(std::vector< std::string >::const_iterator ln = lines.begin(), ln_end = lines.end(); ln != ln_end; ++ln) {
822 
823  int sz = fontsize;
824  int text_style = style;
825 
826  std::string::const_iterator after_markup = use_markup ?
827  parse_markup(ln->begin(), ln->end(), &sz, nullptr, &text_style) : ln->begin();
828  text_surface txt_surf(sz, color, text_style);
829 
830  if (after_markup == ln->end() && (ln+1 != ln_end || lines.begin()+1 == ln_end)) {
831  // we replace empty line by a space (to have a line height)
832  // except for the last line if we have several
833  txt_surf.set_text(" ");
834  } else if (after_markup == ln->begin()) {
835  // simple case, no markup to skip
836  txt_surf.set_text(*ln);
837  } else {
838  const std::string line(after_markup,ln->end());
839  txt_surf.set_text(line);
840  }
841 
842  const text_surface& cached_surf = text_cache::find(txt_surf);
843  const std::vector<surface>&res = cached_surf.get_surfaces();
844 
845  if (!res.empty()) {
846  surfaces.push_back(res);
847  width = std::max<size_t>(cached_surf.width(), width);
848  height += cached_surf.height();
849  }
850  }
851 
852  if (surfaces.empty()) {
853  return surface();
854  } else if (surfaces.size() == 1 && surfaces.front().size() == 1) {
855  surface surf = surfaces.front().front();
856  SDL_SetAlpha(surf, SDL_SRCALPHA | SDL_RLEACCEL, SDL_ALPHA_OPAQUE);
857  return surf;
858  } else {
859 
860  surface res(create_compatible_surface(surfaces.front().front(),width,height));
861  if (res.null())
862  return res;
863 
864  size_t ypos = 0;
865  for(std::vector< std::vector<surface> >::const_iterator i = surfaces.begin(),
866  i_end = surfaces.end(); i != i_end; ++i) {
867  size_t xpos = 0;
868  size_t height = 0;
869 
870  for(std::vector<surface>::const_iterator j = i->begin(),
871  j_end = i->end(); j != j_end; ++j) {
872  SDL_SetAlpha(*j, 0, 0); // direct blit without alpha blending
873  SDL_Rect dstrect = sdl::create_rect(xpos, ypos, 0, 0);
874  sdl_blit(*j, nullptr, res, &dstrect);
875  xpos += (*j)->w;
876  height = std::max<size_t>((*j)->h, height);
877  }
878  ypos += height;
879  }
880 
881  return res;
882  }
883 }
884 
885 
886 surface get_rendered_text(const std::string& str, int size, const SDL_Color& color, int style)
887 {
888  // TODO maybe later also to parse markup here, but a lot to check
889  return render_text(str, size, color, style, false);
890 }
891 
892 SDL_Rect draw_text_line(surface& gui_surface, const SDL_Rect& area, int size,
893  const SDL_Color& color, const std::string& text,
894  int x, int y, bool use_tooltips, int style)
895 {
896  size = preferences::font_scaled(size);
897  if (gui_surface.null()) {
898  text_surface const &u = text_cache::find(text_surface(text, size, color, style));
899  return sdl::create_rect(0, 0, u.width(), u.height());
900  }
901 
902  if(area.w == 0) { // no place to draw
903  return sdl::create_rect(0, 0, 0, 0);
904  }
905 
906  const std::string etext = make_text_ellipsis(text, size, area.w);
907 
908  // for the main current use, we already parsed markup
909  surface surface(render_text(etext,size,color,style,false));
910  if(surface == nullptr) {
911  return sdl::create_rect(0, 0, 0, 0);
912  }
913 
914  SDL_Rect dest;
915  if(x!=-1) {
916  dest.x = x;
917 #ifdef HAVE_FRIBIDI
918  // Oron -- Conditional, until all draw_text_line calls have fixed area parameter
919  if(getenv("NO_RTL") == nullptr) {
920  bool is_rtl = text_cache::find(text_surface(text, size, color, style)).is_rtl();
921  if(is_rtl)
922  dest.x = area.x + area.w - surface->w - (x - area.x);
923  }
924 #endif
925  } else
926  dest.x = (area.w/2)-(surface->w/2);
927  if(y!=-1)
928  dest.y = y;
929  else
930  dest.y = (area.h/2)-(surface->h/2);
931  dest.w = surface->w;
932  dest.h = surface->h;
933 
934  if(line_width(text, size) > area.w) {
935  tooltips::add_tooltip(dest,text);
936  }
937 
938  if(dest.x + dest.w > area.x + area.w) {
939  dest.w = area.x + area.w - dest.x;
940  }
941 
942  if(dest.y + dest.h > area.y + area.h) {
943  dest.h = area.y + area.h - dest.y;
944  }
945 
946  if(gui_surface != nullptr) {
947  SDL_Rect src = dest;
948  src.x = 0;
949  src.y = 0;
950  sdl_blit(surface,&src,gui_surface,&dest);
951  }
952 
953  if(use_tooltips) {
954  tooltips::add_tooltip(dest,text);
955  }
956 
957  return dest;
958 }
959 
961 {
962  // Only returns the maximal size of the first font
963  TTF_Font* const font = get_font(font_id(0, size));
964  if(font == nullptr)
965  return 0;
966  return TTF_FontHeight(font);
967 }
968 
969 int line_width(const std::string& line, int font_size, int style)
970 {
971  return line_size(line,font_size,style).w;
972 }
973 
974 SDL_Rect line_size(const std::string& line, int font_size, int style)
975 {
976  line_size_cache_map& cache = line_size_cache[style][font_size];
977 
978  const line_size_cache_map::const_iterator i = cache.find(line);
979  if(i != cache.end()) {
980  return i->second;
981  }
982 
983  SDL_Rect res;
984 
985  const SDL_Color col = { 0, 0, 0, 0 };
986  text_surface s(line, font_size, col, style);
987 
988  res.w = s.width();
989  res.h = s.height();
990  res.x = res.y = 0;
991 
992  cache.insert(std::pair<std::string,SDL_Rect>(line,res));
993  return res;
994 }
995 
997  int max_width, int style)
998 {
999  static const std::string ellipsis = "...";
1000 
1001  if (line_width(text, font_size, style) <= max_width)
1002  return text;
1003  if(line_width(ellipsis, font_size, style) > max_width)
1004  return "";
1005 
1006  std::string current_substring;
1007 
1008  utf8::iterator itor(text);
1009 
1010  for(; itor != utf8::iterator::end(text); ++itor) {
1011  std::string tmp = current_substring;
1012  tmp.append(itor.substr().first, itor.substr().second);
1013 
1014  if (line_width(tmp + ellipsis, font_size, style) > max_width) {
1015  return current_substring + ellipsis;
1016  }
1017 
1018  current_substring.append(itor.substr().first, itor.substr().second);
1019  }
1020 
1021  return text; // Should not happen
1022 }
1023 
1024 }
1025 
1026 
1027 static bool add_font_to_fontlist(const config &fonts_config,
1028  std::vector<font::subset_descriptor>& fontlist, const std::string& name)
1029 {
1030  const config &font = fonts_config.find_child("font", "name", name);
1031  if (!font) {
1032  return false;
1033  }
1034  //DBG_FT << "Adding a font record: " << font.debug() << std::endl;
1035 
1036  fontlist.push_back(font::subset_descriptor(font));
1037 
1038  return true;
1039 }
1040 
1041 namespace font {
1042 
1043 namespace {
1044  t_string family_order_sans;
1045  t_string family_order_mono;
1046 } // namespace
1047 
1049 {
1050  //read font config separately, so we do not have to re-read the whole
1051  //config when changing languages
1052  config cfg;
1053  try {
1054  const std::string& cfg_path = filesystem::get_wml_location("hardwired/fonts.cfg");
1055  if(cfg_path.empty()) {
1056  ERR_FT << "could not resolve path to fonts.cfg, file not found\n";
1057  return false;
1058  }
1059 
1061  read(cfg, *stream);
1062  } catch(config::error &e) {
1063  ERR_FT << "could not read fonts.cfg:\n"
1064  << e.message << '\n';
1065  return false;
1066  }
1067 
1068  const config &fonts_config = cfg.child("fonts");
1069  if (!fonts_config)
1070  return false;
1071 
1072  std::set<std::string> known_fonts;
1073  for (const config &font : fonts_config.child_range("font")) {
1074  known_fonts.insert(font["name"]);
1075  if (font.has_attribute("bold_name")) {
1076  known_fonts.insert(font["bold_name"]);
1077  }
1078  if (font.has_attribute("italic_name")) {
1079  known_fonts.insert(font["italic_name"]);
1080  }
1081  }
1082 
1083  family_order_sans = fonts_config["family_order"];
1084  family_order_mono = fonts_config["family_order_monospace"];
1085 
1086  if(family_order_mono.empty()) {
1087  ERR_FT << "No monospace font family order defined, falling back to sans serif order\n";
1088  family_order_mono = family_order_sans;
1089  }
1090 
1091  const std::vector<std::string> font_order = utils::split(fonts_config["order"]);
1092  std::vector<font::subset_descriptor> fontlist;
1093  std::vector<std::string>::const_iterator font;
1094  for(font = font_order.begin(); font != font_order.end(); ++font) {
1095  add_font_to_fontlist(fonts_config, fontlist, *font);
1096  known_fonts.erase(*font);
1097  }
1098  std::set<std::string>::const_iterator kfont;
1099  for(kfont = known_fonts.begin(); kfont != known_fonts.end(); ++kfont) {
1100  add_font_to_fontlist(fonts_config, fontlist, *kfont);
1101  }
1102 
1103  if(fontlist.empty())
1104  return false;
1105 
1106  font::set_font_list(fontlist);
1107  return true;
1108 }
1109 
1111 {
1112  switch(fclass) {
1113  case FONT_MONOSPACE:
1114  return family_order_mono;
1115  default:
1116  return family_order_sans;
1117  }
1118 }
1119 
1121 {
1122  if(mode == CACHE_LOBBY) {
1123  text_cache::resize(1000);
1124  } else {
1125  text_cache::resize(50);
1126  }
1127 }
1128 
1129 
1130 }
const SDL_Color BIGMAP_COLOR
Definition: font.cpp:575
const SDL_Color BUTTON_COLOR
Definition: font.cpp:571
child_itors child_range(const std::string &key)
Definition: config.cpp:613
static unsigned int max_size_
Definition: font.cpp:773
int w_
Definition: font.cpp:607
bool initialized_
Definition: font.cpp:609
subset_id get_id(int ch)
Definition: font.cpp:175
bool null() const
Definition: utils.hpp:104
std::pair< int, subset_id > block_t
Definition: font.cpp:130
family_class
Font classes for get_font_families().
const SDL_Color TITLE_COLOR
Definition: font.cpp:573
const SDL_Color PETRIFIED_COLOR
Definition: font.cpp:572
static TTF_Font * get_font(font_id id)
Definition: font.cpp:295
std::vector< surface > surfs_
Definition: font.cpp:611
Graphical text output.
const SDL_Color BLACK_COLOR
Definition: font.cpp:569
std::string text
Definition: font.cpp:120
static l_noret error(LoadState *S, const char *why)
Definition: lundump.cpp:29
bool operator==(text_chunk const &t) const
Definition: font.cpp:116
ucs4_convert_impl::enableif< TD, typename TS::value_type >::type unicode_cast(const TS &source)
static surface render_text(const std::string &text, int fontsize, const SDL_Color &color, int style, bool use_markup)
Definition: font.cpp:813
bool operator!=(text_chunk const &t) const
Definition: font.cpp:117
SDL_Rect line_size(const std::string &line, int font_size, int style)
Determine the size of a line of text given a certain font size.
Definition: font.cpp:974
bool operator<(const font_id &o) const
Definition: font.cpp:81
int style
Definition: font.cpp:98
SDL_Color color_
Definition: font.cpp:605
CACHE
Definition: font.hpp:111
#define h
static std::map< int, std::map< int, line_size_cache_map > > line_size_cache
Definition: font.cpp:194
GLint GLint GLint GLint GLint GLint y
Definition: glew.h:1220
GLenum src
Definition: glew.h:2392
static std::vector< std::string > font_names
Definition: font.cpp:104
bool operator!=(const config &a, const config &b)
Definition: config.hpp:79
char_block_map()
Definition: font.cpp:125
TTF_Font * font
Definition: font.cpp:97
#define LOG_FT
Definition: font.cpp:61
GLenum mode
Definition: glew.h:2390
int subset_id
Definition: font.cpp:70
const SDL_Color NORMAL_COLOR
Definition: font.cpp:564
GLdouble GLdouble t
Definition: glew.h:1366
Definitions for the interface to Wesnoth Markup Language (WML).
#define DBG_FT
Definition: font.cpp:60
font_id(subset_id subset, int size, int style)
Definition: font.cpp:76
std::string str_
Definition: font.cpp:608
const SDL_Color GOOD_COLOR
Definition: font.cpp:567
static void set_font_list(const std::vector< subset_descriptor > &fontlist)
Definition: font.cpp:525
GLuint GLuint stream
Definition: glew.h:5239
static std::vector< std::string > bold_names
Definition: font.cpp:105
const SDL_Color DISABLED_COLOR
Definition: font.cpp:576
const t_string & get_font_families(family_class fclass)
Returns the currently defined fonts.
Definition: font.cpp:1110
int hash_
Definition: font.cpp:603
int h_
Definition: font.cpp:607
#define ERR_FT
Definition: font.cpp:63
GLsizei const char ** path
Definition: glew.h:4654
void insert(int first, int last, subset_id id)
Associates not-associated parts of a range with a new font.
Definition: font.cpp:134
GLuint GLuint end
Definition: glew.h:1221
GLuint64EXT * result
Definition: glew.h:10727
std::map< font_id, ttf_record > tfont_table
Definition: font.cpp:101
static std::string sub(const std::string &s)
Private function to surround an argument with brackets.
static TTF_Font * open_font(const std::string &fname, int size)
Definition: font.cpp:236
const SDL_Color BAD_COLOR
Definition: font.cpp:568
std::map< int, block_t > cbmap_t
Definition: font.cpp:131
std::vector< text_chunk > chunks_
Definition: font.cpp:610
int size
Definition: font.cpp:87
static text_list cache_
Definition: font.cpp:772
GLubyte GLubyte GLubyte GLubyte w
Definition: glew.h:1858
GLenum GLsizei len
Definition: glew.h:5662
static std::vector< text_chunk > split_text(std::string const &utf8_text)
Definition: font.cpp:197
int font_scaled(int size)
bool is_directory(const std::string &fname)
Returns true if the given file is a directory.
static bool check_font_file(std::string name)
Definition: font.cpp:503
std::string path
static char_block_map char_blocks
Definition: font.cpp:188
#define WRN_FT
Definition: font.cpp:62
std::vector< range > present_codepoints
Definition: font.cpp:470
void compress()
Compresses map by merging consecutive ranges with the same font, even if there is some unassociated r...
Definition: font.cpp:159
typedef int(WINAPI *PFNWGLRELEASEPBUFFERDCARBPROC)(HPBUFFERARB hPbuffer
surf
Definition: filter.cpp:143
GLuint color
Definition: glew.h:5801
int get_max_height(int size)
Definition: font.cpp:960
bool load_font_config()
Definition: font.cpp:1048
std::string::const_iterator parse_markup(std::string::const_iterator i1, std::string::const_iterator i2, int *font_size, SDL_Color *color, int *style)
Parses the markup-tags at the front of a string.
topen_font_cache open_fonts
Definition: font.cpp:229
void update_font_path() const
Updates the font path, when initialized it sets the fontpath to game_config::path.
Definition: font.cpp:386
GLuint res
Definition: glew.h:9258
static std::vector< std::string > italic_names
Definition: font.cpp:106
const std::vector< std::string > & get_binary_paths(const std::string &type)
Returns a vector with all possible paths to a given type of binary, e.g.
void init() const
Initializes the font path.
Definition: font.cpp:392
std::pair< int, int > range
Definition: font.cpp:469
void get_files_in_dir(const std::string &dir, std::vector< std::string > *files, std::vector< std::string > *dirs=nullptr, file_name_option mode=FILE_NAME_ONLY, file_filter_option filter=NO_FILTER, file_reorder_option reorder=DONT_REORDER, file_tree_checksum *checksum=nullptr)
Populates 'files' with all the files and 'dirs' with all the directories in dir.
std::map< std::string, tfilter >::iterator itor
Definition: filter.cpp:199
Thrown by operations encountering invalid UTF-8 data.
std::string get_wml_location(const std::string &filename, const std::string &current_dir=std::string())
Returns a complete path to the actual WML file or directory or an empty string if the file isn't pres...
void cache_mode(CACHE mode)
Definition: font.cpp:1120
std::map< std::string, SDL_Rect > line_size_cache_map
Definition: font.cpp:191
SDL_Color inverse(const SDL_Color &color)
Definition: utils.cpp:2458
const GLvdpauSurfaceNV * surfaces
Definition: glew.h:11139
bool operator==(const config &a, const config &b)
Definition: config.cpp:1527
const SDL_Color LABEL_COLOR
Definition: font.cpp:574
static void clear_fonts()
Definition: font.cpp:346
const SDL_Color LOBBY_COLOR
Definition: font.cpp:566
static tcache cache
Definition: minimap.cpp:139
GLfloat GLfloat GLfloat GLfloat h
Definition: glew.h:5910
size_t i
Definition: function.cpp:1057
GLint GLint GLint GLint GLint x
Definition: glew.h:1220
static TTF_Font * open_font_impl(const std::string &, int)
Definition: font.cpp:249
Declarations for File-IO.
void read(config &cfg, std::istream &in, abstract_validator *validator)
Definition: parser.cpp:400
GLdouble GLdouble GLdouble r
Definition: glew.h:1374
#define SDL_SRCALPHA
Definition: alpha.hpp:28
static iterator_base end(const string_type &str)
static tfont_table font_table
Definition: font.cpp:103
int font_size_
Definition: font.cpp:604
int style
Definition: font.cpp:88
void deinit() const
Deinitializes the font path.
Definition: font.cpp:431
GLint GLint GLint GLint GLint GLint GLsizei GLsizei height
Definition: glew.h:1220
std::string make_text_ellipsis(const std::string &text, int font_size, int max_width, int style)
If the text exceeds the specified max width, end it with an ellipsis (...)
Definition: font.cpp:996
GLuint const GLchar * name
Definition: glew.h:1782
SDL_Rect create_rect(const int x, const int y, const int w, const int h)
Creates an empty SDL_Rect.
Definition: rect.cpp:28
bool operator==(const font_id &o) const
Definition: font.cpp:77
GLsizeiptr size
Definition: glew.h:1649
std::istream * preprocess_file(std::string const &fname, preproc_map *defines)
cbmap_t cbmap
Definition: font.cpp:132
SDL_Rect draw_text_line(surface &gui_surface, const SDL_Rect &area, int size, const SDL_Color &color, const std::string &text, int x, int y, bool use_tooltips, int style)
Definition: font.cpp:892
GLclampd n
Definition: glew.h:5903
Contains the SDL_Rect helper code.
bool has_attribute(const std::string &key) const
Definition: config.cpp:514
bool find(E event, F functor)
Tests whether an event handler is available.
std::map< std::pair< std::string, int >, TTF_Font * > topen_font_cache
Definition: font.cpp:228
const std::pair< typename string_type::const_iterator, typename string_type::const_iterator > & substr() const
config & child(const std::string &key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:658
const SDL_Color GRAY_COLOR
Definition: font.cpp:565
config & find_child(const std::string &key, const std::string &name, const std::string &value)
Returns the first child of tag key with a name attribute containing value.
Definition: config.cpp:1010
Standard logging facilities (interface).
surface create_compatible_surface(const surface &surf, int width, int height)
Definition: utils.cpp:2166
std::string message
Definition: exceptions.hpp:29
GLint * first
Definition: glew.h:1496
int style_
Definition: font.cpp:606
std::string ellipsis
boost::optional< std::string > bold_name
Definition: font.cpp:466
static bool add_font_to_fontlist(const config &fonts_config, std::vector< font::subset_descriptor > &fontlist, const std::string &name)
Definition: font.cpp:1027
subset_id subset
Definition: font.cpp:119
GLint GLint GLint GLint GLint GLint GLsizei width
Definition: glew.h:1220
std::vector< std::string > split(std::string const &val, const char c, const int flags)
Splits a (comma-)separated string into a vector of pieces.
#define e
text_chunk(subset_id subset)
Definition: font.cpp:110
const SDL_Color YELLOW_COLOR
Definition: font.cpp:570
void sdl_blit(const surface &src, SDL_Rect *src_rect, surface &dst, SDL_Rect *dst_rect)
Definition: utils.hpp:112
Definition: font.cpp:73
SDL_RWops * load_RWops(const std::string &path)
int SDL_SetAlpha(SDL_Surface *surface, Uint32 flag, Uint8 alpha)
Definition: alpha.cpp:18
subset_id subset
Definition: font.cpp:86
std::string::const_iterator iterator
Definition: tokenizer.hpp:21
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:83
surface get_rendered_text(const std::string &str, int size, const SDL_Color &color, int style)
Definition: font.cpp:886
Compatibility layer for using SDL 1.2 and 2.0.
GLdouble s
Definition: glew.h:1358
const int font_size
int add_tooltip(const SDL_Rect &rect, const std::string &message, const std::string &action, bool use_markup, const surface &foreground)
Definition: tooltips.cpp:180
bool file_exists(const std::string &name)
Returns true if a file or directory with such name already exists.
font_id(subset_id subset, int size)
Definition: font.cpp:75
GLsizei const GLcharARB ** string
Definition: glew.h:4503
int line_width(const std::string &line, int font_size, int style)
Determine the width of a line of text given a certain font size.
Definition: font.cpp:969
static lg::log_domain log_font("font")
boost::optional< std::string > italic_name
Definition: font.cpp:467
std::string name
Definition: font.cpp:465