]> git.lizzy.rs Git - dragonfireclient.git/blob - src/fontengine.cpp
Implement proper font handling
[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 #include "fontengine.h"
20 #include "log.h"
21 #include "main.h"
22 #include "config.h"
23 #include "porting.h"
24 #include "constants.h"
25 #include "filesys.h"
26
27 #if USE_FREETYPE
28 #include "gettext.h"
29 #include "xCGUITTFont.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* glb_fontengine = NULL;
37
38 /** callback to be used on change of font size setting */
39 static void font_setting_changed(const std::string) {
40         glb_fontengine->readSettings();
41 }
42
43 /******************************************************************************/
44 FontEngine::FontEngine(Settings* main_settings, gui::IGUIEnvironment* env) :
45         m_settings(main_settings),
46         m_env(env),
47         m_font_cache(),
48         m_default_size(),
49         m_currentMode(FM_Standard),
50         m_lastMode(),
51         m_lastSize(0),
52         m_lastFont(NULL)
53 {
54
55         for ( unsigned int i = 0; i < FM_MaxMode; i++) {
56                 m_default_size[i] = (FontMode) FONT_SIZE_UNSPECIFIED;
57         }
58
59         assert(m_settings != NULL);
60         assert(m_env != NULL);
61         assert(m_env->getSkin() != NULL);
62
63         m_currentMode = FM_Simple;
64
65 #if USE_FREETYPE
66         if (g_settings->getBool("freetype")) {
67                 m_default_size[FM_Standard] = m_settings->getU16("font_size");
68                 m_default_size[FM_Fallback] = m_settings->getU16("fallback_font_size");
69                 m_default_size[FM_Mono]     = m_settings->getU16("mono_font_size");
70
71                 if (is_yes(gettext("needs_fallback_font"))) {
72                         m_currentMode = FM_Fallback;
73                 }
74                 else {
75                         m_currentMode = FM_Standard;
76                 }
77         }
78
79         // having freetype but not using it is quite a strange case so we need to do
80         // special handling for it
81         if (m_currentMode == FM_Simple) {
82                 std::stringstream fontsize;
83                 fontsize << DEFAULT_FONT_SIZE;
84                 m_settings->setDefault("font_size", fontsize.str());
85                 m_settings->setDefault("mono_font_size", fontsize.str());
86         }
87 #endif
88
89         m_default_size[FM_Simple]       = m_settings->getU16("font_size");
90         m_default_size[FM_SimpleMono]   = m_settings->getU16("mono_font_size");
91
92         updateSkin();
93
94         if (m_currentMode == FM_Standard) {
95                 m_settings->registerChangedCallback("font_size", font_setting_changed);
96                 m_settings->registerChangedCallback("font_path", font_setting_changed);
97                 m_settings->registerChangedCallback("font_shadow", font_setting_changed);
98                 m_settings->registerChangedCallback("font_shadow_alpha", font_setting_changed);
99         }
100         else if (m_currentMode == FM_Fallback) {
101                 m_settings->registerChangedCallback("fallback_font_size", font_setting_changed);
102                 m_settings->registerChangedCallback("fallback_font_path", font_setting_changed);
103                 m_settings->registerChangedCallback("fallback_font_shadow", font_setting_changed);
104                 m_settings->registerChangedCallback("fallback_font_shadow_alpha", font_setting_changed);
105         }
106
107         m_settings->registerChangedCallback("mono_font_path", font_setting_changed);
108         m_settings->registerChangedCallback("mono_font_size", font_setting_changed);
109         m_settings->registerChangedCallback("screen_dpi", font_setting_changed);
110         m_settings->registerChangedCallback("gui_scaling", font_setting_changed);
111 }
112
113 /******************************************************************************/
114 FontEngine::~FontEngine()
115 {
116         cleanCache();
117 }
118
119 /******************************************************************************/
120 void FontEngine::cleanCache()
121 {
122         for ( unsigned int i = 0; i < FM_MaxMode; i++) {
123
124                 for (std::map<unsigned int, irr::gui::IGUIFont*>::iterator iter
125                                 = m_font_cache[i].begin();
126                                 iter != m_font_cache[i].end(); iter++) {
127                         iter->second->drop();
128                         iter->second = NULL;
129                 }
130                 m_font_cache[i].clear();
131         }
132 }
133
134 /******************************************************************************/
135 irr::gui::IGUIFont* FontEngine::getFont(unsigned int font_size, FontMode mode)
136 {
137         if (mode == FM_Unspecified) {
138                 mode = m_currentMode;
139         }
140         else if ((mode == FM_Mono) && (m_currentMode == FM_Simple)) {
141                 mode = FM_SimpleMono;
142         }
143
144         if (font_size == FONT_SIZE_UNSPECIFIED) {
145                 font_size = m_default_size[mode];
146         }
147
148         if ((font_size == m_lastSize) && (mode == m_lastMode)) {
149                 return m_lastFont;
150         }
151
152         if (m_font_cache[mode].find(font_size) == m_font_cache[mode].end()) {
153                 initFont(font_size, mode);
154         }
155
156         if (m_font_cache[mode].find(font_size) == m_font_cache[mode].end()) {
157                 return NULL;
158         }
159
160         m_lastSize = font_size;
161         m_lastMode = mode;
162         m_lastFont = m_font_cache[mode][font_size];
163
164         return m_font_cache[mode][font_size];
165 }
166
167 /******************************************************************************/
168 unsigned int FontEngine::getTextHeight(unsigned int font_size, FontMode mode)
169 {
170         irr::gui::IGUIFont* font = getFont(font_size, mode);
171
172         // use current skin font as fallback
173         if (font == NULL) {
174                 font = m_env->getSkin()->getFont();
175         }
176         assert(font != NULL);
177
178         return font->getDimension(L"Some unimportant example String").Height;
179 }
180
181 /******************************************************************************/
182 unsigned int FontEngine::getTextWidth(const std::wstring& text,
183                 unsigned int font_size, FontMode mode)
184 {
185         irr::gui::IGUIFont* font = getFont(font_size, mode);
186
187         // use current skin font as fallback
188         if (font == NULL) {
189                 font = m_env->getSkin()->getFont();
190         }
191         assert(font != NULL);
192
193         return font->getDimension(text.c_str()).Width;
194 }
195
196
197 /** get line height for a specific font (including empty room between lines) */
198 unsigned int FontEngine::getLineHeight(unsigned int font_size, FontMode mode)
199 {
200         irr::gui::IGUIFont* font = getFont(font_size, mode);
201
202         // use current skin font as fallback
203         if (font == NULL) {
204                 font = m_env->getSkin()->getFont();
205         }
206         assert(font != NULL);
207
208         return font->getDimension(L"Some unimportant example String").Height
209                         + font->getKerningHeight();
210 }
211
212 /******************************************************************************/
213 unsigned int FontEngine::getDefaultFontSize()
214 {
215         return m_default_size[m_currentMode];
216 }
217
218 /******************************************************************************/
219 void FontEngine::readSettings()
220 {
221 #if USE_FREETYPE
222         if (g_settings->getBool("freetype")) {
223                 m_default_size[FM_Standard] = m_settings->getU16("font_size");
224                 m_default_size[FM_Fallback] = m_settings->getU16("fallback_font_size");
225                 m_default_size[FM_Mono]     = m_settings->getU16("mono_font_size");
226
227                 if (is_yes(gettext("needs_fallback_font"))) {
228                         m_currentMode = FM_Fallback;
229                 }
230                 else {
231                         m_currentMode = FM_Standard;
232                 }
233         }
234 #endif
235         m_default_size[FM_Simple]       = m_settings->getU16("font_size");
236         m_default_size[FM_SimpleMono]   = m_settings->getU16("mono_font_size");
237
238         cleanCache();
239         updateFontCache();
240         updateSkin();
241 }
242
243 /******************************************************************************/
244 void FontEngine::updateSkin()
245 {
246         gui::IGUIFont *font = getFont();
247
248         if (font)
249                 m_env->getSkin()->setFont(font);
250         else
251                 errorstream << "FontEngine: Default font file: " <<
252                                 "\n\t\"" << m_settings->get("font_path") << "\"" <<
253                                 "\n\trequired for current screen configuration was not found" <<
254                                 " or was invalid file format." <<
255                                 "\n\tUsing irrlicht default font." << std::endl;
256
257         // If we did fail to create a font our own make irrlicht find a default one
258         font = m_env->getSkin()->getFont();
259         assert(font);
260
261         u32 text_height = font->getDimension(L"Hello, world!").Height;
262         infostream << "text_height=" << text_height << std::endl;
263 }
264
265 /******************************************************************************/
266 void FontEngine::updateFontCache()
267 {
268         /* the only font to be initialized is default one,
269          * all others are re-initialized on demand */
270         initFont(m_default_size[m_currentMode], m_currentMode);
271
272         /* reset font quick access */
273         m_lastMode = FM_Unspecified;
274         m_lastSize = 0;
275         m_lastFont = NULL;
276 }
277
278 /******************************************************************************/
279 void FontEngine::initFont(unsigned int basesize, FontMode mode)
280 {
281
282         std::string font_config_prefix;
283
284         if (mode == FM_Unspecified) {
285                 mode = m_currentMode;
286         }
287
288         switch (mode) {
289
290                 case FM_Standard:
291                         font_config_prefix = "";
292                         break;
293
294                 case FM_Fallback:
295                         font_config_prefix = "fallback_";
296                         break;
297
298                 case FM_Mono:
299                         font_config_prefix = "mono_";
300                         if (m_currentMode == FM_Simple)
301                                 mode = FM_SimpleMono;
302                         break;
303
304                 case FM_Simple: /* Fallthrough */
305                 case FM_SimpleMono: /* Fallthrough */
306                 default:
307                         font_config_prefix = "";
308
309         }
310
311         if (m_font_cache[mode].find(basesize) != m_font_cache[mode].end())
312                 return;
313
314         if ((mode == FM_Simple) || (mode == FM_SimpleMono)) {
315                 initSimpleFont(basesize, mode);
316                 return;
317         }
318 #if USE_FREETYPE
319         else {
320                 if (! is_yes(m_settings->get("freetype"))) {
321                         return;
322                 }
323                 unsigned int size = floor(
324                                 porting::getDisplayDensity() *
325                                 m_settings->getFloat("gui_scaling") *
326                                 basesize);
327                 u32 font_shadow       = 0;
328                 u32 font_shadow_alpha = 0;
329
330                 try {
331                         font_shadow =
332                                         g_settings->getU16(font_config_prefix + "font_shadow");
333                 } catch (SettingNotFoundException&) {}
334                 try {
335                         font_shadow_alpha =
336                                         g_settings->getU16(font_config_prefix + "font_shadow_alpha");
337                 } catch (SettingNotFoundException&) {}
338
339                 std::string font_path = g_settings->get(font_config_prefix + "font_path");
340
341                 if (font_path.substr(font_path.length() -4) != ".ttf") {
342                         errorstream << "FontEngine: \"" << font_path
343                                         << "\" doesn't seem to be a ttf File." << std::endl;
344                         return;
345                 }
346
347                 irr::gui::IGUIFont* font = gui::CGUITTFont::createTTFont(m_env,
348                                 font_path.c_str(), size, true, true, font_shadow,
349                                 font_shadow_alpha);
350
351                 if (font != NULL) {
352                         font->grab();
353                         m_font_cache[mode][basesize] = font;
354                 }
355                 else {
356                         errorstream << "FontEngine: failed to load freetype font: "
357                                         << font_path << std::endl;
358                 }
359         }
360 #endif
361 }
362
363 /** initialize a font without freetype */
364 void FontEngine::initSimpleFont(unsigned int basesize, FontMode mode)
365 {
366         assert((mode == FM_Simple) || (mode == FM_SimpleMono));
367
368         std::string font_path = "";
369         if (mode == FM_Simple) {
370                 font_path = m_settings->get("font_path");
371         } else {
372                 font_path = m_settings->get("mono_font_path");
373         }
374         std::string basename = font_path;
375         std::string ending = font_path.substr(font_path.length() -4);
376
377         if (ending == ".ttf") {
378                 errorstream << "FontEngine: Not trying to open \"" << font_path
379                                 << "\" which seems to be a truetype font." << std::endl;
380                 return;
381         }
382
383         if ((ending == ".xml") || ( ending == ".png")) {
384                 basename = font_path.substr(0,font_path.length()-4);
385         }
386
387         if (basesize == FONT_SIZE_UNSPECIFIED)
388                 basesize = DEFAULT_FONT_SIZE;
389
390         unsigned int size = floor(
391                         porting::getDisplayDensity() *
392                         m_settings->getFloat("gui_scaling") *
393                         basesize);
394
395         irr::gui::IGUIFont* font = NULL;
396
397         for(unsigned int offset = 0; offset < MAX_FONT_SIZE_OFFSET; offset++) {
398
399                 // try opening positive offset
400                 std::stringstream fontsize_plus_png;
401                 fontsize_plus_png << basename << "_" << (size + offset) << ".png";
402
403                 if (fs::PathExists(fontsize_plus_png.str())) {
404                         font = m_env->getFont(fontsize_plus_png.str().c_str());
405
406                         if (font) {
407                                 verbosestream << "FontEngine: found font: " << fontsize_plus_png.str() << std::endl;
408                                 break;
409                         }
410                 }
411
412                 std::stringstream fontsize_plus_xml;
413                 fontsize_plus_xml << basename << "_" << (size + offset) << ".xml";
414
415                 if (fs::PathExists(fontsize_plus_xml.str())) {
416                         font = m_env->getFont(fontsize_plus_xml.str().c_str());
417
418                         if (font) {
419                                 verbosestream << "FontEngine: found font: " << fontsize_plus_xml.str() << std::endl;
420                                 break;
421                         }
422                 }
423
424                 // try negative offset
425                 std::stringstream fontsize_minus_png;
426                 fontsize_minus_png << basename << "_" << (size - offset) << ".png";
427
428                 if (fs::PathExists(fontsize_minus_png.str())) {
429                         font = m_env->getFont(fontsize_minus_png.str().c_str());
430
431                         if (font) {
432                                 verbosestream << "FontEngine: found font: " << fontsize_minus_png.str() << std::endl;
433                                 break;
434                         }
435                 }
436
437                 std::stringstream fontsize_minus_xml;
438                 fontsize_minus_xml << basename << "_" << (size - offset) << ".xml";
439
440                 if (fs::PathExists(fontsize_minus_xml.str())) {
441                         font = m_env->getFont(fontsize_minus_xml.str().c_str());
442
443                         if (font) {
444                                 verbosestream << "FontEngine: found font: " << fontsize_minus_xml.str() << std::endl;
445                                 break;
446                         }
447                 }
448         }
449
450         // try name direct
451         if (font == NULL) {
452                 if (fs::PathExists(font_path)) {
453                         font = m_env->getFont(font_path.c_str());
454                         if (font)
455                                 verbosestream << "FontEngine: found font: " << font_path << std::endl;
456                 }
457         }
458
459         if (font != NULL) {
460                 font->grab();
461                 m_font_cache[mode][basesize] = font;
462         }
463 }