]> git.lizzy.rs Git - dragonfireclient.git/blob - src/fontengine.cpp
Clean up stack after script_get_backtrace (#7854)
[dragonfireclient.git] / src / fontengine.cpp
1 /*
2 Minetest
3 Copyright (C) 2010-2014 sapier <sapier at gmx dot net>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "fontengine.h"
21 #include <cmath>
22 #include "client/renderingengine.h"
23 #include "config.h"
24 #include "porting.h"
25 #include "filesys.h"
26
27 #if USE_FREETYPE
28 #include "gettext.h"
29 #include "irrlicht_changes/CGUITTFont.h"
30 #endif
31
32 /** maximum size distance for getting a "similar" font size */
33 #define MAX_FONT_SIZE_OFFSET 10
34
35 /** reference to access font engine, has to be initialized by main */
36 FontEngine* g_fontengine = NULL;
37
38 /** callback to be used on change of font size setting */
39 static void font_setting_changed(const std::string &name, void *userdata)
40 {
41         g_fontengine->readSettings();
42 }
43
44 /******************************************************************************/
45 FontEngine::FontEngine(Settings* main_settings, gui::IGUIEnvironment* env) :
46         m_settings(main_settings),
47         m_env(env)
48 {
49
50         for (u32 &i : m_default_size) {
51                 i = (FontMode) FONT_SIZE_UNSPECIFIED;
52         }
53
54         assert(m_settings != NULL); // pre-condition
55         assert(m_env != NULL); // pre-condition
56         assert(m_env->getSkin() != NULL); // pre-condition
57
58         m_currentMode = FM_Simple;
59
60 #if USE_FREETYPE
61         if (g_settings->getBool("freetype")) {
62                 m_default_size[FM_Standard] = m_settings->getU16("font_size");
63                 m_default_size[FM_Fallback] = m_settings->getU16("fallback_font_size");
64                 m_default_size[FM_Mono]     = m_settings->getU16("mono_font_size");
65
66                 if (is_yes(gettext("needs_fallback_font"))) {
67                         m_currentMode = FM_Fallback;
68                 }
69                 else {
70                         m_currentMode = FM_Standard;
71                 }
72         }
73
74         // having freetype but not using it is quite a strange case so we need to do
75         // special handling for it
76         if (m_currentMode == FM_Simple) {
77                 std::stringstream fontsize;
78                 fontsize << DEFAULT_FONT_SIZE;
79                 m_settings->setDefault("font_size", fontsize.str());
80                 m_settings->setDefault("mono_font_size", fontsize.str());
81         }
82 #endif
83
84         m_default_size[FM_Simple]       = m_settings->getU16("font_size");
85         m_default_size[FM_SimpleMono]   = m_settings->getU16("mono_font_size");
86
87         updateSkin();
88
89         if (m_currentMode == FM_Standard) {
90                 m_settings->registerChangedCallback("font_size", font_setting_changed, NULL);
91                 m_settings->registerChangedCallback("font_path", font_setting_changed, NULL);
92                 m_settings->registerChangedCallback("font_shadow", font_setting_changed, NULL);
93                 m_settings->registerChangedCallback("font_shadow_alpha", font_setting_changed, NULL);
94         }
95         else if (m_currentMode == FM_Fallback) {
96                 m_settings->registerChangedCallback("fallback_font_size", font_setting_changed, NULL);
97                 m_settings->registerChangedCallback("fallback_font_path", font_setting_changed, NULL);
98                 m_settings->registerChangedCallback("fallback_font_shadow", font_setting_changed, NULL);
99                 m_settings->registerChangedCallback("fallback_font_shadow_alpha", font_setting_changed, NULL);
100         }
101
102         m_settings->registerChangedCallback("mono_font_path", font_setting_changed, NULL);
103         m_settings->registerChangedCallback("mono_font_size", font_setting_changed, NULL);
104         m_settings->registerChangedCallback("screen_dpi", font_setting_changed, NULL);
105         m_settings->registerChangedCallback("gui_scaling", font_setting_changed, NULL);
106 }
107
108 /******************************************************************************/
109 FontEngine::~FontEngine()
110 {
111         cleanCache();
112 }
113
114 /******************************************************************************/
115 void FontEngine::cleanCache()
116 {
117         for (auto &font_cache_it : m_font_cache) {
118
119                 for (auto &font_it : font_cache_it) {
120                         font_it.second->drop();
121                         font_it.second = NULL;
122                 }
123                 font_cache_it.clear();
124         }
125 }
126
127 /******************************************************************************/
128 irr::gui::IGUIFont* FontEngine::getFont(unsigned int font_size, FontMode mode)
129 {
130         if (mode == FM_Unspecified) {
131                 mode = m_currentMode;
132         }
133         else if ((mode == FM_Mono) && (m_currentMode == FM_Simple)) {
134                 mode = FM_SimpleMono;
135         }
136
137         if (font_size == FONT_SIZE_UNSPECIFIED) {
138                 font_size = m_default_size[mode];
139         }
140
141         if ((font_size == m_lastSize) && (mode == m_lastMode)) {
142                 return m_lastFont;
143         }
144
145         if (m_font_cache[mode].find(font_size) == m_font_cache[mode].end()) {
146                 initFont(font_size, mode);
147         }
148
149         if (m_font_cache[mode].find(font_size) == m_font_cache[mode].end()) {
150                 return NULL;
151         }
152
153         m_lastSize = font_size;
154         m_lastMode = mode;
155         m_lastFont = m_font_cache[mode][font_size];
156
157         return m_font_cache[mode][font_size];
158 }
159
160 /******************************************************************************/
161 unsigned int FontEngine::getTextHeight(unsigned int font_size, FontMode mode)
162 {
163         irr::gui::IGUIFont* font = getFont(font_size, mode);
164
165         // use current skin font as fallback
166         if (font == NULL) {
167                 font = m_env->getSkin()->getFont();
168         }
169         FATAL_ERROR_IF(font == NULL, "Could not get skin font");
170
171         return font->getDimension(L"Some unimportant example String").Height;
172 }
173
174 /******************************************************************************/
175 unsigned int FontEngine::getTextWidth(const std::wstring& text,
176                 unsigned int font_size, FontMode mode)
177 {
178         irr::gui::IGUIFont* font = getFont(font_size, mode);
179
180         // use current skin font as fallback
181         if (font == NULL) {
182                 font = m_env->getSkin()->getFont();
183         }
184         FATAL_ERROR_IF(font == NULL, "Could not get font");
185
186         return font->getDimension(text.c_str()).Width;
187 }
188
189
190 /** get line height for a specific font (including empty room between lines) */
191 unsigned int FontEngine::getLineHeight(unsigned int font_size, FontMode mode)
192 {
193         irr::gui::IGUIFont* font = getFont(font_size, mode);
194
195         // use current skin font as fallback
196         if (font == NULL) {
197                 font = m_env->getSkin()->getFont();
198         }
199         FATAL_ERROR_IF(font == NULL, "Could not get font");
200
201         return font->getDimension(L"Some unimportant example String").Height
202                         + font->getKerningHeight();
203 }
204
205 /******************************************************************************/
206 unsigned int FontEngine::getDefaultFontSize()
207 {
208         return m_default_size[m_currentMode];
209 }
210
211 /******************************************************************************/
212 void FontEngine::readSettings()
213 {
214 #if USE_FREETYPE
215         if (g_settings->getBool("freetype")) {
216                 m_default_size[FM_Standard] = m_settings->getU16("font_size");
217                 m_default_size[FM_Fallback] = m_settings->getU16("fallback_font_size");
218                 m_default_size[FM_Mono]     = m_settings->getU16("mono_font_size");
219
220                 if (is_yes(gettext("needs_fallback_font"))) {
221                         m_currentMode = FM_Fallback;
222                 }
223                 else {
224                         m_currentMode = FM_Standard;
225                 }
226         }
227 #endif
228         m_default_size[FM_Simple]       = m_settings->getU16("font_size");
229         m_default_size[FM_SimpleMono]   = m_settings->getU16("mono_font_size");
230
231         cleanCache();
232         updateFontCache();
233         updateSkin();
234 }
235
236 /******************************************************************************/
237 void FontEngine::updateSkin()
238 {
239         gui::IGUIFont *font = getFont();
240
241         if (font)
242                 m_env->getSkin()->setFont(font);
243         else
244                 errorstream << "FontEngine: Default font file: " <<
245                                 "\n\t\"" << m_settings->get("font_path") << "\"" <<
246                                 "\n\trequired for current screen configuration was not found" <<
247                                 " or was invalid file format." <<
248                                 "\n\tUsing irrlicht default font." << std::endl;
249
250         // If we did fail to create a font our own make irrlicht find a default one
251         font = m_env->getSkin()->getFont();
252         FATAL_ERROR_IF(font == NULL, "Could not create/get font");
253
254         u32 text_height = font->getDimension(L"Hello, world!").Height;
255         infostream << "text_height=" << text_height << std::endl;
256 }
257
258 /******************************************************************************/
259 void FontEngine::updateFontCache()
260 {
261         /* the only font to be initialized is default one,
262          * all others are re-initialized on demand */
263         initFont(m_default_size[m_currentMode], m_currentMode);
264
265         /* reset font quick access */
266         m_lastMode = FM_Unspecified;
267         m_lastSize = 0;
268         m_lastFont = NULL;
269 }
270
271 /******************************************************************************/
272 void FontEngine::initFont(unsigned int basesize, FontMode mode)
273 {
274
275         std::string font_config_prefix;
276
277         if (mode == FM_Unspecified) {
278                 mode = m_currentMode;
279         }
280
281         switch (mode) {
282
283                 case FM_Standard:
284                         font_config_prefix = "";
285                         break;
286
287                 case FM_Fallback:
288                         font_config_prefix = "fallback_";
289                         break;
290
291                 case FM_Mono:
292                         font_config_prefix = "mono_";
293                         if (m_currentMode == FM_Simple)
294                                 mode = FM_SimpleMono;
295                         break;
296
297                 case FM_Simple: /* Fallthrough */
298                 case FM_SimpleMono: /* Fallthrough */
299                 default:
300                         font_config_prefix = "";
301
302         }
303
304         if (m_font_cache[mode].find(basesize) != m_font_cache[mode].end())
305                 return;
306
307         if ((mode == FM_Simple) || (mode == FM_SimpleMono)) {
308                 initSimpleFont(basesize, mode);
309                 return;
310         }
311 #if USE_FREETYPE
312         else {
313                 if (!is_yes(m_settings->get("freetype"))) {
314                         return;
315                 }
316                 u32 size = std::floor(RenderingEngine::getDisplayDensity() *
317                                 m_settings->getFloat("gui_scaling") * basesize);
318                 u32 font_shadow       = 0;
319                 u32 font_shadow_alpha = 0;
320
321                 try {
322                         font_shadow =
323                                         g_settings->getU16(font_config_prefix + "font_shadow");
324                 } catch (SettingNotFoundException&) {}
325                 try {
326                         font_shadow_alpha =
327                                         g_settings->getU16(font_config_prefix + "font_shadow_alpha");
328                 } catch (SettingNotFoundException&) {}
329
330                 std::string font_path = g_settings->get(font_config_prefix + "font_path");
331
332                 irr::gui::IGUIFont* font = gui::CGUITTFont::createTTFont(m_env,
333                                 font_path.c_str(), size, true, true, font_shadow,
334                                 font_shadow_alpha);
335
336                 if (font) {
337                         m_font_cache[mode][basesize] = font;
338                         return;
339                 }
340
341                 if (font_config_prefix == "mono_") {
342                         const std::string &mono_font_path = m_settings->getDefault("mono_font_path");
343
344                         if (font_path != mono_font_path) {
345                                 // try original mono font
346                                 errorstream << "FontEngine: failed to load custom mono "
347                                                 "font: " << font_path << ", trying to fall back to "
348                                                 "original mono font" << std::endl;
349
350                                 font = gui::CGUITTFont::createTTFont(m_env,
351                                         mono_font_path.c_str(), size, true, true,
352                                         font_shadow, font_shadow_alpha);
353
354                                 if (font) {
355                                         m_font_cache[mode][basesize] = font;
356                                         return;
357                                 }
358                         }
359                 } else {
360                         // try fallback font
361                         errorstream << "FontEngine: failed to load: " << font_path <<
362                                         ", trying to fall back to fallback font" << std::endl;
363
364                         font_path = g_settings->get(font_config_prefix + "fallback_font_path");
365
366                         font = gui::CGUITTFont::createTTFont(m_env,
367                                 font_path.c_str(), size, true, true, font_shadow,
368                                 font_shadow_alpha);
369
370                         if (font) {
371                                 m_font_cache[mode][basesize] = font;
372                                 return;
373                         }
374
375                         const std::string &fallback_font_path = m_settings->getDefault("fallback_font_path");
376
377                         if (font_path != fallback_font_path) {
378                                 // try original fallback font
379                                 errorstream << "FontEngine: failed to load custom fallback "
380                                                 "font: " << font_path << ", trying to fall back to "
381                                                 "original fallback font" << std::endl;
382
383                                 font = gui::CGUITTFont::createTTFont(m_env,
384                                         fallback_font_path.c_str(), size, true, true,
385                                         font_shadow, font_shadow_alpha);
386
387                                 if (font) {
388                                         m_font_cache[mode][basesize] = font;
389                                         return;
390                                 }
391                         }
392                 }
393
394                 // give up
395                 errorstream << "FontEngine: failed to load freetype font: "
396                                 << font_path << std::endl;
397                 errorstream << "minetest can not continue without a valid font. "
398                                 "Please correct the 'font_path' setting or install the font "
399                                 "file in the proper location" << std::endl;
400                 abort();
401         }
402 #endif
403 }
404
405 /** initialize a font without freetype */
406 void FontEngine::initSimpleFont(unsigned int basesize, FontMode mode)
407 {
408         assert(mode == FM_Simple || mode == FM_SimpleMono); // pre-condition
409
410         std::string font_path;
411         if (mode == FM_Simple) {
412                 font_path = m_settings->get("font_path");
413         } else {
414                 font_path = m_settings->get("mono_font_path");
415         }
416         std::string basename = font_path;
417         std::string ending = font_path.substr(font_path.length() -4);
418
419         if (ending == ".ttf") {
420                 errorstream << "FontEngine: Not trying to open \"" << font_path
421                                 << "\" which seems to be a truetype font." << std::endl;
422                 return;
423         }
424
425         if ((ending == ".xml") || (ending == ".png")) {
426                 basename = font_path.substr(0,font_path.length()-4);
427         }
428
429         if (basesize == FONT_SIZE_UNSPECIFIED)
430                 basesize = DEFAULT_FONT_SIZE;
431
432         u32 size = std::floor(
433                         RenderingEngine::getDisplayDensity() *
434                         m_settings->getFloat("gui_scaling") *
435                         basesize);
436
437         irr::gui::IGUIFont* font = NULL;
438
439         for(unsigned int offset = 0; offset < MAX_FONT_SIZE_OFFSET; offset++) {
440
441                 // try opening positive offset
442                 std::stringstream fontsize_plus_png;
443                 fontsize_plus_png << basename << "_" << (size + offset) << ".png";
444
445                 if (fs::PathExists(fontsize_plus_png.str())) {
446                         font = m_env->getFont(fontsize_plus_png.str().c_str());
447
448                         if (font) {
449                                 verbosestream << "FontEngine: found font: " << fontsize_plus_png.str() << std::endl;
450                                 break;
451                         }
452                 }
453
454                 std::stringstream fontsize_plus_xml;
455                 fontsize_plus_xml << basename << "_" << (size + offset) << ".xml";
456
457                 if (fs::PathExists(fontsize_plus_xml.str())) {
458                         font = m_env->getFont(fontsize_plus_xml.str().c_str());
459
460                         if (font) {
461                                 verbosestream << "FontEngine: found font: " << fontsize_plus_xml.str() << std::endl;
462                                 break;
463                         }
464                 }
465
466                 // try negative offset
467                 std::stringstream fontsize_minus_png;
468                 fontsize_minus_png << basename << "_" << (size - offset) << ".png";
469
470                 if (fs::PathExists(fontsize_minus_png.str())) {
471                         font = m_env->getFont(fontsize_minus_png.str().c_str());
472
473                         if (font) {
474                                 verbosestream << "FontEngine: found font: " << fontsize_minus_png.str() << std::endl;
475                                 break;
476                         }
477                 }
478
479                 std::stringstream fontsize_minus_xml;
480                 fontsize_minus_xml << basename << "_" << (size - offset) << ".xml";
481
482                 if (fs::PathExists(fontsize_minus_xml.str())) {
483                         font = m_env->getFont(fontsize_minus_xml.str().c_str());
484
485                         if (font) {
486                                 verbosestream << "FontEngine: found font: " << fontsize_minus_xml.str() << std::endl;
487                                 break;
488                         }
489                 }
490         }
491
492         // try name direct
493         if (font == NULL) {
494                 if (fs::PathExists(font_path)) {
495                         font = m_env->getFont(font_path.c_str());
496                         if (font)
497                                 verbosestream << "FontEngine: found font: " << font_path << std::endl;
498                 }
499         }
500
501         if (font) {
502                 font->grab();
503                 m_font_cache[mode][basesize] = font;
504         }
505 }