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