1 #include <algorithm>
2 #include <cmath>
3 #include <numeric>
4 #include <sstream>
5 #include <limits>
6 #ifdef TTF
7 #ifndef WINDOWS
8 #include <glob.h>
9 #endif
10 #include <SDL_ttf.h>
11 #endif // TTF
12 
13 #include "font.h"
14 #include "asc.h"
15 #include "chat.h"
16 #include "colors.h"
17 #include "elconfig.h"
18 #include "elloggingwrapper.h"
19 #include "exceptions/extendedexception.hpp"
20 #include "io/elpathwrapper.h"
21 #include "init.h"
22 #include "gl_init.h"
23 #include "textures.h"
24 
25 namespace
26 {
27 
28 #ifdef TTF
next_power_of_two(int n)29 int next_power_of_two(int n)
30 {
31 	int res = 1;
32 	while (res < n)
33 		res *= 2;
34 	return res;
35 }
36 #endif
37 
memcspn(const unsigned char * text,size_t len,const unsigned char * reject,size_t len_reject)38 size_t memcspn(const unsigned char* text, size_t len,
39 	const unsigned char* reject, size_t len_reject)
40 {
41 	size_t i;
42 	bool rej[256] = { false };
43 	for (i = 0; i < len_reject; ++i)
44 		rej[reject[i]] = true;
45 	for (i = 0; i < len && !rej[text[i]]; ++i) /* do nothing */;
46 	return i;
47 }
48 
filter_messages(const text_message * msgs,size_t msgs_size,Uint8 filter,size_t msg_start,size_t & imsg,size_t & ichar)49 bool filter_messages(const text_message *msgs, size_t msgs_size, Uint8 filter,
50 	size_t msg_start, size_t &imsg, size_t &ichar)
51 {
52 #ifndef MAP_EDITOR2
53 	if (filter != FILTER_ALL)
54 	{
55 		// skip all messages of the wrong channel
56 		while (skip_message(&msgs[imsg], filter))
57 		{
58 			if (++imsg >= msgs_size)
59 				imsg = 0;
60 			ichar = 0;
61 			if (imsg == msg_start || !msgs[imsg].data || imsg == msg_start)
62 				return true;
63 		}
64 	}
65 #endif
66 	return !msgs[imsg].data || msgs[imsg].deleted;
67 }
68 
pos_selected(const select_info * sel,size_t imsg,size_t ichar)69 bool pos_selected(const select_info *sel, size_t imsg, size_t ichar)
70 {
71 	if (!sel || TEXT_FIELD_SELECTION_EMPTY(sel))
72 		return false;
73 
74 	std::pair<size_t, size_t> start = std::make_pair(size_t(sel->sm), size_t(sel->sc));
75 	std::pair<size_t, size_t> end   = std::make_pair(size_t(sel->em), size_t(sel->ec));
76 	if (end < start)
77 		std::swap(start, end);
78 
79 	return (imsg > start.first || (imsg == start.first && ichar >= start.second))
80 		&& (imsg < end.first   || (imsg == end.first   && ichar <= end.second));
81 }
82 
83 #ifdef TTF
open_font(const std::string & file_name,int point_size)84 TTF_Font* open_font(const std::string& file_name, int point_size)
85 {
86 	// First try to interpret ttf_file_name as a path relative to the data directory. If that fails,
87 	// try a path relative to the ttf_directory. If that also fails, try as an absolute path.
88 	std::string path = datadir + file_name;
89 	TTF_Font *font = TTF_OpenFont(path.c_str(), point_size);
90 	if (!font)
91 	{
92 		path = ttf_directory + file_name;
93 		font = TTF_OpenFont(path.c_str(), point_size);
94 		if (!font)
95 		{
96 			font = TTF_OpenFont(file_name.c_str(), point_size);
97 		}
98 	}
99 	return font;
100 }
101 #endif
102 
103 } // namespace
104 
105 namespace eternal_lands
106 {
107 
FontOption(size_t font_nr)108 FontOption::FontOption(size_t font_nr): _font_nr(font_nr), _file_name(), _file_base_name(), _font_name(),
109 #ifdef TTF
110 	_is_ttf(false),
111 #endif
112 	_fixed_width(), _failed(false)
113 {
114 	static const std::array<const char*, 7> file_names = { {
115 		"textures/font.dds",
116 		"textures/font.dds",
117 		"textures/font2.dds",
118 		"textures/font3.dds",
119 		"textures/font5.dds",
120 		"textures/font6.dds",
121 		"textures/font7.dds"
122 	} };
123 
124 	std::ostringstream os;
125 	if (font_nr > file_names.size())
126 	{
127 		_failed = true;
128 		LOG_ERROR("Invalid font number %zu", font_nr);
129 	}
130 	else if (font_nr == 0)
131 	{
132 		_file_name = file_names[font_nr];
133 		_font_name = "Type 1 (fixed)";
134 	}
135 	else
136 	{
137 		_file_name = file_names[font_nr];
138 		size_t begin = _file_name.find_last_of('/') + 1;
139 		size_t end = _file_name.find_last_of('.');
140 		os << "Type " << font_nr << " - " << _file_name.substr(begin, end);
141 		_font_name = os.str();
142 	}
143 
144 	if (!el_file_exists(_file_name.c_str()))
145 	{
146 		LOG_ERROR("Unable to find font file '%s'", _file_name.c_str());
147 		_failed = true;
148 	}
149 
150 	size_t sep_pos = _file_name.find_last_of("/\\");
151 	_file_base_name = sep_pos == std::string::npos ? _file_name : _file_name.substr(sep_pos + 1);
152 	_fixed_width = (font_nr != 1 && font_nr != 2);
153 }
154 
155 #ifdef TTF
FontOption(const std::string & file_name)156 FontOption::FontOption(const std::string& file_name): _font_nr(std::numeric_limits<size_t>::max()),
157 	_file_name(file_name), _file_base_name(), _font_name(), _is_ttf(true), _fixed_width(), _failed(false)
158 {
159 	TTF_Font *font = open_font(file_name.c_str(), 40);
160 	if (!font)
161 	{
162 		LOG_ERROR("Failed to open TTF font file '%s'", file_name.c_str());
163 		_failed = true;
164 		return;
165 	}
166 
167 	// Quick check to see if the font is useful
168 	if (!TTF_GlyphIsProvided(font, 'A') || !TTF_GlyphIsProvided(font, '!'))
169 	{
170 		// Nope, can't render in this font
171 		TTF_CloseFont(font);
172 		LOG_ERROR("Unable to render text with TTF font file '%s'", file_name.c_str());
173 		_failed = true;
174 		return;
175 	}
176 
177 	size_t sep_pos = _file_name.find_last_of("/\\");
178 	_file_base_name = sep_pos == std::string::npos ? _file_name : _file_name.substr(sep_pos + 1);
179 
180 	std::string name = TTF_FontFaceFamilyName(font);
181 	std::string style = TTF_FontFaceStyleName(font);
182 
183 	_font_name = name + ' ' + style;
184 	_fixed_width = (TTF_FontFaceIsFixedWidth(font) != 0);
185 
186 	TTF_CloseFont(font);
187 }
188 #endif // TTF
189 
add_select_options(bool add_button) const190 void FontOption::add_select_options(bool add_button) const
191 {
192 	add_multi_option_with_id("ui_font",    _font_name.c_str(), _file_base_name.c_str(), add_button);
193 	add_multi_option_with_id("chat_font",  _font_name.c_str(), _file_base_name.c_str(), add_button);
194 	add_multi_option_with_id("name_font",  _font_name.c_str(), _file_base_name.c_str(), add_button);
195 	add_multi_option_with_id("book_font",  _font_name.c_str(), _file_base_name.c_str(), add_button);
196 	add_multi_option_with_id("note_font",  _font_name.c_str(), _file_base_name.c_str(), add_button);
197 	add_multi_option_with_id("rules_font", _font_name.c_str(), _file_base_name.c_str(), add_button);
198 	if (is_fixed_width())
199 	{
200 		add_multi_option_with_id("encyclopedia_font", _font_name.c_str(), _file_base_name.c_str(),
201 			add_button);
202 	}
203 }
204 
205 const std::array<float, 3> TextDrawOptions::default_foreground_color = { 1.0f, 1.0f, 1.0f };
206 const std::array<float, 3> TextDrawOptions::default_background_color = { 0.0f, 0.0f, 0.0f };
207 const std::array<float, 3> TextDrawOptions::default_selection_color = { 1.0f, 0.635f, 0.0f };
208 
TextDrawOptions()209 TextDrawOptions::TextDrawOptions(): _max_width(0), _max_lines(0), _zoom(1.0),
210 	_line_spacing(1.0), _alignment(LEFT), _vertical_alignment(TOP_LINE), _flags(0),
211 	_fg_color{{-1.0, -1.0, -1.0}}, _bg_color{{-1.0, -1.0, -1.0}},
212 	_sel_color(default_selection_color) {}
213 
use_background_color() const214 void TextDrawOptions::use_background_color() const
215 {
216 	if (has_background_color())
217 		glColor3fv(_bg_color.data());
218 	else
219 		glColor3fv(default_background_color.data());
220 }
221 
use_foreground_color() const222 void TextDrawOptions::use_foreground_color() const
223 {
224 	if (has_foreground_color())
225 		glColor3fv(_fg_color.data());
226 	else
227 		glColor3fv(default_foreground_color.data());
228 }
229 
use_selection_color() const230 void TextDrawOptions::use_selection_color() const
231 {
232 	glColor3fv(_sel_color.data());
233 }
234 
235 // Relative letter frequencies for English text, based on the counts in
236 //
237 // M.N. Jones and D.J.K. Mewhort,
238 // Behavior Research Methods, Instruments, & Computers 36, pp. 388-396 (2004)
239 //
240 // These are used to compute an "average" character width for a font. When
241 // using this, please note that:
242 // 1) This is based on English text, so these frequencies may not be applicable
243 //    to text in to other languages,
244 // 2) It is based on text from the New York Times, and so may not be
245 //    representative for text used in EL,
246 // 3) Only ASCII characters were counted (so accented characters don't contribute),
247 //    and a few ASCII characters are missing as well ('[', '\', ']', '^', '_',
248 //    and '`').
249 // 4) The occurance of the space character was guesstimated from the reported total
250 //    number of words in the corpus.
251 //
252 // When you need to be sure a text will fit in a certain space, use the maximum
253 // character width or use a monospaced font.
254 const std::array<int, Font::nr_glyphs> Font::letter_freqs = {
255 	1647,    0,   33,    0,    6,    0,    1,   24,    6,    6,    2,    0,  116,   30,
256 	 111,    1,   64,   54,   39,   22,   23,   44,   18,   14,   21,   33,    6,    4,
257 	   0,    0,    0,    1,    0,   33,   20,   27,   15,   16,   12,   11,   15,   26,
258 	   9,    5,   13,   31,   24,   12,   17,    1,   17,   36,   38,    7,    4,   13,
259 	   1,   11,    1,    0,    0,    0,    0,    0,    0,  619,  102,  231,  279,  911,
260 	 153,  142,  348,  533,    8,   54,  300,  173,  534,  556,  148,    6,  487,  492,
261 	 648,  190,   77,  119,   15,  125,    8,    0,    0,    0,    0
262 };
263 const ustring Font::ellipsis = reinterpret_cast<const unsigned char*>("...");
264 
Font(Font && font)265 Font::Font(Font&& font): _font_name(font.font_name()), _file_name(font.file_name()),
266 	_flags(font._flags), _texture_width(font._texture_width),
267 	_texture_height(font._texture_height), _metrics(font._metrics), _block_width(font._block_width),
268 	_line_height(font._line_height),
269 	_vertical_advance(font._vertical_advance), _font_top_offset(font._font_top_offset),
270 	_digit_center_offset(font._digit_center_offset), _password_center_offset(font._password_center_offset),
271 	_max_advance(font._max_advance), _max_digit_advance(font._max_digit_advance),
272 	_max_name_advance(font._max_name_advance), _avg_advance(font._avg_advance), _spacing(font._spacing),
273 	_scale_x(font._scale_x), _scale_y(font._scale_y),
274 #ifdef TTF
275 	_point_size(font._point_size), _outline(font._outline),
276 #endif
277 	_texture_id(font._texture_id)
278 {
279 	font._flags &= ~HAS_TEXTURE;
280 }
281 
Font(const FontOption & option)282 Font::Font(const FontOption& option): _font_name(option.font_name()),
283 	_file_name(option.file_name()), _flags(0), _texture_width(256), _texture_height(256), _metrics(),
284 	_block_width(font_block_width), _line_height(default_vertical_advance + 1),
285 	_vertical_advance(default_vertical_advance), _font_top_offset(0),
286 	_digit_center_offset(option.font_number() == 2 ? 10 : 9), _password_center_offset(0),
287 	_max_advance(12), _max_digit_advance(12), _max_name_advance(12), _avg_advance(12), _spacing(0),
288 	_scale_x(11.0 / 12), _scale_y(1.0),
289 #ifdef TTF
290 	_point_size(0), _outline(0),
291 #endif
292 	_texture_id()
293 {
294 	static const std::array<int, nr_glyphs> top_1 = {
295 		15,  2,  2,  2,  1,  2,  3,  2,  2,  2,  2,  6, 11,  8,
296 		11,  0,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  6,  6,
297 		 6,  7,  6,  2,  3,  2,  2,  2,  2,  2,  2,  2,  2,  2,
298 		 2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
299 		 2,  2,  2,  1,  0,  1, -1,  4,  1,  5,  2,  5,  2,  5,
300 		 2,  5,  2,  1,  1,  2,  2,  5,  5,  5,  5,  5,  5,  5,
301 		 3,  5,  5,  5,  5,  5,  5,  2,  2,  2,  8,  2,  1,  1,
302 		 1,  6,  1,  2,  1,  2,  1,  1,  2,  2,  2, -1, -1, -1,
303 		 2,  5,  5,  1,  2,  2, -1,  2,  0,  1,  0,  0,  1,  0,
304 		 1,  0,  1,  0,  1,  1
305 	};
306 	static const std::array<int, nr_glyphs> bottom_1 = {
307 		15, 15,  9, 17, 18, 15, 15,  9, 17, 17, 12, 15, 18, 11,
308 		15, 17, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 17,
309 		15, 13, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
310 		15, 15, 15, 15, 15, 15, 15, 18, 15, 15, 15, 15, 15, 15,
311 		15, 15, 15, 17, 17, 17,  6, 17,  6, 15, 15, 15, 15, 15,
312 		15, 18, 15, 15, 18, 15, 15, 15, 15, 15, 18, 18, 15, 15,
313 		15, 15, 15, 15, 15, 18, 15, 19, 19, 19, 12, 15, 15, 15,
314 		15, 19, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16,
315 		15, 15, 15, 15, 15, 15, 16, 15, 15, 15, 16, 16, 15, 16,
316 		15, 16, 15, 16, 15, 15
317 	};
318 	static const std::array<int, 7> asterisk_centers = { 7, 7, 7, 7, 6, 5, 6 };
319 
320 	size_t font_nr = option.font_number();
321 	std::array<int, nr_glyphs> char_widths;
322 	if (font_nr == 1)
323 	{
324 		char_widths = { {
325 			 4,  2,  7, 11,  8, 12, 12,  2,  7,  7,  9, 10,  3,  8,
326 			 2, 10, 10, 10,  8,  8, 10,  7,  9,  9,  9,  9,  3,  3,
327 			10, 10, 10,  9, 12, 12,  9, 10, 10,  9,  9, 10,  9,  8,
328 			 7, 11,  8, 11, 10, 11,  9, 11, 11,  9, 10,  9, 12, 12,
329 			12, 12, 10,  6, 10,  6, 10, 12,  3, 11,  9,  9,  9,  9,
330 			 8,  9,  9,  4,  6, 10,  4, 11,  9, 10,  9,  9,  8,  8,
331 			 8,  9, 10, 12, 10, 10,  9,  8,  2,  8, 10,  8, 12, 12,
332 			12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
333 			12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
334 			12, 12, 12, 12, 12, 12
335 		} };
336 		_spacing = 4;
337 	}
338 	else if (font_nr == 2)
339 	{
340 		char_widths = { {
341 			 8,  8,  8, 10,  8, 10, 10,  8,  8,  8,  8, 10,  8,  8,
342 			 8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
343 			10, 10, 10,  8, 12, 10, 10, 10, 10, 10, 10, 10, 10, 10,
344 			10, 10, 10, 10, 10, 10, 10, 10,  8, 10, 10, 10, 10, 10,
345 			10, 10, 10, 10, 10, 10, 10,  8,  8,  8,  8,  8,  8,  8,
346 			10,  8,  8,  8,  8,  8,  8, 10,  8,  8,  8,  8,  8,  8,
347 			 8,  8,  8, 10,  8,  8,  8, 10,  8, 10, 10,  8, 10,  8,
348 			 8,  8, 10, 10, 10,  8, 10, 10,  8,  8,  8, 12, 12, 12,
349 			10, 10, 12, 10, 12, 12
350 		} };
351 		_spacing = 2;
352 	}
353 	else
354 	{
355 		std::fill(char_widths.begin(), char_widths.end(), 12);
356 		_flags |= Flags::FIXED_WIDTH;
357 	}
358 
359 	int digit_start = get_position('0');
360 	int digit_end = get_position('9') + 1;
361 	int lower_start = get_position('a');
362 	int lower_end = get_position('z') + 1;
363 	int upper_start = get_position('A');
364 	int upper_end = get_position('Z') + 1;
365 	int underscore_pos = get_position('_');
366 
367 	for (size_t pos = 0; pos < nr_glyphs; ++pos)
368 	{
369 		int row = pos / font_chars_per_line;
370 		int col = pos % font_chars_per_line;
371 
372 		int cw = char_widths[pos] + _spacing;
373 		int skip = (12 - cw) / 2;
374 
375 		_metrics[pos].width = char_widths[pos];
376 		_metrics[pos].advance = char_widths[pos];
377 		_metrics[pos].x_off = 0;
378 		_metrics[pos].top = font_nr < 2 ? top_1[pos] : 0;
379 		_metrics[pos].bottom = font_nr < 2 ? bottom_1[pos] : _line_height;
380 		_metrics[pos].u_start = float(col * font_block_width + skip) / 256;
381 		_metrics[pos].v_start = float(row * font_block_height) / 256;
382 		_metrics[pos].u_end = float((col+1) * font_block_width - 7 - skip) / 256;
383 		_metrics[pos].v_end = float((row+1) * font_block_height - 2) / 256;
384 	}
385 	_avg_advance = calc_average_advance();
386 	_password_center_offset = asterisk_centers[font_nr];
387 	_max_digit_advance = *std::max_element(char_widths.begin() + digit_start, char_widths.begin() + digit_end);
388 	_max_name_advance = std::max({_max_digit_advance,
389 		char_widths[underscore_pos],
390 		*std::max_element(char_widths.begin() + upper_start, char_widths.begin() + upper_end),
391 		*std::max_element(char_widths.begin() + lower_start, char_widths.begin() + lower_end)
392 	});
393 }
394 
395 #ifdef TTF
Font(const FontOption & option,int height,bool outline)396 Font::Font(const FontOption& option, int height, bool outline): _font_name(option.font_name()),
397 	_file_name(option.file_name()), _flags(IS_TTF), _texture_width(0), _texture_height(0),
398 	_metrics(), _block_width(0), _line_height(0), _vertical_advance(0), _font_top_offset(0),
399 	_digit_center_offset(0), _password_center_offset(0), _max_advance(0), _max_digit_advance(0),
400 	_max_name_advance(0), _avg_advance(0), _spacing(0), _scale_x(1.0), _scale_y(1.0), _point_size(),
401 	_outline(std::round(float(height) / (font_block_height - 2))), _texture_id()
402 {
403 	_point_size = find_point_size(height);
404 	if (option.is_fixed_width())
405 		_flags |= Flags::FIXED_WIDTH;
406 	if (outline)
407 		_flags |= Flags::HAS_OUTLINE;
408 }
409 #endif
410 
~Font()411 Font::~Font()
412 {
413 #ifdef TTF
414 	if (is_ttf() && has_texture())
415 		glDeleteTextures(1, &_texture_id.gl_id);
416 #endif
417 }
418 
get_position(unsigned char c)419 int Font::get_position(unsigned char c)
420 {
421 	static const int pos_table[256] = {
422 		-1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
423 		-1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
424 		 0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,  13,  14,  15,
425 		16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,  30,  31,
426 		32,  33,  34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,
427 		48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63,
428 		64,  65,  66,  67,  68,  69,  70,  71,  72,  73,  74,  75,  76,  77,  78,  79,
429 		80,  81,  82,  83,  84,  85,  86,  87,  88,  89,  90,  91,  92,  93,  94,  -1,
430 		-1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
431 		-1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
432 		-1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
433 		-1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
434 		-1, 122,  -1,  -1, 109, 118, 116,  -1,  -1, 123,  -1,  -1,  -1, 125,  -1,  -1,
435 		-1, 120,  -1, 127,  -1,  -1, 110,  -1, 117,  -1, 129,  -1, 111,  -1,  -1, 112,
436 		98, 121,  97,  -1, 106, 115, 113,  99, 102,  96, 100, 101, 124, 124,  -1, 103,
437 		-1, 119, 126, 126, 104,  -1, 107,  -1, 114, 105, 128,  -1, 108,  -1,  -1,  -1
438 	};
439 
440 	return pos_table[c];
441 }
442 
width_pos(int pos,float zoom) const443 int Font::width_pos(int pos, float zoom) const
444 {
445 	if (pos < 0)
446 		return 0;
447 	return std::round((_metrics[pos].width + _spacing) * _scale_x * zoom);
448 }
449 
advance_spacing_pos(int pos,float zoom) const450 int Font::advance_spacing_pos(int pos, float zoom) const
451 {
452 	if (pos < 0)
453 		return 0;
454 	// return width of character + spacing between chars (supports variable width fonts)
455 	return std::round((_metrics[pos].advance + _spacing) * _scale_x * zoom);
456 }
457 
max_width_spacing(float zoom) const458 int Font::max_width_spacing(float zoom) const
459 {
460 	return std::round((_max_advance + _spacing) * _scale_x * zoom);
461 }
462 
average_width_spacing(float zoom) const463 int Font::average_width_spacing(float zoom) const
464 {
465 	return std::round((_avg_advance + _spacing) * _scale_x * zoom);
466 }
467 
max_digit_width_spacing(float zoom) const468 int Font::max_digit_width_spacing(float zoom) const
469 {
470 	return std::round((_max_digit_advance + _spacing) * _scale_x * zoom);
471 }
472 
max_name_width_spacing(float zoom) const473 int Font::max_name_width_spacing(float zoom) const
474 {
475 	return std::round((_max_name_advance + _spacing) * _scale_x * zoom);
476 }
477 
line_width(const unsigned char * str,size_t len,float zoom) const478 int Font::line_width(const unsigned char* str, size_t len, float zoom) const
479 {
480 	int cur_width = 0;
481 	int last_pos = -1;
482 	for (size_t i = 0; i < len; ++i)
483 	{
484 		int pos = get_position(str[i]);
485 		if (pos >= 0)
486 		{
487 			cur_width += advance_spacing_pos(pos, zoom);
488 			last_pos = pos;
489 		}
490 	}
491 	cur_width -= advance_spacing_pos(last_pos, zoom) - width_pos(last_pos, zoom);
492 
493 	return cur_width;
494 }
495 
line_width_spacing(const unsigned char * str,size_t len,float zoom) const496 int Font::line_width_spacing(const unsigned char* str, size_t len, float zoom) const
497 {
498 	int cur_width = 0;
499 	for (size_t i = 0; i < len; ++i)
500 		cur_width += advance_spacing(str[i], zoom);
501 
502 	return cur_width;
503 }
504 
height(float zoom) const505 int Font::height(float zoom) const
506 {
507 	return std::round(_line_height * _scale_y * zoom);
508 }
509 
vertical_advance(float zoom,float line_spacing) const510 int Font::vertical_advance(float zoom, float line_spacing) const
511 {
512 	return std::round(_vertical_advance * _scale_y * zoom * line_spacing);
513 }
514 
max_nr_lines(int max_height,float zoom,float line_spacing) const515 int Font::max_nr_lines(int max_height, float zoom, float line_spacing) const
516 {
517 	int line_height = height(zoom);
518 	if (max_height < line_height)
519 		return 0;
520 
521 	int line_skip = vertical_advance(zoom, line_spacing);
522 	return 1 + (max_height - line_height) / line_skip;
523 }
524 
text_height(int nr_lines,float zoom,float line_spacing)525 int Font::text_height(int nr_lines, float zoom, float line_spacing)
526 {
527 	if (nr_lines <= 0)
528 		return 0;
529 	return height(zoom) + (nr_lines-1) * vertical_advance(zoom, line_spacing);
530 }
531 
dimensions(const unsigned char * str,size_t len,float zoom,float line_spacing) const532 std::pair<int, int> Font::dimensions(const unsigned char* str, size_t len, float zoom,
533 	float line_spacing) const
534 {
535 	if (len == 0)
536 		return std::make_pair(0, 0);
537 
538 	size_t end = memcspn(str, len, reinterpret_cast<const unsigned char*>("\r\n"), 2);
539 	int w = line_width(str, end, zoom);
540 	int h = height(zoom);
541 	size_t off = end + 1;
542 
543 	int line_skip = vertical_advance(zoom, line_spacing);
544 	while (off < len)
545 	{
546 		end = memcspn(str + off, len - off, reinterpret_cast<const unsigned char*>("\r\n"), 2);
547 		w = std::max(w, line_width(str + off, end, zoom));
548 		h += line_skip;
549 		off += end + 1;
550 	}
551 	return std::make_pair(w, h);
552 }
553 
top_bottom_unscaled(const unsigned char * text,size_t len)554 std::pair<int, int> Font::top_bottom_unscaled(const unsigned char* text, size_t len)
555 {
556 	int top = _line_height, bottom = 0;
557 	for (size_t i = 0; i < len; ++i)
558 	{
559 		int pos = get_position(text[i]);
560 		if (pos >= 0)
561 		{
562 			top = std::min(top, _metrics[pos].top);
563 			bottom = std::max(bottom, _metrics[pos].bottom);
564 		}
565 	}
566 
567 	return std::make_pair(top, bottom);
568 }
569 
top_bottom(const unsigned char * text,size_t len,float zoom)570 std::pair<int, int> Font::top_bottom(const unsigned char* text, size_t len, float zoom)
571 {
572 	int top, bottom;
573 	std::tie(top, bottom) = top_bottom_unscaled(text, len);
574 	top = std::round(_scale_y * zoom * top);
575 	bottom = std::round(_scale_y * zoom * bottom);
576 
577 	return std::make_pair(top, bottom);
578 }
579 
center_offset(const unsigned char * text,size_t len,float zoom)580 int Font::center_offset(const unsigned char* text, size_t len, float zoom)
581 {
582 	int top, bottom;
583 	std::tie(top, bottom) = top_bottom_unscaled(text, len);
584 	if (top >= bottom)
585 		return 0;
586 
587 	return std::round(_scale_y * zoom * 0.5 * (bottom + top - _line_height));
588 }
589 
590 // Rules for wrapping text:
591 // 1) Soft line breaks take up no space, the cursor should never be on a soft line break.
592 // 2) The cursor can be on a hard line break character, though. Inserting at that position will
593 //    insert a new character at that position. So a hard break takes up the width of a cursor,
594 //    even though it is invisible.
595 // 3) When possible, lines are broken after the last space. If no space is found on the current
596 //    line, the word is broken after the last character that fits on the line. Since the cursor
597 //    should never be on a soft break, we can fill the entire line.
598 // 4) We must be able to place the cursor at the end of the text. Therefore, if the last line cannot
599 //    fit the cursor, an extra soft break is inserted.
reset_soft_breaks(const unsigned char * text,size_t text_len,const TextDrawOptions & options,ssize_t cursor,int * max_line_width)600 std::tuple<ustring, int, int> Font::reset_soft_breaks(const unsigned char *text,
601 	size_t text_len, const TextDrawOptions& options, ssize_t cursor, int *max_line_width)
602 {
603 	int block_width = std::ceil(_block_width * _scale_x * options.zoom());
604 	int cursor_width = advance_spacing('_', options.zoom());
605 
606 	if (!text || options.max_width() < block_width)
607 		return std::make_tuple(ustring(), 0, 0);
608 
609 	std::basic_string<unsigned char> wrapped_text;
610 	size_t start = 0, end, end_last_space = 0, last_start = 0;
611 	int nr_lines = 0;
612 	int diff_cursor = 0;
613 	while (start < text_len)
614 	{
615 		int cur_width = 0;
616 		for (end = start; end < text_len; ++end)
617 		{
618 			unsigned char c = text[end];
619 			if (c == '\r')
620 			{
621 				continue;
622 			}
623 
624 			// We need to be able to place a cursor at this position, so use the maximum of the
625 			// cursor width and character width to determine if the character fits.
626 			int chr_width = advance_spacing(c, options.zoom());
627 			if (cur_width + block_width <= options.max_width()
628 				|| cur_width + std::max(chr_width, cursor_width) <= options.max_width())
629 			{
630 				cur_width += chr_width;
631 				if (c == ' ')
632 				{
633 					end_last_space = end + 1;
634 				}
635 				else if (c == '\n')
636 				{
637 					++end;
638 					break;
639 				}
640 			}
641 			else
642 			{
643 				// Character won't fit. Split line after the last space.
644 				// If not found, break in the middle of the word.
645 				if (end_last_space > start)
646 					end = end_last_space;
647 				break;
648 			}
649 		}
650 
651 		for (size_t i = start; i < end; ++i)
652 		{
653 			if (text[i] == '\r')
654 			{
655 				if (ssize_t(i) < cursor)
656 					--diff_cursor;
657 			}
658 			else
659 			{
660 				wrapped_text.push_back(text[i]);
661 			}
662 		}
663 
664 		++nr_lines;
665 		if (end < text_len && (wrapped_text.empty() || wrapped_text.back() != '\n'))
666 		{
667 			wrapped_text.push_back('\r');
668 			if (ssize_t(end) <= cursor)
669 				++diff_cursor;
670 		}
671 
672 		if (max_line_width)
673 		{
674 			cur_width = line_width(text + start, end - start, options.zoom());
675 			*max_line_width = std::max(*max_line_width, cur_width);
676 		}
677 
678 		last_start = start;
679 		start = end;
680 	}
681 
682 	// If the message ends on a newline, an extra empty line is printed, but it has not been counted yet.
683 	if (!wrapped_text.empty() && wrapped_text.back() == '\n')
684 		++nr_lines;
685 
686 	// If a cursor will not fit behind the last line, add an extra line break.
687 	int last_line_width = line_width(text + last_start, text_len - last_start, options.zoom());
688 	if (last_line_width + cursor_width > options.max_width())
689 	{
690 		wrapped_text.push_back('\r');
691 		++nr_lines;
692 	}
693 
694 	return std::make_tuple(wrapped_text, nr_lines, diff_cursor);
695 }
696 
load_texture()697 bool Font::load_texture()
698 {
699 #ifdef TTF
700 	if (is_ttf())
701 	{
702 		return build_texture_atlas();
703 	}
704 	else
705 	{
706 		_texture_id.cache_id = ::load_texture_cached(_file_name.c_str(), tt_font);
707 		_flags |= Flags::HAS_TEXTURE;
708 		return true;
709 	}
710 #else // TTF
711 	_texture_id = ::load_texture_cached(_file_name.c_str(), tt_font);
712 	_flags |= Flags::HAS_TEXTURE;
713 	return true;
714 #endif // TTF
715 }
716 
bind_texture() const717 void Font::bind_texture() const
718 {
719 #ifdef TTF
720 	if (is_ttf())
721 	{
722 		::bind_texture_id(_texture_id.gl_id);
723 	}
724 	else
725 	{
726 		::bind_texture(_texture_id.cache_id);
727 	}
728 #else // TTF
729 	::bind_texture(_texture_id);
730 #endif // TTF
731 }
732 
get_texture_coordinates(int pos,float & u_start,float & u_end,float & v_start,float & v_end) const733 void Font::get_texture_coordinates(int pos,
734 	float &u_start, float &u_end, float &v_start, float &v_end) const
735 {
736 	u_start = _metrics[pos].u_start;
737 	v_start = _metrics[pos].v_start;
738 	u_end   = _metrics[pos].u_end;
739 	v_end   = _metrics[pos].v_end;
740 }
741 
set_color(int color)742 void Font::set_color(int color)
743 {
744 	float r = static_cast<float>(colors_list[color].r1) / 255;
745 	float g = static_cast<float>(colors_list[color].g1) / 255;
746 	float b = static_cast<float>(colors_list[color].b1) / 255;
747 	glColor3f(r, g, b);
748 }
749 
draw_char(unsigned char c,int x,int y,float zoom,bool ignore_color) const750 int Font::draw_char(unsigned char c, int x, int y, float zoom, bool ignore_color) const
751 {
752 	if (is_color(c))
753 	{
754 		if (!ignore_color)
755 			set_color(from_color_char(c));
756 		return 0;
757 	}
758 
759 	int pos = get_position(c);
760 	if (pos < 0) // watch for illegal/non-display characters
761 	{
762 		return 0;
763 	}
764 
765 	// There are two widths here: char_width and advance. The first is the width
766 	// of the character as drawn on the screen, the second the number of pixels
767 	// the pen should advance for drawing the next character. Interestingly,
768 	// char_width can be larger than advance, epsecially for bold fonts. For
769 	// size calculations, the only relevant quantity is advance, though.
770 	int char_width = width_pos(pos, zoom);
771 	int advance = advance_spacing_pos(pos, zoom);
772 	int char_height = height(zoom);
773 
774 	float u_start, u_end, v_start, v_end;
775 	get_texture_coordinates(pos, u_start, u_end, v_start, v_end);
776 
777 	// Adjust for character offset in the font
778 	x += _metrics[pos].x_off;
779 
780 	// and place the text from the graphics on the map
781 	glTexCoord2f(u_start, v_start); glVertex3i(x, y - _outline, 0);
782 	glTexCoord2f(u_start, v_end);   glVertex3i(x, y + char_height + _outline, 0);
783 	glTexCoord2f(u_end,   v_end);   glVertex3i(x + char_width, y + char_height + _outline, 0);
784 	glTexCoord2f(u_end,   v_start); glVertex3i(x + char_width, y - _outline, 0);
785 
786 	return advance;
787 }
788 
draw_help_background(int x,int y,int width,int height) const789 void Font::draw_help_background(int x, int y, int width, int height) const
790 {
791 	glDisable(GL_TEXTURE_2D);
792 	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
793 	glEnable(GL_BLEND);
794 
795 	glColor4f(0.0f, 0.0f, 0.0f, 0.5f);
796 	glBegin(GL_QUADS);
797 	glVertex3i(x - 1,     y + height, 0);
798 	glVertex3i(x - 1,     y,          0);
799 	glVertex3i(x + width, y,          0);
800 	glVertex3i(x + width, y + height, 0);
801 	glEnd();
802 
803 	glDisable(GL_BLEND);
804 	glEnable(GL_TEXTURE_2D);
805 }
806 
draw_line(const unsigned char * text,size_t len,int x,int y,const TextDrawOptions & options,size_t sel_begin,size_t sel_end) const807 void Font::draw_line(const unsigned char* text, size_t len, int x, int y,
808 	const TextDrawOptions &options, size_t sel_begin, size_t sel_end) const
809 {
810 	if (options.shadow())
811 	{
812 		TextDrawOptions new_options = options;
813 		new_options.set_shadow(false);
814 
815 		int delta = std::round(options.zoom());
816 		if (delta > 0)
817 		{
818 			new_options.set_ignore_color();
819 
820 			new_options.use_background_color();
821 			draw_line(text, len, x-delta, y-delta, new_options, sel_begin, sel_end);
822 			draw_line(text, len, x-delta, y,       new_options, sel_begin, sel_end);
823 			draw_line(text, len, x-delta, y+delta, new_options, sel_begin, sel_end);
824 			draw_line(text, len, x,       y+delta, new_options, sel_begin, sel_end);
825 			draw_line(text, len, x+delta, y+delta, new_options, sel_begin, sel_end);
826 			draw_line(text, len, x+delta, y,       new_options, sel_begin, sel_end);
827 			draw_line(text, len, x+delta, y-delta, new_options, sel_begin, sel_end);
828 			draw_line(text, len, x,       y-delta, new_options, sel_begin, sel_end);
829 		}
830 
831 		new_options.set_ignore_color(false);
832 		draw_line(text, len, x, y, new_options, sel_begin, sel_end);
833 	}
834 	else if (sel_end <= sel_begin || sel_begin >= len || options.ignore_color())
835 	{
836 		if (!options.ignore_color() && options.has_foreground_color())
837 			options.use_foreground_color();
838 
839 		int cur_x = x;
840 		for (size_t i = 0; i < len; ++i)
841 		{
842 			cur_x += draw_char(text[i], cur_x, y, options.zoom(), options.ignore_color());
843 		}
844 	}
845 	else
846 	{
847 		int cur_x = x;
848 		int last_color_char = 0;
849 		if (sel_begin > 0)
850 		{
851 			if (options.has_foreground_color())
852 				options.use_foreground_color();
853 
854 			for (size_t i = 0; i < sel_begin; ++i)
855 			{
856 				if (is_color(text[i]))
857 					last_color_char = text[i];
858 				cur_x += draw_char(text[i], cur_x, y, options.zoom(), false);
859 			}
860 		}
861 
862 		options.use_selection_color();
863 
864 		for (size_t i = sel_begin; i < std::min(sel_end, len); ++i)
865 		{
866 			if (is_color(text[i]))
867 				last_color_char = text[i];
868 			cur_x += draw_char(text[i], cur_x, y, options.zoom(), false);
869 		}
870 
871 		if (len > sel_end)
872 		{
873 			if (last_color_char)
874 				set_color(from_color_char(last_color_char));
875 			else
876 				options.use_foreground_color();
877 
878 			for (size_t i = sel_end; i < len; ++i)
879 			{
880 				cur_x += draw_char(text[i], cur_x, y, options.zoom(), false);
881 			}
882 		}
883 	}
884 }
885 
clip_line(const unsigned char * text,size_t len,const TextDrawOptions & options,unsigned char & before_color,unsigned char & after_color,int & width) const886 std::pair<size_t, size_t> Font::clip_line(const unsigned char *text, size_t len,
887 	const TextDrawOptions &options, unsigned char &before_color, unsigned char &after_color,
888 	int &width) const
889 {
890 	int max_width = options.max_width() > 0 ? options.max_width() : window_width;
891 
892 	before_color = 0;
893 	after_color = 0;
894 	width = line_width_spacing(text, len, options.zoom());
895 	if (width <= max_width)
896 		// Entire string fits
897 		return std::make_pair(0, len);
898 
899 	int trunc_width = max_width;
900 	if (options.ellipsis())
901 	{
902 		int ellipsis_width = line_width_spacing(ellipsis, options.zoom());
903 		if (options.alignment() == TextDrawOptions::Alignment::CENTER)
904 			ellipsis_width *= 2;
905 		trunc_width -= ellipsis_width;
906 	}
907 
908 	size_t start = 0, end = len;
909 	switch (options.alignment())
910 	{
911 		case TextDrawOptions::Alignment::LEFT:
912 		{
913 			while (end > 0 && width > trunc_width)
914 			{
915 				unsigned char ch = text[--end];
916 				if (is_color(ch) && !after_color)
917 					after_color = ch;
918 				else
919 					width -= advance_spacing(ch, options.zoom());
920 			}
921 			break;
922 		}
923 		case TextDrawOptions::Alignment::RIGHT:
924 		{
925 			while (start < len && width > trunc_width)
926 			{
927 				unsigned char ch = text[start++];
928 				if (is_color(ch))
929 					before_color = ch;
930 				else
931 					width -= advance_spacing(ch, options.zoom());
932 			}
933 			break;
934 		}
935 		case TextDrawOptions::Alignment::CENTER:
936 		{
937 			int d_left = 0, d_right = 0;
938 			while (start < end && width - d_left - d_right > trunc_width)
939 			{
940 				if (d_left < d_right)
941 				{
942 					unsigned char ch = text[start++];
943 					if (is_color(ch))
944 						before_color = ch;
945 					else
946 						d_left += advance_spacing(ch, options.zoom());
947 				}
948 				else
949 				{
950 					unsigned char ch = text[--end];
951 					if (is_color(ch) && !after_color)
952 						after_color = ch;
953 					else
954 						d_right += advance_spacing(ch, options.zoom());
955 				}
956 			}
957 			width -= d_left + d_right;
958 			break;
959 		}
960 	}
961 
962 	width = max_width;
963 	return std::make_pair(start, end-start);
964 }
965 
draw(const unsigned char * text,size_t len,int x,int y,const TextDrawOptions & options,size_t sel_begin,size_t sel_end) const966 void Font::draw(const unsigned char* text, size_t len, int x, int y,
967 	const TextDrawOptions &options, size_t sel_begin, size_t sel_end) const
968 {
969 	if (options.shrink_to_fit() && options.max_width() > 0)
970 	{
971 		int width = dimensions(text, len, options.zoom()).first;
972 		if (width > options.max_width())
973 		{
974 			TextDrawOptions new_options = TextDrawOptions(options).set_max_width(0)
975 				.set_zoom(options.zoom() * float(options.max_width()) / width)
976 				.set_shrink_to_fit(false);
977 			draw(text, len, x, y, new_options, sel_begin, sel_end);
978 			return;
979 		}
980 	}
981 
982 	int tot_width = 0, tot_height = 0;
983 	if (options.is_help()
984 		|| options.vertical_alignment() == TextDrawOptions::VerticalAlignment::BOTTOM_LINE
985 		|| options.vertical_alignment() == TextDrawOptions::VerticalAlignment::CENTER_LINE)
986 	{
987 		std::tie(tot_width, tot_height) = dimensions(text, len, options.zoom(), options.line_spacing());
988 	}
989 
990 	switch (options.vertical_alignment())
991 	{
992 		case TextDrawOptions::VerticalAlignment::TOP_LINE:
993 			break;
994 		case TextDrawOptions::VerticalAlignment::TOP_FONT:
995 			y -= std::round(options.zoom() * _scale_y * _font_top_offset);
996 			break;
997 		case TextDrawOptions::VerticalAlignment::BOTTOM_LINE:
998 			y -= tot_height;
999 			break;
1000 		case TextDrawOptions::VerticalAlignment::CENTER_LINE:
1001 			y -= tot_height / 2;
1002 			break;
1003 		case CENTER_DIGITS:
1004 			y -= std::round(options.zoom() * _scale_y * _digit_center_offset);
1005 			break;
1006 		case CENTER_PASSWORD:
1007 			y -= std::round(options.zoom() * _scale_y * _password_center_offset);
1008 			break;
1009 	}
1010 
1011 	if (options.is_help())
1012 	{
1013 		int x_left = 0;
1014 		switch (options.alignment())
1015 		{
1016 			case TextDrawOptions::Alignment::LEFT:   x_left = x; break;
1017 			case TextDrawOptions::Alignment::CENTER: x_left = x - tot_width / 2; break;
1018 			case TextDrawOptions::Alignment::RIGHT:  x_left = x - tot_width; break;
1019 		}
1020 		draw_help_background(x_left, y, tot_width, tot_height);
1021 	}
1022 
1023 #ifdef OPENGL_TRACE
1024 CHECK_GL_ERRORS();
1025 #endif //OPENGL_TRACE
1026 	glEnable(GL_ALPHA_TEST); // enable alpha filtering, so we have some alpha key
1027 	glAlphaFunc(GL_GREATER, 0.1f);
1028 	if (is_ttf())
1029 	{
1030 		// Only enable alpha blending for TTF fonts. The old style fonts have an alpha channel,
1031 		// but this has never been used before except for alpha filtering. Instead, the
1032 		// semi-transparent border has always been drawn as solid black.
1033 		glEnable(GL_BLEND);
1034 		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1035 	}
1036 	bind_texture();
1037 	glBegin(GL_QUADS);
1038 
1039 	int line_skip = vertical_advance(options.zoom(), options.line_spacing());
1040 	int nr_lines_drawn = 0;
1041 	size_t start = 0;
1042 	while (start < len)
1043 	{
1044 		size_t line_len = memcspn(text + start, len-start,
1045 			reinterpret_cast<const unsigned char*>("\r\n"), 2);
1046 		size_t clipped_off, clipped_line_len;
1047 		int line_width;
1048 		unsigned char before_color, after_color;
1049 		std::tie(clipped_off, clipped_line_len) = clip_line(text + start, line_len,
1050 			options, before_color, after_color, line_width);
1051 		bool draw_ellipsis = options.ellipsis() && clipped_line_len < line_len;
1052 		int ellipsis_width = draw_ellipsis ? line_width_spacing(ellipsis, options.zoom()) : 0;
1053 
1054 		int x_left = 0;
1055 		switch (options.alignment())
1056 		{
1057 			case TextDrawOptions::Alignment::LEFT:   x_left = x; break;
1058 			case TextDrawOptions::Alignment::CENTER: x_left = x - line_width / 2; break;
1059 			case TextDrawOptions::Alignment::RIGHT:  x_left = x - line_width; break;
1060 		}
1061 
1062 		if (before_color)
1063 			set_color(from_color_char(before_color));
1064 
1065 		int x_draw = x_left;
1066 		if (draw_ellipsis && options.alignment() != TextDrawOptions::Alignment::LEFT)
1067 		{
1068 			draw_line(ellipsis.data(), ellipsis.length(), x_draw, y, options);
1069 			x_draw += ellipsis_width;
1070 		}
1071 		draw_line(text + start + clipped_off, clipped_line_len, x_draw, y, options,
1072 			sel_begin < start ? 0 : sel_begin - start,
1073 			sel_end < start ? 0 : sel_end - start);
1074 		if (draw_ellipsis && options.alignment() != TextDrawOptions::Alignment::RIGHT)
1075 		{
1076 			x_draw = x_left + line_width - ellipsis_width;
1077 			draw_line(ellipsis.data(), ellipsis.length(), x_draw, y, options);
1078 		}
1079 
1080 		if (after_color)
1081 			set_color(from_color_char(after_color));
1082 
1083 		++nr_lines_drawn;
1084 		if (options.max_lines() > 0 && nr_lines_drawn >= options.max_lines())
1085 			break;
1086 
1087 		y += line_skip;
1088 		start += line_len + 1;
1089 	}
1090 
1091 	glEnd();
1092 	if (is_ttf())
1093 	{
1094 		glDisable(GL_BLEND);
1095 	}
1096 	glDisable(GL_ALPHA_TEST);
1097 #ifdef OPENGL_TRACE
1098 CHECK_GL_ERRORS();
1099 #endif //OPENGL_TRACE
1100 }
1101 
draw_messages(const text_message * msgs,size_t msgs_size,int x,int y,Uint8 filter,size_t msg_start,size_t offset_start,const TextDrawOptions & options,ssize_t cursor,select_info * select) const1102 void Font::draw_messages(const text_message *msgs, size_t msgs_size, int x, int y,
1103 	Uint8 filter, size_t msg_start, size_t offset_start,
1104 	const TextDrawOptions &options, ssize_t cursor, select_info* select) const
1105 {
1106 	int block_width = std::ceil(_block_width * _scale_x * options.zoom());
1107 	int line_skip = vertical_advance(options.zoom(), options.line_spacing());
1108 	int cursor_width = advance_spacing('_', options.zoom());
1109 
1110 	if (options.max_width() < block_width || options.max_lines() < 1)
1111 		// no point in trying
1112 		return;
1113 
1114 	size_t imsg = msg_start;
1115 	size_t ichar = offset_start;
1116 	if (filter_messages(msgs, msgs_size, filter, msg_start, imsg, ichar))
1117 		// nothing to draw
1118 		return;
1119 
1120 	unsigned char ch = msgs[imsg].data[ichar];
1121 	unsigned char last_color_char = 0;
1122 	if (!is_color(ch))
1123 	{
1124 		ssize_t i;
1125 		// search backwards for the last color
1126 		for (i = ssize_t(ichar) - 1; i >= 0; --i)
1127 		{
1128 			ch = msgs[imsg].data[i];
1129 			if (is_color(ch))
1130 			{
1131 				set_color(from_color_char(ch));
1132 				last_color_char = ch;
1133 				break;
1134 			}
1135 		}
1136 
1137 		if (i < 0 && msgs[imsg].r >= 0)
1138 			// no color character found, try the message color
1139 			glColor3f(msgs[imsg].r, msgs[imsg].g, msgs[imsg].b);
1140 	}
1141 
1142  	glEnable(GL_ALPHA_TEST);	// enable alpha filtering, so we have some alpha key
1143 	glAlphaFunc(GL_GREATER, 0.1f);
1144 	if (is_ttf())
1145 	{
1146 		// Only enable alpha blending for TTF fonts. The old style fonts have an alpha channel,
1147 		// but this has never been used before. Instead, the semi-transparent border has always
1148 		// been drawn as solid black.
1149 		glEnable(GL_BLEND);
1150 		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1151 	}
1152 	bind_texture();
1153 
1154 	int cur_x = x, cur_y = y, cur_line = 0;
1155 	int cursor_x = x - 1, cursor_y = y - 1;
1156 	size_t i_total = 0;
1157 	bool in_select = false;
1158 	glBegin(GL_QUADS);
1159 	while (true)
1160 	{
1161 		ch = msgs[imsg].data[ichar];
1162 		// watch for special characters
1163 		if (ch == '\0')
1164 		{
1165 			// end of message
1166 			if (++imsg >= msgs_size)
1167 				imsg = 0;
1168 			ichar = 0;
1169 			if (imsg == msg_start
1170 				|| filter_messages(msgs, msgs_size, filter, msg_start, imsg, ichar))
1171 				break;
1172 
1173 			last_color_char = 0;
1174 		}
1175 
1176 		if (select && select->lines && select->lines[cur_line].msg == -1)
1177 		{
1178 			select->lines[cur_line].msg = imsg;
1179 			select->lines[cur_line].chr = ichar;
1180 		}
1181 
1182 		if (ch == '\n' || ch == '\r' || ch == '\0')
1183 		{
1184 			if (i_total == cursor)
1185 			{
1186 				// Cursor is on the newline character
1187 				cursor_x = cur_x;
1188 				cursor_y = cur_y;
1189 			}
1190 
1191 			// newline
1192 			if (++cur_line >= options.max_lines())
1193 				break;
1194 			cur_y += line_skip;
1195 			cur_x = x;
1196 			if (ch != '\0')
1197 				++ichar;
1198 			++i_total;
1199 			continue;
1200 		}
1201 
1202 		if (pos_selected(select, imsg, ichar))
1203 		{
1204 			if (!in_select)
1205 			{
1206 				glColor3fv(TextDrawOptions::default_selection_color.data());
1207 				in_select = true;
1208 			}
1209 		}
1210 		else if (in_select)
1211 		{
1212 			if (last_color_char)
1213 				set_color(from_color_char(last_color_char));
1214 			else if (msgs[imsg].r >= 0)
1215 				glColor3f(msgs[imsg].r, msgs[imsg].g, msgs[imsg].b);
1216 			else
1217 				set_color(c_grey1);
1218 
1219 			in_select = false;
1220 		}
1221 
1222 		if (is_color(ch))
1223 			last_color_char = ch;
1224 
1225 		int chr_width = advance_spacing(ch, options.zoom());
1226 		if (cur_x - x + chr_width <= options.max_width())
1227 		{
1228 			if (i_total == cursor)
1229 			{
1230 				cursor_x = cur_x;
1231 				cursor_y = cur_y;
1232 			}
1233 			cur_x += draw_char(ch, cur_x, cur_y, options.zoom(), in_select);
1234 			++ichar;
1235 			++i_total;
1236 		}
1237 		else
1238 		{
1239 			// ignore rest of this line, but keep track of
1240 			// color characters
1241 			++ichar;
1242 			++i_total;
1243 			while (true)
1244 			{
1245 				ch = msgs[imsg].data[ichar];
1246 				if (ch == '\0' || ch == '\n' || ch == '\r')
1247 					break;
1248 				if (is_color(ch))
1249 					last_color_char = ch;
1250 				++ichar;
1251 				++i_total;
1252 			}
1253 		}
1254 	}
1255 
1256 	if (i_total == cursor)
1257 	{
1258 		if (cur_x - x + cursor_width <= options.max_width())
1259 		{
1260 			cursor_x = cur_x;
1261 			cursor_y = cur_y;
1262 		}
1263 		else if (cur_line + 1 < options.max_lines())
1264 		{
1265 			cursor_x = x;
1266 			cursor_y = cur_y + line_skip;
1267 		}
1268 	}
1269 
1270 	if (cursor_x >= x && cursor_y >= y)
1271 	{
1272 		draw_char('_', cursor_x, cursor_y, options.zoom(), true);
1273 	}
1274 
1275 	glEnd();
1276 	if (is_ttf())
1277 	{
1278 		glDisable(GL_BLEND);
1279 	}
1280 	glDisable(GL_ALPHA_TEST);
1281 #ifdef OPENGL_TRACE
1282 CHECK_GL_ERRORS();
1283 #endif //OPENGL_TRACE
1284 }
1285 
draw_console_separator(int x_space,int y,const TextDrawOptions & options) const1286 void Font::draw_console_separator(int x_space, int y, const TextDrawOptions& options) const
1287 {
1288 	int pos = get_position('^');
1289 	int char_width = width_pos(pos, options.zoom());
1290 	int char_height = height(options.zoom());
1291 	int dx = advance_spacing_pos(pos, options.zoom());
1292 
1293 	float u_start, u_end, v_start, v_end;
1294 	get_texture_coordinates(pos, u_start, u_end, v_start, v_end);
1295 
1296 	glEnable(GL_ALPHA_TEST); // enable alpha filtering, so we have some alpha key
1297 	glAlphaFunc(GL_GREATER, 0.1f);
1298 	bind_texture();
1299 
1300 	glBegin(GL_QUADS);
1301 	int x = x_space;
1302 	while (x + char_width <= options.max_width())
1303 	{
1304 		glTexCoord2f(u_start, v_start); glVertex3i(x, y, 0);
1305 		glTexCoord2f(u_start, v_end);   glVertex3i(x, y + char_height, 0);
1306 		glTexCoord2f(u_end,   v_end);   glVertex3i(x + char_width, y + char_height, 0);
1307 		glTexCoord2f(u_end,   v_start); glVertex3i(x + char_width, y, 0);
1308 
1309 		x += dx;
1310 		if (x + char_width > options.max_width())
1311 			break;
1312 
1313 		glTexCoord2f(u_start, v_start); glVertex3i(x, y, 0);
1314 		glTexCoord2f(u_start, v_end);   glVertex3i(x, y + char_height, 0);
1315 		glTexCoord2f(u_end,   v_end);   glVertex3i(x + char_width, y + char_height, 0);
1316 		glTexCoord2f(u_end,   v_start); glVertex3i(x + char_width, y, 0);
1317 
1318 		x += 2 * dx;
1319 	}
1320 	glEnd();
1321 
1322 	glDisable(GL_ALPHA_TEST);
1323 #ifdef OPENGL_TRACE
1324 CHECK_GL_ERRORS();
1325 #endif //OPENGL_TRACE
1326 }
1327 
1328 #ifdef ELC
1329 #ifndef MAP_EDITOR2
draw_ortho_ingame_string(const unsigned char * text,size_t len,float x,float y,float z,int max_lines,float zoom_x,float zoom_y) const1330 void Font::draw_ortho_ingame_string(const unsigned char* text, size_t len,
1331 	float x, float y, float z, int max_lines, float zoom_x, float zoom_y) const
1332 {
1333 	int char_height = height(zoom_y);
1334 	int line_skip = vertical_advance(zoom_y);
1335 	float cur_x = x;
1336 	float cur_y = y;
1337 	int cur_line = 0;
1338 
1339 	glEnable(GL_ALPHA_TEST); // enable alpha filtering, so we have some alpha key
1340 	glAlphaFunc(GL_GREATER, 0.1f);
1341 	if (is_ttf())
1342 	{
1343 		// Only enable alpha blending for TTF fonts. The old style fonts have an alpha channel,
1344 		// but this has never been used before. Instead, the semi-transparent border has always
1345 		// been drawn as solid black.
1346 		glEnable(GL_BLEND);
1347 		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1348 	}
1349 	bind_texture();
1350 	glBegin(GL_QUADS);
1351 
1352 	for (size_t i = 0; i < len; ++i)
1353 	{
1354 		unsigned char ch = text[i];
1355 		if (ch == '\n' || ch == '\r')
1356 		{
1357 			cur_y -= line_skip;
1358 			cur_x = x;
1359 			if (++cur_line >= max_lines)
1360 				break;
1361 		}
1362 		else if (is_color(ch))
1363 		{
1364 			glEnd(); // Ooops - NV bug fix!!
1365 			set_color(from_color_char(ch));
1366 			glBegin(GL_QUADS);
1367 		}
1368 		else
1369 		{
1370 			int pos = get_position(ch);
1371 			if (pos >= 0)
1372 			{
1373 				int char_width = width_pos(pos, zoom_x);
1374 				float u_start, u_end, v_start, v_end;
1375 				get_texture_coordinates(pos, u_start, u_end, v_start, v_end);
1376 
1377 				float x_left = cur_x + _metrics[pos].x_off;
1378 
1379 				glTexCoord2f(u_start, v_start); glVertex3f(x_left,            cur_y+char_height+_outline, z);
1380 				glTexCoord2f(u_start, v_end);   glVertex3f(x_left,            cur_y-_outline,             z);
1381 				glTexCoord2f(u_end,   v_end);   glVertex3f(x_left+char_width, cur_y-_outline,             z);
1382 				glTexCoord2f(u_end,   v_start); glVertex3f(x_left+char_width, cur_y+char_height+_outline, z);
1383 
1384 				cur_x += advance_spacing_pos(pos, zoom_x);
1385 			}
1386 		}
1387 	}
1388 
1389 	glEnd();
1390 	if (is_ttf())
1391 		glDisable(GL_BLEND);
1392 	glDisable(GL_ALPHA_TEST);
1393 }
1394 #endif // !MAP_EDITOR_2
1395 #endif // ELC
1396 
1397 #ifdef TTF
render_glyph(size_t i_glyph,int size,int y_delta,int outline_size,TTF_Font * font,SDL_Surface * surface)1398 bool Font::render_glyph(size_t i_glyph, int size, int y_delta, int outline_size, TTF_Font *font,
1399 	SDL_Surface *surface)
1400 {
1401 	static const Uint16 glyphs[nr_glyphs] = {
1402 		' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-',
1403 		'.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';',
1404 		'<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
1405 		'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
1406 		'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e',
1407 		'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
1408 		't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', 252, 233, 226, // üéâ'
1409 		224, 231, 234, 235, 232, 239, 244, 249, 228, 246, 252, 196, 214, 220, // àçêëèïôùäöüÄÖÜ
1410 		223, 230, 248, 229, 198, 216, 197, 241, 209, 225, 193, 201, 237, 205, // ßæøåÆØÅñÑáÁÉíÍ
1411 		243, 211, 250, 218, 251, 238                                          // óÓúÚûî
1412 
1413 	};
1414 	static const SDL_Color white = { .r = 0xff, .g = 0xff, .b = 0xff, .a = 0xff };
1415 	static const SDL_Color black = { .r = 0x00, .g = 0x00, .b = 0x00, .a = 0xff };
1416 
1417 	Uint16 glyph = glyphs[i_glyph];
1418 	if (!TTF_GlyphIsProvided(font, glyph))
1419 	{
1420 		LOG_ERROR("Font '%s' does not provide glyph for code point '%d': %s",
1421 			_font_name.c_str(), glyph, TTF_GetError());
1422 		return false;
1423 	}
1424 
1425 	SDL_Surface* glyph_surface;
1426 	if (outline_size > 0)
1427 	{
1428 		TTF_SetFontOutline(font, outline_size);
1429 		glyph_surface = TTF_RenderGlyph_Blended(font, glyph, black);
1430 		if (!glyph_surface)
1431 		{
1432 			const char* locale = setlocale(LC_ALL, "");
1433 			LOG_ERROR("Failed to render outline for TTF glyph '%lc' in font \"%s\": %s", glyphs[i_glyph],
1434 				_font_name.c_str(), TTF_GetError());
1435 			setlocale(LC_ALL, locale);
1436 			return false;
1437 		}
1438 
1439 		TTF_SetFontOutline(font, 0);
1440 		SDL_Surface *fg_surface = TTF_RenderGlyph_Blended(font, glyph, white);
1441 		if (!fg_surface)
1442 		{
1443 			const char* locale = setlocale(LC_ALL, "");
1444 			LOG_ERROR("Failed to render TTF glyph '%lc' in font \"%s\": %s", glyphs[i_glyph],
1445 				_font_name.c_str(), TTF_GetError());
1446 			setlocale(LC_ALL, locale);
1447 			return false;
1448 		}
1449 
1450 		SDL_Rect rect = {outline_size, outline_size, fg_surface->w, fg_surface->h};
1451 		SDL_SetSurfaceBlendMode(glyph_surface, SDL_BLENDMODE_BLEND);
1452 		SDL_BlitSurface(fg_surface, NULL, glyph_surface, &rect);
1453 		SDL_FreeSurface(fg_surface);
1454 	}
1455 	else
1456 	{
1457 		glyph_surface = TTF_RenderGlyph_Blended(font, glyph, white);
1458 		if (!glyph_surface)
1459 		{
1460 			const char* locale = setlocale(LC_ALL, "");
1461 			LOG_ERROR("Failed to render TTF glyph '%lc' in font \"%s\": %s", glyphs[i_glyph],
1462 				_font_name.c_str(), TTF_GetError());
1463 			setlocale(LC_ALL, locale);
1464 			return false;
1465 		}
1466 	}
1467 
1468 	int width = glyph_surface->w;
1469 	int height = glyph_surface->h;
1470 
1471 	int row = i_glyph / font_chars_per_line;
1472 	int col = i_glyph % font_chars_per_line;
1473 
1474 	// Reserve space for the outline, even when it is not used
1475 	int row_height = size + 2 * _outline + 2;
1476 
1477 	SDL_Rect area;
1478 	area.x = col*size;
1479 	area.y = row*row_height + 1 + y_delta + _outline - outline_size;
1480 	area.w = width;
1481 	area.h = height;
1482 
1483 	SDL_SetSurfaceAlphaMod(glyph_surface, 0xFF);
1484 	SDL_SetSurfaceBlendMode(glyph_surface, SDL_BLENDMODE_NONE);
1485 	int err = SDL_BlitSurface(glyph_surface, NULL, surface, &area);
1486 	SDL_FreeSurface(glyph_surface);
1487 	if (err)
1488 	{
1489 		LOG_ERROR("Failed to write glyph to surface: %s", SDL_GetError());
1490 		return false;
1491 	}
1492 
1493 	int y_min, y_max, x_min;
1494 	_metrics[i_glyph].width = width;
1495 	TTF_GlyphMetrics(font, glyph, &x_min, nullptr, &y_min, &y_max, &_metrics[i_glyph].advance);
1496 	_metrics[i_glyph].x_off = std::min(x_min, 0);
1497 	_metrics[i_glyph].top = y_delta + TTF_FontAscent(font) - y_max;
1498 	_metrics[i_glyph].bottom = y_delta + TTF_FontAscent(font) - y_min;
1499 	_metrics[i_glyph].u_start = float(col * size) / surface->w;
1500 	_metrics[i_glyph].v_start = float(row * row_height + 1) / surface->h;
1501 	_metrics[i_glyph].u_end = float(col * size + width) / surface->w;
1502 	_metrics[i_glyph].v_end = float(row * row_height + row_height - 1) / surface->h;
1503 
1504 	return true;
1505 }
1506 
find_point_size(int height)1507 int Font::find_point_size(int height)
1508 {
1509 	// If the point size is too small, we may run into problems later where the size of the
1510 	// glyph becomes zero. This leads to an error in the generation of the texture atlas, which
1511 	// will reset the font to the default font. It is hard to distinguish such errors, where the
1512 	// font size is simply too small, from real rendering issues. So we cap the point size at a
1513 	// minimum of 6, which seems to be a safe limit.
1514 	static const int min_point_size = 6;
1515 
1516 	int min = min_point_size, max = 2 * height;
1517 	if (max <= min)
1518 	{
1519 		// Height is too small. Check if the font will open at the minimum point size. If not
1520 		// return 0 to use the default point size and hope for the best.
1521 		TTF_Font *font = open_font(_file_name.c_str(), min);
1522 		if (!font)
1523 			return 0;
1524 		TTF_CloseFont(font);
1525 		return min;
1526 	}
1527 
1528 	while (max > min + 1)
1529 	{
1530 		int mid = (min + max) / 2;
1531 		TTF_Font *font = open_font(_file_name.c_str(), mid);
1532 		if (!font)
1533 			return 0;
1534 
1535 		int size = TTF_FontLineSkip(font);
1536 		TTF_CloseFont(font);
1537 
1538 		if (size == height)
1539 			return mid;
1540 		else if (size < height)
1541 			min = mid;
1542 		else
1543 			max = mid;
1544 	}
1545 	return max;
1546 }
1547 
build_texture_atlas()1548 bool Font::build_texture_atlas()
1549 {
1550 	static const int nr_rows = (nr_glyphs + font_chars_per_line-1) / font_chars_per_line;
1551 
1552 	if (!is_ttf())
1553 	{
1554 		LOG_ERROR("Font '%s' is not a TrueType font, no need to build a texture",
1555 			_font_name.c_str());
1556 		return false;
1557 	}
1558 
1559 	int point_size = _point_size ? _point_size : ttf_point_size;
1560 	TTF_Font *font = open_font(_file_name.c_str(), point_size);
1561 	if (!font)
1562 	{
1563 		LOG_ERROR("Failed to open TrueType font %s: %s", _file_name.c_str(), TTF_GetError());
1564 		_flags |= Flags::FAILED;
1565 		return false;
1566 	}
1567 
1568 	int outline_size = has_outline() ? _outline : 0;
1569 
1570 	int size = TTF_FontLineSkip(font);
1571 	int width = next_power_of_two(font_chars_per_line * size);
1572 	// Keep two rows of empty pixels (one top, one bottom) around the glyphs for systems with an
1573 	// alternative view on texture coordinates (i.e. off by one pixel). Perhaps they may lose a
1574 	// single pixel row of a really high character, but at least we won't draw part of the character
1575 	// bwlow or above it.
1576 	int height = next_power_of_two(nr_rows * (size + 2 * _outline + 2));
1577 	SDL_Surface *image = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 32,
1578 #if SDL_BYTEORDER == SDL_LIL_ENDIAN /* OpenGL RGBA masks */
1579 		0x000000FF,
1580 		0x0000FF00,
1581 		0x00FF0000,
1582 		0xFF000000
1583 #else
1584 		0xFF000000,
1585 		0x00FF0000,
1586 		0x0000FF00,
1587 		0x000000FF
1588 #endif
1589 	);
1590 	if (!image)
1591 	{
1592 		LOG_ERROR("Failed to create surface for TTF texture atlas: %s", TTF_GetError());
1593 		TTF_CloseFont(font);
1594 		_flags |= Flags::FAILED;
1595 		return false;
1596 	}
1597 
1598 	int y_delta = (size - TTF_FontHeight(font)) / 2;
1599 	for (size_t i_glyph = 0; i_glyph < nr_glyphs; ++i_glyph)
1600 	{
1601 		if (!render_glyph(i_glyph, size, y_delta, outline_size, font, image))
1602 		{
1603 			SDL_FreeSurface(image);
1604 			TTF_CloseFont(font);
1605 			_flags |= Flags::FAILED;
1606 			return false;
1607 		}
1608 	}
1609 
1610 	GLuint texture_id;
1611 	glGenTextures(1, &texture_id);
1612 	::bind_texture_id(texture_id);
1613 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1614 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1615 	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE,
1616 		image->pixels);
1617 
1618 	SDL_FreeSurface(image);
1619 	_texture_id.gl_id = texture_id;
1620 	_texture_width = width;
1621 	_texture_height = height;
1622 	_block_width = size;
1623 	_line_height = _vertical_advance = size;
1624 	_font_top_offset = y_delta;
1625 
1626 	int digit_start = get_position('0');
1627 	int digit_end = get_position('9') + 1;
1628 	int lower_start = get_position('a');
1629 	int lower_end = get_position('z') + 1;
1630 	int upper_start = get_position('A');
1631 	int upper_end = get_position('Z') + 1;
1632 	int underscore_pos = get_position('_');
1633 
1634 	int digit_top = std::min_element(_metrics.begin() + digit_start, _metrics.begin() + digit_end,
1635 		[](const Metrics& m0, const Metrics& m1) { return m0.top < m1.top; })->top;
1636 	int digit_bottom = std::max_element(_metrics.begin() + digit_start, _metrics.begin() + digit_end,
1637 		[](const Metrics& m0, const Metrics& m1) { return m0.bottom < m1.bottom; })->bottom;
1638 	_digit_center_offset = (digit_top + digit_bottom) / 2;
1639 	int pos = get_position('*');
1640 	_password_center_offset = (_metrics[pos].top + _metrics[pos].bottom) / 2;
1641 	_max_advance = std::max_element(_metrics.begin(), _metrics.end(),
1642 		[](const Metrics& m0, const Metrics& m1) { return m0.advance < m1.advance; })->advance;
1643 	_max_digit_advance = std::max_element(_metrics.begin() + digit_start, _metrics.begin() + digit_end,
1644 		[](const Metrics& m0, const Metrics& m1) { return m0.advance < m1.advance; })->advance;
1645 	_max_name_advance= std::max({_max_digit_advance,
1646 		_metrics[underscore_pos].advance,
1647 		std::max_element(_metrics.begin() + upper_start, _metrics.begin() + upper_end,
1648 			[](const Metrics& m0, const Metrics& m1) { return m0.advance < m1.advance; })->advance,
1649 		std::max_element(_metrics.begin() + lower_start, _metrics.begin() + lower_end,
1650 			[](const Metrics& m0, const Metrics& m1) { return m0.advance < m1.advance; })->advance
1651 	});
1652 	_avg_advance = calc_average_advance();
1653 	_scale_x = _scale_y = float(font_block_height - 2) / size;
1654 	_flags |= Flags::HAS_TEXTURE;
1655 
1656 	TTF_CloseFont(font);
1657 
1658 	return true;
1659 }
1660 
1661 #endif // TTF
1662 
calc_average_advance()1663 int Font::calc_average_advance()
1664 {
1665 	int total = std::accumulate(letter_freqs.begin(), letter_freqs.end(), 0);
1666 	int dot = std::inner_product(letter_freqs.begin(), letter_freqs.end(), _metrics.begin(), 0,
1667 		std::plus<int>(), [](int f, const Metrics& m) { return f * m.advance; });
1668 	return std::round(float(dot) / total);
1669 }
1670 
1671 const std::array<size_t, NR_FONT_CATS> FontManager::_default_font_idxs
1672 	= { 0, 0, 0, 2, 0, 3, 0, 0 };
1673 std::array<size_t, NR_FONT_CATS> FontManager::font_idxs = { 0, 0, 0, 2, 0, 3, 0, 0, 0 };
1674 std::array<float, NR_FONT_CATS> FontManager::font_scales
1675 	= { 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 };
1676 
initialize()1677 bool FontManager::initialize()
1678 {
1679     _options.clear();
1680 	_fonts.clear();
1681 
1682 	for (size_t i = 0; i < _nr_bundled_fonts; ++i)
1683 	{
1684 		FontOption option(i);
1685 		if (!option.failed())
1686 			_options.push_back(std::move(option));
1687 	}
1688 	if (_options.empty() || _options[0].failed())
1689 		return false;
1690 #ifdef TTF
1691 	initialize_ttf();
1692 #endif
1693 	add_select_options();
1694 
1695 	_fonts.insert(std::make_pair(0, Font(_options[0])));
1696 
1697 	return true;
1698 }
1699 
1700 #ifdef TTF
initialize_ttf()1701 void FontManager::initialize_ttf()
1702 {
1703 	static const char* patterns[] = { "*.ttf", "*.ttc", "*.otf" };
1704 
1705 	if (!use_ttf)
1706 		return;
1707 
1708 	if (!TTF_WasInit() && TTF_Init() == -1)
1709 	{
1710 		LOG_ERROR("Failed to initialize True Type fonts: %s", TTF_GetError());
1711 		use_ttf = 0;
1712 		return;
1713 	}
1714 
1715 	size_t nr_existing_fonts = _options.size();
1716 	// First load from the game fonts directory
1717 	std::string fonts_dir = std::string(datadir) + "/fonts";
1718 	for (const char* pattern: patterns)
1719 	{
1720 		search_files_and_apply(fonts_dir.c_str(), pattern,
1721 			[](const char *fname_ptr) {
1722 				size_t dir_name_len = strlen(datadir);
1723 				std::string fname;
1724 				if (!strncmp(fname_ptr, datadir, dir_name_len))
1725 					fname = fname_ptr + dir_name_len;
1726 				else
1727 					fname = fname_ptr;
1728 				FontOption option(fname);
1729 				if (!option.failed())
1730 					FontManager::get_instance()._options.push_back(std::move(option));
1731 			}, 0);
1732 	}
1733 	// Now search files in the user specified fonts directory
1734 	for (const char* pattern: patterns)
1735 	{
1736 		search_files_and_apply(ttf_directory, pattern,
1737 			[](const char *fname_ptr) {
1738 				size_t dir_name_len = strlen(ttf_directory);
1739 				std::string fname;
1740 				if (!strncmp(fname_ptr, ttf_directory, dir_name_len))
1741 					fname = fname_ptr + dir_name_len;
1742 				else
1743 					fname = fname_ptr;
1744 				FontOption option(fname);
1745 				if (!option.failed())
1746 					FontManager::get_instance()._options.push_back(std::move(option));
1747 			}, 1);
1748 	}
1749 
1750 	// Remove duplicate fonts, possibly caused by multiple copies of the same font in different
1751 	// directories. A font is assumed to be a duplicate of another if both the base name of the
1752 	// file and the font name are equal.
1753 	std::sort(_options.begin() + nr_existing_fonts, _options.end(),
1754 		[](const FontOption& f0, const FontOption& f1) {
1755 			if (f0.file_base_name() == f1.file_base_name())
1756 				return f0.font_name() < f1.font_name();
1757 			else
1758 				return f0.file_base_name() < f1.file_base_name();
1759 		});
1760 	auto new_end = std::unique(_options.begin(), _options.end(),
1761 		[](const FontOption& f0, const FontOption& f1) {
1762 			return f0.file_base_name() == f1.file_base_name() && f0.font_name() == f1.font_name(); });
1763 	_options.erase(new_end, _options.end());
1764 
1765 	// Sort TTF fonts by font name, but keep them after EL bundled fonts
1766 	std::sort(_options.begin() + nr_existing_fonts, _options.end(),
1767 		[](const FontOption& f0, const FontOption& f1) { return f0.font_name() < f1.font_name(); });
1768 }
1769 #endif // TTF
1770 
add_select_options(bool add_button)1771 void FontManager::add_select_options(bool add_button)
1772 {
1773 	clear_multiselect_var("ui_font");
1774 	clear_multiselect_var("chat_font");
1775 	clear_multiselect_var("name_font");
1776 	clear_multiselect_var("book_font");
1777 	clear_multiselect_var("note_font");
1778 	clear_multiselect_var("rules_font");
1779 	clear_multiselect_var("encyclopedia_font");
1780 
1781 	_fixed_width_idxs.clear();
1782 	for (size_t i = 0; i < _options.size(); ++i)
1783 	{
1784 		const FontOption& option = _options[i];
1785 		if (option.is_fixed_width())
1786 			_fixed_width_idxs.push_back(i);
1787 		option.add_select_options(add_button);
1788 	}
1789 
1790 	if (add_button)
1791 	{
1792 		set_multiselect_var("ui_font", font_idxs[UI_FONT], true);
1793 		set_multiselect_var("chat_font", font_idxs[CHAT_FONT], true);
1794 		set_multiselect_var("name_font", font_idxs[NAME_FONT], true);
1795 		set_multiselect_var("book_font", font_idxs[BOOK_FONT], true);
1796 		set_multiselect_var("note_font", font_idxs[NOTE_FONT], true);
1797 		set_multiselect_var("rules_font", font_idxs[RULES_FONT], true);
1798 		auto it = std::find(_fixed_width_idxs.begin(), _fixed_width_idxs.end(),
1799 			font_idxs[ENCYCLOPEDIA_FONT]);
1800 		if (it != _fixed_width_idxs.end())
1801 			set_multiselect_var("encyclopedia_font", it - _fixed_width_idxs.begin(), true);
1802 	}
1803 }
1804 
get_key(size_t idx,int height,bool outline)1805 static uint32_t get_key(size_t idx, int height, bool outline)
1806 {
1807 	// It is unlikely that there will be more than 64k fonts, or that the line height wil be more
1808 	// than 32k pixels, so combine the three values into a single key 32-bit key.
1809 	return (outline << 31) | ((height & 0x7fff) << 16) | (idx & 0xffff);
1810 }
1811 
get(Category cat,float text_zoom)1812 Font& FontManager::get(Category cat, float text_zoom)
1813 {
1814 	size_t idx = font_idxs[cat];
1815 	bool outline = cat != BOOK_FONT; // Don't draw an outline for book text
1816 
1817 	if (idx > _options.size())
1818 	{
1819 #ifdef TTF
1820 		// Invalid font number
1821 		if (cat == CONFIG_FONT)
1822 		{
1823 			// Probably TTF was disabled, and the settings window was using a TTF font. Check if
1824 			// it is still available
1825 			int height = std::round((Font::font_block_height - 2) * text_zoom * font_scales[cat]);
1826 			uint32_t key = get_key(0xffff, height, outline);
1827 			auto it = _fonts.find(key);
1828 			if (it != _fonts.end())
1829 				return it->second;
1830 			else
1831 				font_idxs[cat] = idx = 0;
1832 		}
1833 		else
1834 		{
1835 			font_idxs[cat] = idx = 0;
1836 		}
1837 #else // TTF
1838 		font_idxs[cat] = idx = 0;
1839 #endif // TTF
1840 	}
1841 
1842 #ifdef TTF
1843 	// Bundled fonts come in only one size, so always use 0 for the line height and rely on scaling
1844 	// to display the correct size.
1845 	int height = _options[idx].is_ttf()
1846 		? std::round((Font::font_block_height - 2) * text_zoom * font_scales[cat])
1847 		: 0;
1848 	uint32_t key = get_key(idx, height, outline);
1849 	auto it = _fonts.find(key);
1850 	if (it == _fonts.end())
1851 	{
1852 		// The font has not been loaded yet
1853 		Font font = _options[idx].is_ttf() ? Font(_options[idx], height, outline) : Font(_options[idx]);
1854 		it = _fonts.insert(std::make_pair(key, std::move(font))).first;
1855 	}
1856 #else // TTF
1857 	uint32_t key = idx;
1858 	auto it = _fonts.find(idx);
1859 	if (it == _fonts.end())
1860 	{
1861 		// The font has not been loaded yet
1862 		Font font = Font(_options[idx]);
1863 		it = _fonts.insert(std::make_pair(key, std::move(font))).first;
1864 	}
1865 #endif // TTF
1866 
1867 	Font *result = &it->second;
1868 	if (result->failed())
1869 	{
1870 		// Failed to load previously, switch to fixed
1871 		font_idxs[cat] = 0;
1872 		return _fonts.at(0);
1873 	}
1874 
1875 	if (!result->has_texture() && !result->load_texture())
1876 	{
1877 		// Failed to load or generate texture, switch to fixed
1878 		font_idxs[cat] = 0;
1879 		return _fonts.at(0);
1880 	}
1881 
1882 	return *result;
1883 }
1884 
1885 #ifdef TTF
disable_ttf()1886 void FontManager::disable_ttf()
1887 {
1888 	// If not yet initialized, do nothing
1889 	if (!is_initialized())
1890 		return;
1891 
1892 	size_t config_font_idx = font_idxs[CONFIG_FONT];
1893 
1894 	// Save the file names of the fonts currently in use
1895 	for (size_t i = 0; i < NR_FONT_CATS; ++i)
1896 	{
1897 		size_t font_num = font_idxs[i];
1898 		if (font_num >= _options.size())
1899 			// Invalid index, possibly configuration font after disabling TTF previously
1900 			font_num = 0;
1901 
1902 		_saved_font_files[i] = _options[font_num].file_name();
1903 		if (_options[font_num].is_ttf())
1904 		{
1905 			// Revert to default bundled font
1906 			font_idxs[i] = (i == CONFIG_FONT ? 0xffff : _default_font_idxs[i]);
1907 		}
1908 	}
1909 
1910 	// Remove the font options
1911 	auto first_ttf = std::find_if(_options.begin(), _options.end(),
1912 		[](const FontOption& opt) { return opt.is_ttf(); });
1913 	_options.erase(first_ttf, _options.end());
1914 
1915 	// Remove the fonts themselves
1916 	std::unordered_map<uint32_t, Font> config_fonts;
1917 	for (auto it = _fonts.begin(); it != _fonts.end(); )
1918 	{
1919 		if (it->second.is_ttf())
1920 		{
1921 			if ((it->first & 0xffff) == config_font_idx)
1922 			{
1923 				config_fonts.insert(std::make_pair((it->first & 0xffff0000) | 0xffff, std::move(it->second)));
1924 			}
1925 			it = _fonts.erase(it);
1926 		}
1927 		else
1928 		{
1929 			++it;
1930 		}
1931 	}
1932 	// Reinstate config font with invalid index
1933 	for (auto it = config_fonts.begin(); it != config_fonts.end(); ++it)
1934 		_fonts.insert(std::make_pair(it->first, std::move(it->second)));
1935 
1936 	// Remove the TTF font options from the selection
1937 	add_select_options(true);
1938 }
1939 
enable_ttf()1940 void FontManager::enable_ttf()
1941 {
1942 	// If not yet initialized, do nothing
1943 	if (!is_initialized())
1944 		return;
1945 
1946 	// Add TrueType fonts to the font list
1947 	initialize_ttf();
1948 
1949 	// Check if we saved font file names when we disabled TTF, and if so,
1950 	// restore them. Only restore TTF fonts, keeping any changes in bundled
1951 	// fonts.
1952 	for (size_t i = 0; i < NR_FONT_CATS; ++i)
1953 	{
1954 		// Font for the settings window should not change, it will be updated and set to the
1955 		// UI font when the window is closed and reopened
1956 		if (i == CONFIG_FONT)
1957 			continue;
1958 
1959 		const std::string& fname = _saved_font_files[i];
1960 		if (!fname.empty())
1961 		{
1962 			std::vector<FontOption>::const_iterator it = std::find_if(_options.begin(), _options.end(),
1963 				[fname](const FontOption& opt) { return opt.file_name() == fname; });
1964 			if (it != _options.end() && it->is_ttf())
1965 				font_idxs[i] = it - _options.begin();
1966 		}
1967 	}
1968 
1969 	// Add selection options for the new fonts
1970 	add_select_options(true);
1971 }
1972 #endif // TTF
1973 
1974 } // namespace eternal_lands
1975 
1976 
1977 extern "C"
1978 {
1979 
1980 using namespace eternal_lands;
1981 
1982 size_t *font_idxs = FontManager::font_idxs.data();
1983 float *font_scales = FontManager::font_scales.data();
1984 #ifdef TTF
1985 int use_ttf = 0;
1986 #ifdef LINUX
1987 char ttf_directory[TTF_DIR_SIZE] = "/usr/share/fonts/TTF";
1988 #elif defined WINDOWS
1989 char ttf_directory[TTF_DIR_SIZE] = "C:/Windows/Fonts";
1990 #elif defined OSX
1991 char ttf_directory[TTF_DIR_SIZE] = "/System/Library/Fonts";
1992 #else
1993 char ttf_directory[TTF_DIR_SIZE];
1994 #endif //
1995 #endif // TTF
1996 
initialize_fonts()1997 int initialize_fonts()
1998 {
1999 	return FontManager::get_instance().initialize();
2000 }
2001 
get_fixed_width_font_number(size_t idx)2002 size_t get_fixed_width_font_number(size_t idx)
2003 {
2004 	return FontManager::get_instance().fixed_width_font_number(idx);
2005 }
2006 
get_char_width_zoom(unsigned char c,font_cat cat,float zoom)2007 int get_char_width_zoom(unsigned char c, font_cat cat, float zoom)
2008 {
2009 	return FontManager::get_instance().advance_spacing(cat, c, zoom);
2010 }
get_max_char_width_zoom(font_cat cat,float zoom)2011 int get_max_char_width_zoom(font_cat cat, float zoom)
2012 {
2013 	return FontManager::get_instance().max_width_spacing(cat, zoom);
2014 }
get_avg_char_width_zoom(font_cat cat,float zoom)2015 int get_avg_char_width_zoom(font_cat cat, float zoom)
2016 {
2017 	return FontManager::get_instance().average_width_spacing(cat, zoom);
2018 }
get_max_digit_width_zoom(font_cat cat,float zoom)2019 int get_max_digit_width_zoom(font_cat cat, float zoom)
2020 {
2021 	return FontManager::get_instance().max_digit_width_spacing(cat, zoom);
2022 }
get_max_name_width_zoom(font_cat cat,float zoom)2023 int get_max_name_width_zoom(font_cat cat, float zoom)
2024 {
2025 	return FontManager::get_instance().max_name_width_spacing(cat, zoom);
2026 }
get_buf_width_zoom(const unsigned char * str,size_t len,font_cat cat,float zoom)2027 int get_buf_width_zoom(const unsigned char* str, size_t len, font_cat cat, float zoom)
2028 {
2029 	return FontManager::get_instance().line_width(static_cast<FontManager::Category>(cat),
2030 		str, len, zoom);
2031 }
get_line_height(font_cat cat,float text_zoom)2032 int get_line_height(font_cat cat, float text_zoom)
2033 {
2034 	return FontManager::get_instance().line_height(cat, text_zoom);
2035 }
get_line_skip(font_cat cat,float text_zoom)2036 int get_line_skip(font_cat cat, float text_zoom)
2037 {
2038 	return FontManager::get_instance().vertical_advance(cat, text_zoom);
2039 }
get_max_nr_lines(int height,font_cat cat,float zoom)2040 int get_max_nr_lines(int height, font_cat cat, float zoom)
2041 {
2042 	return FontManager::get_instance().max_nr_lines(cat, height, zoom);
2043 }
get_text_height(int nr_lines,font_cat cat,float zoom)2044 int get_text_height(int nr_lines, font_cat cat, float zoom)
2045 {
2046 	return FontManager::get_instance().text_height(cat, nr_lines, zoom);
2047 }
get_buf_dimensions(const unsigned char * str,size_t len,font_cat cat,float text_zoom,int * width,int * height)2048 void get_buf_dimensions(const unsigned char* str, size_t len, font_cat cat, float text_zoom,
2049 	int *width, int *height)
2050 {
2051 	auto dims = FontManager::get_instance().dimensions(cat, str, len, text_zoom);
2052 	*width = dims.first;
2053 	*height = dims.second;
2054 }
2055 
get_center_offset(const unsigned char * text,size_t len,font_cat cat,float zoom)2056 int get_center_offset(const unsigned char* text, size_t len, font_cat cat, float zoom)
2057 {
2058 	return FontManager::get_instance().center_offset(cat, text, len, zoom);
2059 }
2060 
get_top_bottom(const unsigned char * text,size_t len,font_cat cat,float zoom,int * top,int * bottom)2061 void get_top_bottom(const unsigned char* text, size_t len, font_cat cat, float zoom,
2062 	int *top, int *bottom)
2063 {
2064 	auto coords = FontManager::get_instance().top_bottom(cat, text, len, zoom);
2065 	*top = coords.first;
2066 	*bottom = coords.second;
2067 }
2068 
reset_soft_breaks(unsigned char * text,int len,int size,font_cat cat,float text_zoom,int width,int * cursor_ptr,int * max_line_width)2069 int reset_soft_breaks(unsigned char *text, int len, int size, font_cat cat,
2070 	float text_zoom, int width, int *cursor_ptr, int *max_line_width)
2071 {
2072 	int cursor = cursor_ptr ? *cursor_ptr : -1;
2073 	TextDrawOptions options = TextDrawOptions().set_max_width(width).set_zoom(text_zoom);
2074 	ustring wrapped_text;
2075 	int nr_lines, cursor_diff;
2076 	std::tie(wrapped_text, nr_lines, cursor_diff) = FontManager::get_instance()
2077 		.reset_soft_breaks(cat, text, len, options, cursor, max_line_width);
2078 
2079 	size_t new_len = wrapped_text.copy(text, size_t(size)-1);
2080 	text[new_len] = '\0';
2081 	if (cursor_ptr)
2082 		*cursor_ptr = std::min(cursor + cursor_diff, size - 1);
2083 
2084 	return nr_lines;
2085 }
put_small_colored_text_in_box_zoomed(int color,const unsigned char * text,int len,int width,unsigned char * buffer,float text_zoom)2086 void put_small_colored_text_in_box_zoomed(int color,
2087 	const unsigned char* text, int len, int width,
2088 	unsigned char* buffer, float text_zoom)
2089 {
2090 	TextDrawOptions options = TextDrawOptions().set_max_width(width)
2091 		.set_zoom(text_zoom * DEFAULT_SMALL_RATIO);
2092 	ustring wrapped_text;
2093 	int nr_lines, cursor_diff;
2094 	std::tie(wrapped_text, nr_lines, cursor_diff) = FontManager::get_instance()
2095 		.reset_soft_breaks(FontManager::Category::UI_FONT, text, len, options, -1, nullptr);
2096 	// If we don't end on a newline, add one.
2097 	// TODO: check if that's necessary.
2098 	if (wrapped_text.back() != '\n')
2099 		wrapped_text.push_back('\n');
2100 
2101 	size_t new_len = 0;
2102 	if (!is_color(wrapped_text.front()))
2103 		buffer[new_len++] = to_color_char(color);
2104 	// FIXME: no size specified for either buffer. Pass unlimited size,
2105 	// and hope for the best...
2106 	new_len += wrapped_text.copy(buffer + new_len, ustring::npos);
2107 	buffer[new_len] = '\0';
2108 }
2109 
vdraw_text(int x,int y,const unsigned char * text,size_t len,font_cat cat,va_list options)2110 void vdraw_text(int x, int y, const unsigned char* text, size_t len, font_cat cat, va_list options)
2111 {
2112 	TextDrawOptions tdo;
2113 	text_draw_option_sel sel;
2114 	int sel_begin = 0, sel_end = 0;
2115 	bool end_reached = false;
2116 	do
2117 	{
2118 		sel = text_draw_option_sel(va_arg(options, int));
2119 		switch (sel)
2120 		{
2121 			case TDO_MAX_WIDTH:
2122 				tdo.set_max_width(va_arg(options, int));
2123 				break;
2124 			case TDO_MAX_LINES:
2125 				tdo.set_max_lines(va_arg(options, int));
2126 				break;
2127 			case TDO_ZOOM:
2128 				tdo.set_zoom(va_arg(options, double));
2129 				break;
2130 			case TDO_LINE_SPACING:
2131 				tdo.set_line_spacing(va_arg(options, double));
2132 				break;
2133 			case TDO_ALIGNMENT:
2134 				tdo.set_alignment(TextDrawOptions::Alignment(va_arg(options, int)));
2135 				break;
2136 			case TDO_VERTICAL_ALIGNMENT:
2137 				tdo.set_vertical_alignment(TextDrawOptions::VerticalAlignment(va_arg(options, int)));
2138 				break;
2139 			case TDO_SHADOW:
2140 				tdo.set_shadow(va_arg(options, int));
2141 				break;
2142 			case TDO_FOREGROUND:
2143 			{
2144 				float r = va_arg(options, double);
2145 				float g = va_arg(options, double);
2146 				float b = va_arg(options, double);
2147 				tdo.set_foreground(r, g, b);
2148 				break;
2149 			}
2150 			case TDO_BACKGROUND:
2151 			{
2152 				float r = va_arg(options, double);
2153 				float g = va_arg(options, double);
2154 				float b = va_arg(options, double);
2155 				tdo.set_background(r, g, b);
2156 				break;
2157 			}
2158 			case TDO_SELECTION:
2159 			{
2160 				float r = va_arg(options, double);
2161 				float g = va_arg(options, double);
2162 				float b = va_arg(options, double);
2163 				tdo.set_selection(r, g, b);
2164 				break;
2165 			}
2166 			case TDO_IGNORE_COLOR:
2167 				tdo.set_ignore_color(va_arg(options, int));
2168 				break;
2169 			case TDO_HELP:
2170 				tdo.set_help(va_arg(options, int));
2171 				break;
2172 			case TDO_ELLIPSIS:
2173 				tdo.set_ellipsis(va_arg(options, int));
2174 				break;
2175 			case TDO_SHRINK_TO_FIT:
2176 				tdo.set_shrink_to_fit(va_arg(options, int));
2177 				break;
2178 			case TDO_SEL_BEGIN:
2179 				sel_begin = va_arg(options, int);
2180 				break;
2181 			case TDO_SEL_END:
2182 				sel_end = va_arg(options, int);
2183 				break;
2184 			case TDO_END:
2185 				end_reached = true;
2186 				break;
2187 			default:
2188 				LOG_ERROR("Unknown text draw option selector %d", sel);
2189 				end_reached = true;
2190 				break;
2191 		}
2192 	} while (!end_reached);
2193 
2194 	FontManager::get_instance().draw(cat, text, len, x, y, tdo, sel_begin, sel_end);
2195 }
2196 
draw_string_zoomed_centered_around(int x,int y,const unsigned char * text,int center_idx,float text_zoom)2197 void draw_string_zoomed_centered_around(int x, int y,
2198 	const unsigned char *text, int center_idx, float text_zoom)
2199 {
2200 	size_t len = strlen(reinterpret_cast<const char*>(text));
2201 	size_t idx = center_idx;
2202 	TextDrawOptions options = TextDrawOptions().set_zoom(text_zoom)
2203 		.set_alignment(TextDrawOptions::Alignment::RIGHT);
2204 	FontManager::get_instance().draw(FontManager::Category::UI_FONT, text,
2205 		std::min(len, idx), x, y, options);
2206 	if (idx < len)
2207 	{
2208 		options.set_alignment(TextDrawOptions::Alignment::LEFT);
2209 		FontManager::get_instance().draw(FontManager::Category::UI_FONT, text + idx,
2210 			len - idx, x, y, options);
2211 	}
2212 }
2213 
draw_messages(int x,int y,text_message * msgs,int msgs_size,Uint8 filter,int msg_start,int offset_start,int cursor,int width,int height,font_cat cat,float text_zoom,select_info * select)2214 void draw_messages(int x, int y, text_message *msgs, int msgs_size, Uint8 filter,
2215 	int msg_start, int offset_start, int cursor, int width, int height,
2216 	font_cat cat, float text_zoom, select_info* select)
2217 {
2218 	int max_nr_lines = FontManager::get_instance().max_nr_lines(cat, height, text_zoom);
2219 	TextDrawOptions options = TextDrawOptions().set_max_width(width)
2220 		.set_max_lines(max_nr_lines)
2221 		.set_zoom(text_zoom);
2222 	FontManager::get_instance().draw_messages(cat, msgs, msgs_size, x, y, filter,
2223 		msg_start, offset_start, options, cursor, select);
2224 }
2225 
draw_console_separator(int x_space,int y,int width,float text_zoom)2226 void draw_console_separator(int x_space, int y, int width, float text_zoom)
2227 {
2228 	TextDrawOptions options = TextDrawOptions().set_max_width(width).set_zoom(text_zoom);
2229 	FontManager::get_instance().draw_console_separator(CHAT_FONT, x_space, y, options);
2230 }
2231 
2232 #ifdef ELC
2233 #ifndef MAP_EDITOR2
draw_ortho_ingame_string(float x,float y,float z,const unsigned char * text,int max_lines,font_cat cat,float zoom_x,float zoom_y)2234 void draw_ortho_ingame_string(float x, float y, float z, const unsigned char *text, int max_lines,
2235 	font_cat cat, float zoom_x, float zoom_y)
2236 {
2237 	FontManager::get_instance().draw_ortho_ingame_string(cat, text,
2238 		strlen(reinterpret_cast<const char*>(text)),
2239 		x, y, z, max_lines, zoom_x, zoom_y);
2240 }
2241 #endif // !MAP_EDITOR2
2242 #endif // ELC
2243 
set_config_font(void)2244 void set_config_font(void)
2245 {
2246 	FontManager::get_instance().set_config_font();
2247 }
2248 
has_glyph(unsigned char c)2249 int has_glyph(unsigned char c)
2250 {
2251 	return Font::has_glyph(c);
2252 }
2253 
2254 #ifdef TTF
disable_ttf(void)2255 void disable_ttf(void)
2256 {
2257 	FontManager::get_instance().disable_ttf();
2258 }
2259 
enable_ttf(void)2260 void enable_ttf(void)
2261 {
2262 	FontManager::get_instance().enable_ttf();
2263 }
2264 #endif // TTF
2265 
2266 } //extern "C"
2267