The Battle for Wesnoth  1.15.7+dev
sdl_ttf.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2016 - 2018 by Chris Beck<render787@gmail.com>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 #include "font/sdl_ttf.hpp"
16 
17 #include "font/error.hpp"
18 #include "font/font_config.hpp"
19 #include "font/font_id.hpp"
20 #include "font/text_cache.hpp"
21 #include "font/text_surface.hpp"
22 
23 #include "filesystem.hpp"
24 #include "font/marked-up_text.hpp"
25 #include "game_config.hpp"
26 #include "log.hpp"
27 #include "preferences/general.hpp"
28 #include "tooltips.hpp"
29 
30 #include "sdl/rect.hpp"
31 #include "sdl/surface.hpp"
33 
34 #include <SDL2/SDL_ttf.h>
35 
36 #include <map>
37 #include <string>
38 #include <vector>
39 
40 static lg::log_domain log_font("font");
41 #define DBG_FT LOG_STREAM(debug, log_font)
42 #define LOG_FT LOG_STREAM(info, log_font)
43 #define WRN_FT LOG_STREAM(warn, log_font)
44 #define ERR_FT LOG_STREAM(err, log_font)
45 
46 namespace font
47 {
48 namespace
49 {
50 // Record stored in the font table.
51 // If the record for font_id (FOO, Bold + Underline) is a record (BAR, Bold),
52 // it means that BAR is a Bold-styled version of FOO which we shipped with the
53 // game, and now SDL_TTF should be used to style BAR as underline for the final results.
54 struct ttf_record
55 {
56  std::shared_ptr<TTF_Font> font;
57  int style;
58 };
59 static std::map<font_id, ttf_record> font_table;
60 
61 // The indices in these vectors correspond to the font_id.subset values in font_table.
62 static std::vector<std::string> font_names;
63 static std::vector<std::string> bold_names;
64 static std::vector<std::string> italic_names;
65 
66 struct family_record
67 {
68  std::shared_ptr<const TTF_Font> font;
71 };
72 /**
73  * Used for implementing find_font_containing, the elements are in the same order as the arguments
74  * to set_font_list(). The fonts here are a subset of those in font_table, because
75  * find_font_containing doesn't need size-specific instances of a font.
76  *
77  * In most locales, the subset_ids will match the indices into this vector. This is only a
78  * coincidence, and it won't be true (at the time of writing) in Chinese.
79  *
80  * \todo Are all variants of a font guaranteed to have exactly the same glyphs? For example, might
81  * an italic variant only contain the glyphs which are major improvements on an automatic skew of
82  * the non-italic version?
83  */
84 std::vector<family_record> family_table;
85 
86 const auto no_font_found = family_record{nullptr, -1, ""};
87 /**
88  * Given a unicode code point, returns the first (using the order passed to set_font_list) font
89  * that includes that code point. Returns no_font_found if none of the known fonts contain this value.
90  */
91 const family_record& find_font_containing(int ch)
92 {
93  for(const auto& i : family_table) {
94  if(TTF_GlyphIsProvided(i.font.get(), ch)) {
95  return i;
96  }
97  }
98  LOG_FT << "Glyph " << ch << " not provided by any font\n";
99  return no_font_found;
100 }
101 
102 // cache sizes of small text
103 typedef std::map<std::string, SDL_Rect> line_size_cache_map;
104 
105 // map of styles -> sizes -> cache
106 static std::map<int, std::map<int, line_size_cache_map>> line_size_cache;
107 
108 /**
109  * Destructor for using std::unique_ptr or std::shared_ptr as an RAII holder for a TTF_Font.
110  */
111 struct font_deleter
112 {
113  void operator()(TTF_Font* font)
114  {
115  if(font != nullptr)
116  TTF_CloseFont(font);
117  }
118 };
119 
120 std::shared_ptr<TTF_Font> open_font(const std::string& fname, int size)
121 {
123  if(!game_config::path.empty()) {
124  name = game_config::path + "/fonts/" + fname;
125  if(!filesystem::file_exists(name)) {
126  name = "fonts/" + fname;
127  if(!filesystem::file_exists(name)) {
128  name = fname;
129  if(!filesystem::file_exists(name)) {
130  ERR_FT << "Failed opening font: '" << name << "': No such file or directory" << std::endl;
131  return nullptr;
132  }
133  }
134  }
135 
136  } else {
137  name = "fonts/" + fname;
138  if(!filesystem::file_exists(name)) {
139  if(!filesystem::file_exists(fname)) {
140  ERR_FT << "Failed opening font: '" << name << "': No such file or directory" << std::endl;
141  return nullptr;
142  }
143  name = fname;
144  }
145  }
146 
148  std::unique_ptr<TTF_Font, font_deleter> font;
149  font.reset(TTF_OpenFontRW(rwops.release(), true, size)); // SDL takes ownership of rwops
150  if(font == nullptr) {
151  ERR_FT << "Failed opening font: '" << fname << "'\n";
152  ERR_FT << "TTF_OpenFont: " << TTF_GetError() << std::endl;
153  return nullptr;
154  }
155 
156  DBG_FT << "Opened a font: " << fname << ", in size " << size << std::endl;
157 
158  return font;
159 }
160 
161 } // anonymous namespace
162 
163 // Gets an appropriately configured TTF Font, for this font size and style.
164 // Loads fonts if necessary. For styled fonts, we search for a ``shipped''
165 // version of the font which is prestyled. If this fails we find the closest
166 // thing which we did ship, and store a record of this, which allows to
167 // rapidly correct the remaining styling using SDL_TTF.
168 //
169 // Uses the font table for caching.
170 std::shared_ptr<TTF_Font> sdl_ttf::get_font(font_id id)
171 {
172  const auto it = font_table.find(id);
173  if(it != font_table.end() && it->second.font != nullptr) {
174  return it->second.font;
175  }
176 
177  // There's no record, so we need to try to find a solution for this font
178  // and make a record of it. If the indices are out of bounds don't bother though.
179  if(id.subset < 0 || std::size_t(id.subset) >= font_names.size()) {
180  return nullptr;
181  }
182 
183  // Favor to use the shipped Italic font over bold if both are present and are needed.
184  if((id.style & TTF_STYLE_ITALIC) && italic_names[id.subset].size()) {
185  if(auto font = open_font(italic_names[id.subset], id.size)) {
186  ttf_record rec{font, TTF_STYLE_ITALIC};
187  // The next line adds bold if needed
188  TTF_SetFontStyle(font.get(), id.style ^ TTF_STYLE_ITALIC);
189  font_table.emplace(id, rec);
190  return font;
191  }
192  }
193 
194  // Now see if the shipped Bold font is useful and available.
195  if((id.style & TTF_STYLE_BOLD) && bold_names[id.subset].size()) {
196  if(auto font = open_font(bold_names[id.subset], id.size)) {
197  ttf_record rec{font, TTF_STYLE_BOLD};
198  // The next line adds italic if needed
199  TTF_SetFontStyle(font.get(), id.style ^ TTF_STYLE_BOLD);
200  font_table.emplace(id, rec);
201  return font;
202  }
203  }
204 
205  // Try just to use the basic version of the font then.
206  if(font_names[id.subset].size()) {
207  if(auto font = open_font(font_names[id.subset], id.size)) {
208  ttf_record rec{font, TTF_STYLE_NORMAL};
209  TTF_SetFontStyle(font.get(), id.style);
210  font_table.emplace(id, rec);
211  return font;
212  }
213  }
214 
215  // Failed to find a font.
216  ttf_record rec{nullptr, TTF_STYLE_NORMAL};
217  font_table.emplace(id, rec);
218  return nullptr;
219 }
220 
221 /***
222  * Interface to SDL_TTF
223  */
224 
225 static surface render_text(const std::string& text, int fontsize, const color_t& color, int style, bool use_markup)
226 {
227  // we keep blank lines and spaces (may be wanted for indentation)
228  const std::vector<std::string> lines = utils::split(text, '\n', 0);
229  std::vector<std::vector<surface>> surfaces;
230  surfaces.reserve(lines.size());
231  std::size_t width = 0, height = 0;
232 
233  for(std::vector< std::string >::const_iterator ln = lines.begin(), ln_end = lines.end(); ln != ln_end; ++ln) {
234 
235  int sz = fontsize;
236  int text_style = style;
237 
238  std::string::const_iterator after_markup = use_markup ?
239  parse_markup(ln->begin(), ln->end(), &sz, nullptr, &text_style) : ln->begin();
240  text_surface txt_surf(sz, color, text_style);
241 
242  if (after_markup == ln->end() && (ln+1 != ln_end || lines.begin()+1 == ln_end)) {
243  // we replace empty line by a space (to have a line height)
244  // except for the last line if we have several
245  txt_surf.set_text(" ");
246  } else if (after_markup == ln->begin()) {
247  // simple case, no markup to skip
248  txt_surf.set_text(*ln);
249  } else {
250  const std::string line(after_markup,ln->end());
251  txt_surf.set_text(line);
252  }
253 
254  const text_surface& cached_surf = text_cache::find(txt_surf);
255  const std::vector<surface>&res = cached_surf.get_surfaces();
256 
257  if (!res.empty()) {
258  surfaces.push_back(res);
259  width = std::max<std::size_t>(cached_surf.width(), width);
260  height += cached_surf.height();
261  }
262  }
263 
264  if (surfaces.empty()) {
265  return surface();
266  } else if (surfaces.size() == 1 && surfaces.front().size() == 1) {
267  surface surf = surfaces.front().front();
268  return surf;
269  } else {
270  surface res(width,height);
271  if (!res)
272  return res;
273 
274  std::size_t ypos = 0;
275  for(std::vector< std::vector<surface>>::iterator i = surfaces.begin(),
276  i_end = surfaces.end(); i != i_end; ++i) {
277  std::size_t xpos = 0;
278  height = 0;
279 
280  for(std::vector<surface>::iterator j = i->begin(),
281  j_end = i->end(); j != j_end; ++j) {
282  SDL_Rect dstrect = sdl::create_rect(xpos, ypos, 0, 0);
283  blit_surface(*j, nullptr, res, &dstrect);
284  xpos += (*j)->w;
285  height = std::max<std::size_t>((*j)->h, height);
286  }
287  ypos += height;
288  }
289 
290  return res;
291  }
292 }
293 
294 
295 surface get_rendered_text(const std::string& str, int size, const color_t& color, int style)
296 {
297  // TODO maybe later also to parse markup here, but a lot to check
298  return render_text(str, size, color, style, false);
299 }
300 
301 SDL_Rect draw_text_line(surface& gui_surface, const SDL_Rect& area, int size,
302  const color_t& color, const std::string& text,
303  int x, int y, bool use_tooltips, int style)
304 {
305  size = preferences::font_scaled(size);
306  if (!gui_surface) {
307  const text_surface& u = text_cache::find(text_surface(text, size, color, style));
308  return sdl::create_rect(0, 0, u.width(), u.height());
309  }
310 
311  if(area.w == 0) { // no place to draw
312  return {0, 0, 0, 0};
313  }
314 
315  const std::string etext = make_text_ellipsis(text, size, area.w);
316 
317  // for the main current use, we already parsed markup
318  surface surface(render_text(etext,size,color,style,false));
319  if(surface == nullptr) {
320  return {0, 0, 0, 0};
321  }
322 
323  SDL_Rect dest;
324  if(x!=-1) {
325  dest.x = x;
326 #ifdef HAVE_FRIBIDI
327  // Oron -- Conditional, until all draw_text_line calls have fixed area parameter
328  if(getenv("NO_RTL") == nullptr) {
329  bool is_rtl = text_cache::find(text_surface(text, size, color, style)).is_rtl();
330  if(is_rtl)
331  dest.x = area.x + area.w - surface->w - (x - area.x);
332  }
333 #endif
334  } else
335  dest.x = (area.w/2)-(surface->w/2);
336  if(y!=-1)
337  dest.y = y;
338  else
339  dest.y = (area.h/2)-(surface->h/2);
340  dest.w = surface->w;
341  dest.h = surface->h;
342 
343  if(line_width(text, size) > area.w) {
344  tooltips::add_tooltip(dest,text);
345  }
346 
347  if(dest.x + dest.w > area.x + area.w) {
348  dest.w = area.x + area.w - dest.x;
349  }
350 
351  if(dest.y + dest.h > area.y + area.h) {
352  dest.h = area.y + area.h - dest.y;
353  }
354 
355  if(gui_surface != nullptr) {
356  SDL_Rect src = dest;
357  src.x = 0;
358  src.y = 0;
359  sdl_blit(surface,&src,gui_surface,&dest);
360  }
361 
362  if(use_tooltips) {
363  tooltips::add_tooltip(dest,text);
364  }
365 
366  return dest;
367 }
368 
370 {
371  // Only returns the maximal size of the first font
372  const auto font = sdl_ttf::get_font(font_id(0, size));
373  if(font == nullptr)
374  return 0;
375  return TTF_FontHeight(font.get());
376 }
377 
378 int line_width(const std::string& line, int font_size, int style)
379 {
380  return line_size(line,font_size,style).w;
381 }
382 
383 SDL_Rect line_size(const std::string& line, int font_size, int style)
384 {
385  line_size_cache_map& cache = line_size_cache[style][font_size];
386 
387  const line_size_cache_map::const_iterator i = cache.find(line);
388  if(i != cache.end()) {
389  return i->second;
390  }
391 
392  SDL_Rect res;
393 
394  const color_t col { 0, 0, 0, 0 };
395  text_surface s(line, font_size, col, style);
396 
397  res.w = s.width();
398  res.h = s.height();
399  res.x = res.y = 0;
400 
401  cache.emplace(line,res);
402  return res;
403 }
404 
406  int max_width, int style)
407 {
408  if (line_width(text, font_size, style) <= max_width)
409  return text;
410  if(line_width(ellipsis, font_size, style) > max_width)
411  return "";
412 
413  std::string current_substring;
414 
415  try {
416  utf8::iterator itor(text);
417  for(; itor != utf8::iterator::end(text); ++itor) {
418  std::string tmp = current_substring;
419  tmp.append(itor.substr().first, itor.substr().second);
420 
421  if (line_width(tmp + ellipsis, font_size, style) > max_width) {
422  return current_substring + ellipsis;
423  }
424 
425  current_substring.append(itor.substr().first, itor.substr().second);
426  }
427  }
429  WRN_FT << "Invalid UTF-8 string: \"" << text << "\"" << std::endl;
430  return "";
431  }
432 
433  return text; // Should not happen
434 }
435 
436 /***
437  * Initialize and destruction
438  */
439 
441 {
442  const int res = TTF_Init();
443  if(res == -1) {
444  ERR_FT << "Could not initialize SDL_TTF" << std::endl;
445  throw font::error("SDL_TTF could not initialize, TTF_INIT returned: " + std::to_string(res));
446  } else {
447  LOG_FT << "Initialized true type fonts\n";
448  }
449 }
450 
451 static void clear_fonts()
452 {
453  // Ensure that the shared_ptr<TTF_Font>s' destructors run before TTF_Quit().
454  font_table.clear();
455  family_table.clear();
456 
457  font_names.clear();
458  bold_names.clear();
459  italic_names.clear();
460 
461  line_size_cache.clear();
462 }
463 
465 {
466  clear_fonts();
467  TTF_Quit();
468 }
469 
470 // sets the font list to be used.
471 void sdl_ttf::set_font_list(const std::vector<subset_descriptor>& fontlist)
472 {
473  // Wesnoth's startup sequence usually loads the same set of fonts twice.
474  // See if we can use the already-loaded fonts.
475  if(!font_names.empty()) {
476  std::vector<family_record> reordered_family_table;
477  bool found_all_fonts = true;
478  for(const auto& f : fontlist) {
479  // Ignore fonts if the font file doesn't exist - this matches the behavior of when we
480  // can't reuse the already-loaded fonts.
481  if(!check_font_file(f.name))
482  continue;
483  const auto& old_record = std::find_if(
484  family_table.cbegin(), family_table.cend(), [&f](family_record x) { return f.name == x.name; });
485  if(old_record == family_table.cend()) {
486  found_all_fonts = false;
487  break;
488  }
489  reordered_family_table.emplace_back(*old_record);
490  }
491  if(found_all_fonts) {
492  std::swap(family_table, reordered_family_table);
493  DBG_FT << "Reordered the font list, the order is now:\n";
494  for(const auto& x : family_table) {
495  DBG_FT << "[" << x.subset << "]:\t\tbase:\t'" << x.name << "'\n";
496  }
497  return;
498  }
499  }
500 
501  // The existing fonts weren't sufficient, or this is the first time that this function has been
502  // called. Load all the fonts from scratch.
503  clear_fonts();
504 
505  // To access TTF_GlyphIsProvided, we need to create instances of each font. Choose a size that
506  // the GUI will want to use.
507  const auto default_size = preferences::font_scaled(font::SIZE_NORMAL);
508 
509  for(const auto& f : fontlist) {
510  if(!check_font_file(f.name))
511  continue;
512  // Insert fonts only if the font file exists
513  const subset_id subset = font_names.size();
514  font_names.push_back(f.name);
515 
516  if(f.bold_name && check_font_file(*f.bold_name)) {
517  bold_names.push_back(*f.bold_name);
518  } else {
519  bold_names.emplace_back();
520  }
521 
522  if(f.italic_name && check_font_file(*f.italic_name)) {
523  italic_names.push_back(*f.italic_name);
524  } else {
525  italic_names.emplace_back();
526  }
527 
528  auto font = sdl_ttf::get_font(font_id{subset, default_size});
529  family_table.push_back(family_record{std::move(font), subset, f.name});
530  }
531 
532  assert(font_names.size() == bold_names.size());
533  assert(font_names.size() == italic_names.size());
534 
535  DBG_FT << "Set the font list. The styled font families are:\n";
536 
537  for(std::size_t i = 0; i < font_names.size(); ++i) {
538  DBG_FT << "[" << i << "]:\t\tbase:\t'" << font_names[i] << "'\tbold:\t'" << bold_names[i] << "'\titalic:\t'"
539  << italic_names[i] << "'\n";
540  }
541 }
542 
543 /**
544  * Splits the UTF-8 text into text_chunks using the same font.
545  *
546  * This uses a greedy-match - once we've found the start of a chunk,
547  * include as many characters as we can in the same chunk.
548  *
549  * If we've got a fallback font that contains all characters, and a
550  * preferred font that will only contains some of them, this means that
551  * we minimize the number of times that we switch from one font to the
552  * other - once we've had to use the fallback, keep using it.
553  *
554  * This also means that combining characters such as U+308 or U+FE00 are
555  * kept with the character that they should be modifying.
556  */
557 std::vector<text_chunk> sdl_ttf::split_text(const std::string& utf8_text)
558 {
559  std::vector<text_chunk> chunks;
560 
561  if(utf8_text.empty())
562  return chunks;
563 
564  try {
565  const auto end = utf8::iterator::end(utf8_text);
566  auto chunk_start = utf8::iterator(utf8_text);
567  while(chunk_start != end) {
568  auto& family = find_font_containing(*chunk_start);
569  if(family.subset >= 0) {
570  auto ch = chunk_start;
571  auto last_in_chunk = chunk_start;
572  while(ch != end && TTF_GlyphIsProvided(family.font.get(), *ch)) {
573  last_in_chunk = ch;
574  ++ch;
575  }
576  chunks.emplace_back(
577  family.subset, std::string{chunk_start.substr().first, last_in_chunk.substr().second});
578  chunk_start = ch;
579  } else {
580  ++chunk_start;
581  }
582  }
583  } catch(utf8::invalid_utf8_exception&) {
584  WRN_FT << "Invalid UTF-8 string: \"" << utf8_text << "\"" << std::endl;
585  }
586  return chunks;
587 }
588 
589 } // end namespace font
bool check_font_file(std::string name)
Test if a font file exists.
Definition: font_config.cpp:63
Note: Specific to sdl_ttf.
std::string::const_iterator parse_markup(std::string::const_iterator i1, std::string::const_iterator i2, int *font_size, color_t *color, int *style)
Parses the markup-tags at the front of a string.
static l_noret error(LoadState *S, const char *why)
Definition: lundump.cpp:39
#define ERR_FT
Definition: sdl_ttf.cpp:44
static surface render_text(const std::string &text, int fontsize, const color_t &color, int style, bool use_markup)
Definition: sdl_ttf.cpp:225
int subset_id
Font family, acts an an enumeration with each font loaded by stl_ttf::set_font_list getting an indivi...
Definition: font_id.hpp:40
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:267
rwops_ptr make_read_RWops(const std::string &path)
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: sdl_ttf.cpp:383
std::size_t width() const
std::string str
Definition: statement.cpp:110
subset_id subset
Definition: sdl_ttf.cpp:69
ucs4::iterator_base< std::string, ucs4_convert_impl::convert_impl< char >::type > iterator
Definition: unicode.hpp:40
const int SIZE_NORMAL
Definition: constants.cpp:19
void blit_surface(const surface &surf, const SDL_Rect *srcrect, surface &dst, const SDL_Rect *dstrect)
Replacement for sdl_blit.
Definition: utils.cpp:2009
static std::vector< text_chunk > split_text(const std::string &utf8_text)
Splits the UTF-8 text into text_chunks using the same font.
Definition: sdl_ttf.cpp:557
const std::string ellipsis
Definition: constants.cpp:35
static lg::log_domain log_font("font")
std::shared_ptr< TTF_Font > font
Definition: sdl_ttf.cpp:56
int font_scaled(int size)
Definition: general.cpp:449
static std::shared_ptr< TTF_Font > get_font(font_id)
Definition: sdl_ttf.cpp:170
std::string path
Definition: game_config.cpp:39
const std::pair< typename string_type::const_iterator, typename string_type::const_iterator > & substr() const
void swap(config &lhs, config &rhs)
Implement non-member swap function for std::swap (calls config::swap).
Definition: config.cpp:1399
int get_max_height(int size)
Definition: sdl_ttf.cpp:369
void set_text(const std::string &str)
const std::vector< surface > & get_surfaces() const
std::size_t i
Definition: function.cpp:933
Thrown by operations encountering invalid UTF-8 data.
#define WRN_FT
Definition: sdl_ttf.cpp:43
u64 size
Definition: statement.cpp:80
static text_surface & find(const text_surface &t)
Definition: text_cache.cpp:37
static void clear_fonts()
Definition: sdl_ttf.cpp:451
static map_location::DIRECTION s
static void set_font_list(const std::vector< subset_descriptor > &fontlist)
Set the list of fonts.
Definition: sdl_ttf.cpp:471
#define LOG_FT
Definition: sdl_ttf.cpp:42
static tcache cache
Definition: minimap.cpp:134
std::string name
Definition: sdl_ttf.cpp:70
#define DBG_FT
Definition: sdl_ttf.cpp:41
surface get_rendered_text(const std::string &str, int size, const color_t &color, int style)
Definition: sdl_ttf.cpp:295
Declarations for File-IO.
std::unique_ptr< SDL_RWops, void(*)(SDL_RWops *)> rwops_ptr
Definition: filesystem.hpp:42
std::size_t height() const
static iterator_base end(const string_type &str)
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: sdl_ttf.cpp:405
SDL_Rect create_rect(const int x, const int y, const int w, const int h)
Creates an SDL_Rect with the given dimensions.
Definition: rect.hpp:39
Contains the SDL_Rect helper code.
SDL_Rect draw_text_line(surface &gui_surface, const SDL_Rect &area, int size, const color_t &color, const std::string &text, int x, int y, bool use_tooltips, int style)
Definition: sdl_ttf.cpp:301
#define f
std::vector< std::string > split(const config_attribute_value &val)
Standard logging facilities (interface).
void sdl_blit(const surface &src, SDL_Rect *src_rect, surface &dst, SDL_Rect *dst_rect)
Definition: utils.hpp:33
int style
Definition: sdl_ttf.cpp:57
const int font_size
Definition: button.cpp:40
int add_tooltip(const SDL_Rect &rect, const std::string &message, const std::string &action, bool use_markup, const surface &foreground)
Definition: tooltips.cpp:175
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: sdl_ttf.cpp:378
std::string::const_iterator iterator
Definition: tokenizer.hpp:24