]> git.lizzy.rs Git - minetest.git/blob - src/client/fontengine.cpp
Settings: Purge getDefault, clean FontEngine
[minetest.git] / src / client / 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 #include "gettext.h"
27
28 #if USE_FREETYPE
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(gui::IGUIEnvironment* env) :
46         m_env(env)
47 {
48
49         for (u32 &i : m_default_size) {
50                 i = (FontMode) FONT_SIZE_UNSPECIFIED;
51         }
52
53         assert(g_settings != NULL); // pre-condition
54         assert(m_env != NULL); // pre-condition
55         assert(m_env->getSkin() != NULL); // pre-condition
56
57         readSettings();
58
59         if (m_currentMode == FM_Standard) {
60                 g_settings->registerChangedCallback("font_size", font_setting_changed, NULL);
61                 g_settings->registerChangedCallback("font_bold", font_setting_changed, NULL);
62                 g_settings->registerChangedCallback("font_italic", font_setting_changed, NULL);
63                 g_settings->registerChangedCallback("font_path", font_setting_changed, NULL);
64                 g_settings->registerChangedCallback("font_path_bold", font_setting_changed, NULL);
65                 g_settings->registerChangedCallback("font_path_italic", font_setting_changed, NULL);
66                 g_settings->registerChangedCallback("font_path_bolditalic", font_setting_changed, NULL);
67                 g_settings->registerChangedCallback("font_shadow", font_setting_changed, NULL);
68                 g_settings->registerChangedCallback("font_shadow_alpha", font_setting_changed, NULL);
69         }
70         else if (m_currentMode == FM_Fallback) {
71                 g_settings->registerChangedCallback("fallback_font_size", font_setting_changed, NULL);
72                 g_settings->registerChangedCallback("fallback_font_path", font_setting_changed, NULL);
73                 g_settings->registerChangedCallback("fallback_font_shadow", font_setting_changed, NULL);
74                 g_settings->registerChangedCallback("fallback_font_shadow_alpha", font_setting_changed, NULL);
75         }
76
77         g_settings->registerChangedCallback("mono_font_path", font_setting_changed, NULL);
78         g_settings->registerChangedCallback("mono_font_size", font_setting_changed, NULL);
79         g_settings->registerChangedCallback("screen_dpi", font_setting_changed, NULL);
80         g_settings->registerChangedCallback("gui_scaling", font_setting_changed, NULL);
81 }
82
83 /******************************************************************************/
84 FontEngine::~FontEngine()
85 {
86         cleanCache();
87 }
88
89 /******************************************************************************/
90 void FontEngine::cleanCache()
91 {
92         for (auto &font_cache_it : m_font_cache) {
93
94                 for (auto &font_it : font_cache_it) {
95                         font_it.second->drop();
96                         font_it.second = NULL;
97                 }
98                 font_cache_it.clear();
99         }
100 }
101
102 /******************************************************************************/
103 irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec)
104 {
105         if (spec.mode == FM_Unspecified) {
106                 spec.mode = m_currentMode;
107         } else if (m_currentMode == FM_Simple) {
108                 // Freetype disabled -> Force simple mode
109                 spec.mode = (spec.mode == FM_Mono ||
110                                 spec.mode == FM_SimpleMono) ?
111                                 FM_SimpleMono : FM_Simple;
112                 // Support for those could be added, but who cares?
113                 spec.bold = false;
114                 spec.italic = false;
115         }
116
117         // Fallback to default size
118         if (spec.size == FONT_SIZE_UNSPECIFIED)
119                 spec.size = m_default_size[spec.mode];
120
121         const auto &cache = m_font_cache[spec.getHash()];
122         auto it = cache.find(spec.size);
123         if (it != cache.end())
124                 return it->second;
125
126         // Font does not yet exist
127         gui::IGUIFont *font = nullptr;
128         if (spec.mode == FM_Simple || spec.mode == FM_SimpleMono)
129                 font = initSimpleFont(spec);
130         else
131                 font = initFont(spec);
132
133         m_font_cache[spec.getHash()][spec.size] = font;
134
135         return font;
136 }
137
138 /******************************************************************************/
139 unsigned int FontEngine::getTextHeight(const FontSpec &spec)
140 {
141         irr::gui::IGUIFont *font = getFont(spec);
142
143         // use current skin font as fallback
144         if (font == NULL) {
145                 font = m_env->getSkin()->getFont();
146         }
147         FATAL_ERROR_IF(font == NULL, "Could not get skin font");
148
149         return font->getDimension(L"Some unimportant example String").Height;
150 }
151
152 /******************************************************************************/
153 unsigned int FontEngine::getTextWidth(const std::wstring &text, const FontSpec &spec)
154 {
155         irr::gui::IGUIFont *font = getFont(spec);
156
157         // use current skin font as fallback
158         if (font == NULL) {
159                 font = m_env->getSkin()->getFont();
160         }
161         FATAL_ERROR_IF(font == NULL, "Could not get font");
162
163         return font->getDimension(text.c_str()).Width;
164 }
165
166
167 /** get line height for a specific font (including empty room between lines) */
168 unsigned int FontEngine::getLineHeight(const FontSpec &spec)
169 {
170         irr::gui::IGUIFont *font = getFont(spec);
171
172         // use current skin font as fallback
173         if (font == NULL) {
174                 font = m_env->getSkin()->getFont();
175         }
176         FATAL_ERROR_IF(font == NULL, "Could not get font");
177
178         return font->getDimension(L"Some unimportant example String").Height
179                         + font->getKerningHeight();
180 }
181
182 /******************************************************************************/
183 unsigned int FontEngine::getDefaultFontSize()
184 {
185         return m_default_size[m_currentMode];
186 }
187
188 unsigned int FontEngine::getFontSize(FontMode mode)
189 {
190         if (m_currentMode == FM_Simple) {
191                 if (mode == FM_Mono || mode == FM_SimpleMono)
192                         return m_default_size[FM_SimpleMono];
193                 else
194                         return m_default_size[FM_Simple];
195         }
196
197         if (mode == FM_Unspecified)
198                 return m_default_size[FM_Standard];
199
200         return m_default_size[mode];
201 }
202
203 /******************************************************************************/
204 void FontEngine::readSettings()
205 {
206         if (USE_FREETYPE && g_settings->getBool("freetype")) {
207                 m_default_size[FM_Standard] = g_settings->getU16("font_size");
208                 m_default_size[FM_Fallback] = g_settings->getU16("fallback_font_size");
209                 m_default_size[FM_Mono]     = g_settings->getU16("mono_font_size");
210
211                 /*~ DO NOT TRANSLATE THIS LITERALLY!
212                 This is a special string. Put either "no" or "yes"
213                 into the translation field (literally).
214                 Choose "yes" if the language requires use of the fallback
215                 font, "no" otherwise.
216                 The fallback font is (normally) required for languages with
217                 non-Latin script, like Chinese.
218                 When in doubt, test your translation. */
219                 m_currentMode = is_yes(gettext("needs_fallback_font")) ?
220                                 FM_Fallback : FM_Standard;
221
222                 m_default_bold = g_settings->getBool("font_bold");
223                 m_default_italic = g_settings->getBool("font_italic");
224
225         } else {
226                 m_currentMode = FM_Simple;
227         }
228
229         m_default_size[FM_Simple]       = g_settings->getU16("font_size");
230         m_default_size[FM_SimpleMono]   = g_settings->getU16("mono_font_size");
231
232         cleanCache();
233         updateFontCache();
234         updateSkin();
235 }
236
237 /******************************************************************************/
238 void FontEngine::updateSkin()
239 {
240         gui::IGUIFont *font = getFont();
241
242         if (font)
243                 m_env->getSkin()->setFont(font);
244         else
245                 errorstream << "FontEngine: Default font file: " <<
246                                 "\n\t\"" << g_settings->get("font_path") << "\"" <<
247                                 "\n\trequired for current screen configuration was not found" <<
248                                 " or was invalid file format." <<
249                                 "\n\tUsing irrlicht default font." << std::endl;
250
251         // If we did fail to create a font our own make irrlicht find a default one
252         font = m_env->getSkin()->getFont();
253         FATAL_ERROR_IF(font == NULL, "Could not create/get font");
254
255         u32 text_height = font->getDimension(L"Hello, world!").Height;
256         infostream << "FontEngine: measured text_height=" << text_height << std::endl;
257 }
258
259 /******************************************************************************/
260 void FontEngine::updateFontCache()
261 {
262         /* the only font to be initialized is default one,
263          * all others are re-initialized on demand */
264         getFont(FONT_SIZE_UNSPECIFIED, FM_Unspecified);
265 }
266
267 /******************************************************************************/
268 gui::IGUIFont *FontEngine::initFont(const FontSpec &spec)
269 {
270         assert(spec.mode != FM_Unspecified);
271         assert(spec.size != FONT_SIZE_UNSPECIFIED);
272
273         std::string setting_prefix = "";
274
275         switch (spec.mode) {
276                 case FM_Fallback:
277                         setting_prefix = "fallback_";
278                         break;
279                 case FM_Mono:
280                 case FM_SimpleMono:
281                         setting_prefix = "mono_";
282                         break;
283                 default:
284                         break;
285         }
286
287         std::string setting_suffix = "";
288         if (spec.bold)
289                 setting_suffix.append("_bold");
290         if (spec.italic)
291                 setting_suffix.append("_italic");
292
293         u32 size = std::floor(RenderingEngine::getDisplayDensity() *
294                         g_settings->getFloat("gui_scaling") * spec.size);
295
296         if (size == 0) {
297                 errorstream << "FontEngine: attempt to use font size 0" << std::endl;
298                 errorstream << "  display density: " << RenderingEngine::getDisplayDensity() << std::endl;
299                 abort();
300         }
301
302         u16 font_shadow       = 0;
303         u16 font_shadow_alpha = 0;
304         g_settings->getU16NoEx(setting_prefix + "font_shadow", font_shadow);
305         g_settings->getU16NoEx(setting_prefix + "font_shadow_alpha",
306                         font_shadow_alpha);
307
308         std::string wanted_font_path;
309         wanted_font_path = g_settings->get(setting_prefix + "font_path" + setting_suffix);
310
311         std::string fallback_settings[] = {
312                 wanted_font_path,
313                 g_settings->get("fallback_font_path"),
314                 Settings::getLayer(SL_DEFAULTS)->get(setting_prefix + "font_path")
315         };
316
317 #if USE_FREETYPE
318         for (const std::string &font_path : fallback_settings) {
319                 irr::gui::IGUIFont *font = gui::CGUITTFont::createTTFont(m_env,
320                                 font_path.c_str(), size, true, true, font_shadow,
321                                 font_shadow_alpha);
322
323                 if (font)
324                         return font;
325
326                 errorstream << "FontEngine: Cannot load '" << font_path <<
327                                 "'. Trying to fall back to another path." << std::endl;
328         }
329
330
331         // give up
332         errorstream << "minetest can not continue without a valid font. "
333                         "Please correct the 'font_path' setting or install the font "
334                         "file in the proper location" << std::endl;
335 #else
336         errorstream << "FontEngine: Tried to load freetype fonts but Minetest was"
337                         " not compiled with that library." << std::endl;
338 #endif
339         abort();
340 }
341
342 /** initialize a font without freetype */
343 gui::IGUIFont *FontEngine::initSimpleFont(const FontSpec &spec)
344 {
345         assert(spec.mode == FM_Simple || spec.mode == FM_SimpleMono);
346         assert(spec.size != FONT_SIZE_UNSPECIFIED);
347
348         const std::string &font_path = g_settings->get(
349                         (spec.mode == FM_SimpleMono) ? "mono_font_path" : "font_path");
350
351         size_t pos_dot = font_path.find_last_of('.');
352         std::string basename = font_path;
353         std::string ending = lowercase(font_path.substr(pos_dot));
354
355         if (ending == ".ttf") {
356                 errorstream << "FontEngine: Found font \"" << font_path
357                                 << "\" but freetype is not available." << std::endl;
358                 return nullptr;
359         }
360
361         if (ending == ".xml" || ending == ".png")
362                 basename = font_path.substr(0, pos_dot);
363
364         u32 size = std::floor(
365                         RenderingEngine::getDisplayDensity() *
366                         g_settings->getFloat("gui_scaling") *
367                         spec.size);
368
369         irr::gui::IGUIFont *font = nullptr;
370         std::string font_extensions[] = { ".png", ".xml" };
371
372         // Find nearest matching font scale
373         // Does a "zig-zag motion" (positibe/negative), from 0 to MAX_FONT_SIZE_OFFSET
374         for (s32 zoffset = 0; zoffset < MAX_FONT_SIZE_OFFSET * 2; zoffset++) {
375                 std::stringstream path;
376
377                 // LSB to sign
378                 s32 sign = (zoffset & 1) ? -1 : 1;
379                 s32 offset = zoffset >> 1;
380
381                 for (const std::string &ext : font_extensions) {
382                         path.str(""); // Clear
383                         path << basename << "_" << (size + offset * sign) << ext;
384
385                         if (!fs::PathExists(path.str()))
386                                 continue;
387
388                         font = m_env->getFont(path.str().c_str());
389
390                         if (font) {
391                                 verbosestream << "FontEngine: found font: " << path.str() << std::endl;
392                                 break;
393                         }
394                 }
395
396                 if (font)
397                         break;
398         }
399
400         // try name direct
401         if (font == NULL) {
402                 if (fs::PathExists(font_path)) {
403                         font = m_env->getFont(font_path.c_str());
404                         if (font)
405                                 verbosestream << "FontEngine: found font: " << font_path << std::endl;
406                 }
407         }
408
409         return font;
410 }