Translated using Weblate (Chinese (Simplified))
[oweals/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(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 /******************************************************************************/
190 void FontEngine::readSettings()
191 {
192         if (USE_FREETYPE && g_settings->getBool("freetype")) {
193                 m_default_size[FM_Standard] = m_settings->getU16("font_size");
194                 m_default_size[FM_Fallback] = m_settings->getU16("fallback_font_size");
195                 m_default_size[FM_Mono]     = m_settings->getU16("mono_font_size");
196
197                 /*~ DO NOT TRANSLATE THIS LITERALLY!
198                 This is a special string. Put either "no" or "yes"
199                 into the translation field (literally).
200                 Choose "yes" if the language requires use of the fallback
201                 font, "no" otherwise.
202                 The fallback font is (normally) required for languages with
203                 non-Latin script, like Chinese.
204                 When in doubt, test your translation. */
205                 m_currentMode = is_yes(gettext("needs_fallback_font")) ?
206                                 FM_Fallback : FM_Standard;
207
208                 m_default_bold = m_settings->getBool("font_bold");
209                 m_default_italic = m_settings->getBool("font_italic");
210
211         } else {
212                 m_currentMode = FM_Simple;
213         }
214
215         m_default_size[FM_Simple]       = m_settings->getU16("font_size");
216         m_default_size[FM_SimpleMono]   = m_settings->getU16("mono_font_size");
217
218         cleanCache();
219         updateFontCache();
220         updateSkin();
221 }
222
223 /******************************************************************************/
224 void FontEngine::updateSkin()
225 {
226         gui::IGUIFont *font = getFont();
227
228         if (font)
229                 m_env->getSkin()->setFont(font);
230         else
231                 errorstream << "FontEngine: Default font file: " <<
232                                 "\n\t\"" << m_settings->get("font_path") << "\"" <<
233                                 "\n\trequired for current screen configuration was not found" <<
234                                 " or was invalid file format." <<
235                                 "\n\tUsing irrlicht default font." << std::endl;
236
237         // If we did fail to create a font our own make irrlicht find a default one
238         font = m_env->getSkin()->getFont();
239         FATAL_ERROR_IF(font == NULL, "Could not create/get font");
240
241         u32 text_height = font->getDimension(L"Hello, world!").Height;
242         infostream << "FontEngine: measured text_height=" << text_height << std::endl;
243 }
244
245 /******************************************************************************/
246 void FontEngine::updateFontCache()
247 {
248         /* the only font to be initialized is default one,
249          * all others are re-initialized on demand */
250         getFont(FONT_SIZE_UNSPECIFIED, FM_Unspecified);
251 }
252
253 /******************************************************************************/
254 gui::IGUIFont *FontEngine::initFont(const FontSpec &spec)
255 {
256         assert(spec.mode != FM_Unspecified);
257         assert(spec.size != FONT_SIZE_UNSPECIFIED);
258
259         std::string setting_prefix = "";
260
261         switch (spec.mode) {
262                 case FM_Fallback:
263                         setting_prefix = "fallback_";
264                         break;
265                 case FM_Mono:
266                 case FM_SimpleMono:
267                         setting_prefix = "mono_";
268                         break;
269                 default:
270                         break;
271         }
272
273         std::string setting_suffix = "";
274         if (spec.bold)
275                 setting_suffix.append("_bold");
276         if (spec.italic)
277                 setting_suffix.append("_italic");
278
279         u32 size = std::floor(RenderingEngine::getDisplayDensity() *
280                         m_settings->getFloat("gui_scaling") * spec.size);
281
282         if (size == 0) {
283                 errorstream << "FontEngine: attempt to use font size 0" << std::endl;
284                 errorstream << "  display density: " << RenderingEngine::getDisplayDensity() << std::endl;
285                 abort();
286         }
287
288         u16 font_shadow       = 0;
289         u16 font_shadow_alpha = 0;
290         g_settings->getU16NoEx(setting_prefix + "font_shadow", font_shadow);
291         g_settings->getU16NoEx(setting_prefix + "font_shadow_alpha",
292                         font_shadow_alpha);
293
294         std::string wanted_font_path;
295         wanted_font_path = g_settings->get(setting_prefix + "font_path" + setting_suffix);
296
297         std::string fallback_settings[] = {
298                 wanted_font_path,
299                 m_settings->get("fallback_font_path"),
300                 m_settings->getDefault(setting_prefix + "font_path")
301         };
302
303 #if USE_FREETYPE
304         for (const std::string &font_path : fallback_settings) {
305                 irr::gui::IGUIFont *font = gui::CGUITTFont::createTTFont(m_env,
306                                 font_path.c_str(), size, true, true, font_shadow,
307                                 font_shadow_alpha);
308
309                 if (font)
310                         return font;
311
312                 errorstream << "FontEngine: Cannot load '" << font_path <<
313                                 "'. Trying to fall back to another path." << std::endl;
314         }
315
316
317         // give up
318         errorstream << "minetest can not continue without a valid font. "
319                         "Please correct the 'font_path' setting or install the font "
320                         "file in the proper location" << std::endl;
321 #else
322         errorstream << "FontEngine: Tried to load freetype fonts but Minetest was"
323                         " not compiled with that library." << std::endl;
324 #endif
325         abort();
326 }
327
328 /** initialize a font without freetype */
329 gui::IGUIFont *FontEngine::initSimpleFont(const FontSpec &spec)
330 {
331         assert(spec.mode == FM_Simple || spec.mode == FM_SimpleMono);
332         assert(spec.size != FONT_SIZE_UNSPECIFIED);
333
334         const std::string &font_path = m_settings->get(
335                         (spec.mode == FM_SimpleMono) ? "mono_font_path" : "font_path");
336
337         size_t pos_dot = font_path.find_last_of('.');
338         std::string basename = font_path;
339         std::string ending = lowercase(font_path.substr(pos_dot));
340
341         if (ending == ".ttf") {
342                 errorstream << "FontEngine: Found font \"" << font_path
343                                 << "\" but freetype is not available." << std::endl;
344                 return nullptr;
345         }
346
347         if (ending == ".xml" || ending == ".png")
348                 basename = font_path.substr(0, pos_dot);
349
350         u32 size = std::floor(
351                         RenderingEngine::getDisplayDensity() *
352                         m_settings->getFloat("gui_scaling") *
353                         spec.size);
354
355         irr::gui::IGUIFont *font = nullptr;
356         std::string font_extensions[] = { ".png", ".xml" };
357
358         // Find nearest matching font scale
359         // Does a "zig-zag motion" (positibe/negative), from 0 to MAX_FONT_SIZE_OFFSET
360         for (s32 zoffset = 0; zoffset < MAX_FONT_SIZE_OFFSET * 2; zoffset++) {
361                 std::stringstream path;
362
363                 // LSB to sign
364                 s32 sign = (zoffset & 1) ? -1 : 1;
365                 s32 offset = zoffset >> 1;
366
367                 for (const std::string &ext : font_extensions) {
368                         path.str(""); // Clear
369                         path << basename << "_" << (size + offset * sign) << ext;
370
371                         if (!fs::PathExists(path.str()))
372                                 continue;
373
374                         font = m_env->getFont(path.str().c_str());
375
376                         if (font) {
377                                 verbosestream << "FontEngine: found font: " << path.str() << std::endl;
378                                 break;
379                         }
380                 }
381
382                 if (font)
383                         break;
384         }
385
386         // try name direct
387         if (font == NULL) {
388                 if (fs::PathExists(font_path)) {
389                         font = m_env->getFont(font_path.c_str());
390                         if (font)
391                                 verbosestream << "FontEngine: found font: " << font_path << std::endl;
392                 }
393         }
394
395         return font;
396 }