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 /******************************************************************************/
190 void FontEngine::readSettings()
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");
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;
208 m_default_bold = m_settings->getBool("font_bold");
209 m_default_italic = m_settings->getBool("font_italic");
212 m_currentMode = FM_Simple;
215 m_default_size[FM_Simple] = m_settings->getU16("font_size");
216 m_default_size[FM_SimpleMono] = m_settings->getU16("mono_font_size");
223 /******************************************************************************/
224 void FontEngine::updateSkin()
226 gui::IGUIFont *font = getFont();
229 m_env->getSkin()->setFont(font);
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;
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");
241 u32 text_height = font->getDimension(L"Hello, world!").Height;
242 infostream << "FontEngine: measured text_height=" << text_height << std::endl;
245 /******************************************************************************/
246 void FontEngine::updateFontCache()
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);
253 /******************************************************************************/
254 gui::IGUIFont *FontEngine::initFont(const FontSpec &spec)
256 assert(spec.mode != FM_Unspecified);
257 assert(spec.size != FONT_SIZE_UNSPECIFIED);
259 std::string setting_prefix = "";
263 setting_prefix = "fallback_";
267 setting_prefix = "mono_";
273 std::string setting_suffix = "";
275 setting_suffix.append("_bold");
277 setting_suffix.append("_italic");
279 u32 size = std::floor(RenderingEngine::getDisplayDensity() *
280 m_settings->getFloat("gui_scaling") * spec.size);
283 errorstream << "FontEngine: attempt to use font size 0" << std::endl;
284 errorstream << " display density: " << RenderingEngine::getDisplayDensity() << std::endl;
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",
294 std::string wanted_font_path;
295 wanted_font_path = g_settings->get(setting_prefix + "font_path" + setting_suffix);
297 std::string fallback_settings[] = {
299 m_settings->get("fallback_font_path"),
300 m_settings->getDefault(setting_prefix + "font_path")
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,
312 errorstream << "FontEngine: Cannot load '" << font_path <<
313 "'. Trying to fall back to another path." << std::endl;
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;
322 errorstream << "FontEngine: Tried to load freetype fonts but Minetest was"
323 " not compiled with that library." << std::endl;
328 /** initialize a font without freetype */
329 gui::IGUIFont *FontEngine::initSimpleFont(const FontSpec &spec)
331 assert(spec.mode == FM_Simple || spec.mode == FM_SimpleMono);
332 assert(spec.size != FONT_SIZE_UNSPECIFIED);
334 const std::string &font_path = m_settings->get(
335 (spec.mode == FM_SimpleMono) ? "mono_font_path" : "font_path");
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));
341 if (ending == ".ttf") {
342 errorstream << "FontEngine: Found font \"" << font_path
343 << "\" but freetype is not available." << std::endl;
347 if (ending == ".xml" || ending == ".png")
348 basename = font_path.substr(0, pos_dot);
350 u32 size = std::floor(
351 RenderingEngine::getDisplayDensity() *
352 m_settings->getFloat("gui_scaling") *
355 irr::gui::IGUIFont *font = nullptr;
356 std::string font_extensions[] = { ".png", ".xml" };
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;
364 s32 sign = (zoffset & 1) ? -1 : 1;
365 s32 offset = zoffset >> 1;
367 for (const std::string &ext : font_extensions) {
368 path.str(""); // Clear
369 path << basename << "_" << (size + offset * sign) << ext;
371 if (!fs::PathExists(path.str()))
374 font = m_env->getFont(path.str().c_str());
377 verbosestream << "FontEngine: found font: " << path.str() << std::endl;
388 if (fs::PathExists(font_path)) {
389 font = m_env->getFont(font_path.c_str());
391 verbosestream << "FontEngine: found font: " << font_path << std::endl;