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