3 Copyright (C) 2010-2014 sapier <sapier at gmx dot net>
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.
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.
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.
20 #include "fontengine.h"
22 #include "client/renderingengine.h"
29 #include "irrlicht_changes/CGUITTFont.h"
32 /** maximum size distance for getting a "similar" font size */
33 #define MAX_FONT_SIZE_OFFSET 10
35 /** reference to access font engine, has to be initialized by main */
36 FontEngine* g_fontengine = NULL;
38 /** callback to be used on change of font size setting */
39 static void font_setting_changed(const std::string &name, void *userdata)
41 g_fontengine->readSettings();
44 /******************************************************************************/
45 FontEngine::FontEngine(Settings* main_settings, gui::IGUIEnvironment* env) :
46 m_settings(main_settings),
50 for (u32 &i : m_default_size) {
51 i = (FontMode) FONT_SIZE_UNSPECIFIED;
54 assert(m_settings != NULL); // pre-condition
55 assert(m_env != NULL); // pre-condition
56 assert(m_env->getSkin() != NULL); // pre-condition
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);
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);
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);
84 /******************************************************************************/
85 FontEngine::~FontEngine()
90 /******************************************************************************/
91 void FontEngine::cleanCache()
93 for (auto &font_cache_it : m_font_cache) {
95 for (auto &font_it : font_cache_it) {
96 font_it.second->drop();
97 font_it.second = NULL;
99 font_cache_it.clear();
103 /******************************************************************************/
104 irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec)
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?
118 // Fallback to default size
119 if (spec.size == FONT_SIZE_UNSPECIFIED)
120 spec.size = m_default_size[spec.mode];
122 const auto &cache = m_font_cache[spec.getHash()];
123 auto it = cache.find(spec.size);
124 if (it != cache.end())
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);
132 font = initFont(spec);
134 m_font_cache[spec.getHash()][spec.size] = font;
139 /******************************************************************************/
140 unsigned int FontEngine::getTextHeight(const FontSpec &spec)
142 irr::gui::IGUIFont *font = getFont(spec);
144 // use current skin font as fallback
146 font = m_env->getSkin()->getFont();
148 FATAL_ERROR_IF(font == NULL, "Could not get skin font");
150 return font->getDimension(L"Some unimportant example String").Height;
153 /******************************************************************************/
154 unsigned int FontEngine::getTextWidth(const std::wstring &text, const FontSpec &spec)
156 irr::gui::IGUIFont *font = getFont(spec);
158 // use current skin font as fallback
160 font = m_env->getSkin()->getFont();
162 FATAL_ERROR_IF(font == NULL, "Could not get font");
164 return font->getDimension(text.c_str()).Width;
168 /** get line height for a specific font (including empty room between lines) */
169 unsigned int FontEngine::getLineHeight(const FontSpec &spec)
171 irr::gui::IGUIFont *font = getFont(spec);
173 // use current skin font as fallback
175 font = m_env->getSkin()->getFont();
177 FATAL_ERROR_IF(font == NULL, "Could not get font");
179 return font->getDimension(L"Some unimportant example String").Height
180 + font->getKerningHeight();
183 /******************************************************************************/
184 unsigned int FontEngine::getDefaultFontSize()
186 return m_default_size[m_currentMode];
189 unsigned int FontEngine::getFontSize(FontMode mode)
191 if (m_currentMode == FM_Simple) {
192 if (mode == FM_Mono || mode == FM_SimpleMono)
193 return m_default_size[FM_SimpleMono];
195 return m_default_size[FM_Simple];
198 if (mode == FM_Unspecified)
199 return m_default_size[FM_Standard];
201 return m_default_size[mode];
204 /******************************************************************************/
205 void FontEngine::readSettings()
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");
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;
223 m_default_bold = m_settings->getBool("font_bold");
224 m_default_italic = m_settings->getBool("font_italic");
227 m_currentMode = FM_Simple;
230 m_default_size[FM_Simple] = m_settings->getU16("font_size");
231 m_default_size[FM_SimpleMono] = m_settings->getU16("mono_font_size");
238 /******************************************************************************/
239 void FontEngine::updateSkin()
241 gui::IGUIFont *font = getFont();
244 m_env->getSkin()->setFont(font);
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;
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");
256 u32 text_height = font->getDimension(L"Hello, world!").Height;
257 infostream << "FontEngine: measured text_height=" << text_height << std::endl;
260 /******************************************************************************/
261 void FontEngine::updateFontCache()
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);
268 /******************************************************************************/
269 gui::IGUIFont *FontEngine::initFont(const FontSpec &spec)
271 assert(spec.mode != FM_Unspecified);
272 assert(spec.size != FONT_SIZE_UNSPECIFIED);
274 std::string setting_prefix = "";
278 setting_prefix = "fallback_";
282 setting_prefix = "mono_";
288 std::string setting_suffix = "";
290 setting_suffix.append("_bold");
292 setting_suffix.append("_italic");
294 u32 size = std::floor(RenderingEngine::getDisplayDensity() *
295 m_settings->getFloat("gui_scaling") * spec.size);
298 errorstream << "FontEngine: attempt to use font size 0" << std::endl;
299 errorstream << " display density: " << RenderingEngine::getDisplayDensity() << std::endl;
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",
309 std::string wanted_font_path;
310 wanted_font_path = g_settings->get(setting_prefix + "font_path" + setting_suffix);
312 std::string fallback_settings[] = {
314 m_settings->get("fallback_font_path"),
315 m_settings->getDefault(setting_prefix + "font_path")
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,
327 errorstream << "FontEngine: Cannot load '" << font_path <<
328 "'. Trying to fall back to another path." << std::endl;
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;
337 errorstream << "FontEngine: Tried to load freetype fonts but Minetest was"
338 " not compiled with that library." << std::endl;
343 /** initialize a font without freetype */
344 gui::IGUIFont *FontEngine::initSimpleFont(const FontSpec &spec)
346 assert(spec.mode == FM_Simple || spec.mode == FM_SimpleMono);
347 assert(spec.size != FONT_SIZE_UNSPECIFIED);
349 const std::string &font_path = m_settings->get(
350 (spec.mode == FM_SimpleMono) ? "mono_font_path" : "font_path");
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));
356 if (ending == ".ttf") {
357 errorstream << "FontEngine: Found font \"" << font_path
358 << "\" but freetype is not available." << std::endl;
362 if (ending == ".xml" || ending == ".png")
363 basename = font_path.substr(0, pos_dot);
365 u32 size = std::floor(
366 RenderingEngine::getDisplayDensity() *
367 m_settings->getFloat("gui_scaling") *
370 irr::gui::IGUIFont *font = nullptr;
371 std::string font_extensions[] = { ".png", ".xml" };
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;
379 s32 sign = (zoffset & 1) ? -1 : 1;
380 s32 offset = zoffset >> 1;
382 for (const std::string &ext : font_extensions) {
383 path.str(""); // Clear
384 path << basename << "_" << (size + offset * sign) << ext;
386 if (!fs::PathExists(path.str()))
389 font = m_env->getFont(path.str().c_str());
392 verbosestream << "FontEngine: found font: " << path.str() << std::endl;
403 if (fs::PathExists(font_path)) {
404 font = m_env->getFont(font_path.c_str());
406 verbosestream << "FontEngine: found font: " << font_path << std::endl;