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(gui::IGUIEnvironment* env) :
49 for (u32 &i : m_default_size) {
50 i = (FontMode) FONT_SIZE_UNSPECIFIED;
53 assert(g_settings != NULL); // pre-condition
54 assert(m_env != NULL); // pre-condition
55 assert(m_env->getSkin() != NULL); // pre-condition
59 if (m_currentMode == FM_Standard) {
60 g_settings->registerChangedCallback("font_size", font_setting_changed, NULL);
61 g_settings->registerChangedCallback("font_bold", font_setting_changed, NULL);
62 g_settings->registerChangedCallback("font_italic", font_setting_changed, NULL);
63 g_settings->registerChangedCallback("font_path", font_setting_changed, NULL);
64 g_settings->registerChangedCallback("font_path_bold", font_setting_changed, NULL);
65 g_settings->registerChangedCallback("font_path_italic", font_setting_changed, NULL);
66 g_settings->registerChangedCallback("font_path_bolditalic", font_setting_changed, NULL);
67 g_settings->registerChangedCallback("font_shadow", font_setting_changed, NULL);
68 g_settings->registerChangedCallback("font_shadow_alpha", font_setting_changed, NULL);
70 else if (m_currentMode == FM_Fallback) {
71 g_settings->registerChangedCallback("fallback_font_size", font_setting_changed, NULL);
72 g_settings->registerChangedCallback("fallback_font_path", font_setting_changed, NULL);
73 g_settings->registerChangedCallback("fallback_font_shadow", font_setting_changed, NULL);
74 g_settings->registerChangedCallback("fallback_font_shadow_alpha", font_setting_changed, NULL);
77 g_settings->registerChangedCallback("mono_font_path", font_setting_changed, NULL);
78 g_settings->registerChangedCallback("mono_font_size", font_setting_changed, NULL);
79 g_settings->registerChangedCallback("screen_dpi", font_setting_changed, NULL);
80 g_settings->registerChangedCallback("gui_scaling", font_setting_changed, NULL);
83 /******************************************************************************/
84 FontEngine::~FontEngine()
89 /******************************************************************************/
90 void FontEngine::cleanCache()
92 for (auto &font_cache_it : m_font_cache) {
94 for (auto &font_it : font_cache_it) {
95 font_it.second->drop();
96 font_it.second = NULL;
98 font_cache_it.clear();
102 /******************************************************************************/
103 irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec)
105 if (spec.mode == FM_Unspecified) {
106 spec.mode = m_currentMode;
107 } else if (m_currentMode == FM_Simple) {
108 // Freetype disabled -> Force simple mode
109 spec.mode = (spec.mode == FM_Mono ||
110 spec.mode == FM_SimpleMono) ?
111 FM_SimpleMono : FM_Simple;
112 // Support for those could be added, but who cares?
117 // Fallback to default size
118 if (spec.size == FONT_SIZE_UNSPECIFIED)
119 spec.size = m_default_size[spec.mode];
121 const auto &cache = m_font_cache[spec.getHash()];
122 auto it = cache.find(spec.size);
123 if (it != cache.end())
126 // Font does not yet exist
127 gui::IGUIFont *font = nullptr;
128 if (spec.mode == FM_Simple || spec.mode == FM_SimpleMono)
129 font = initSimpleFont(spec);
131 font = initFont(spec);
133 m_font_cache[spec.getHash()][spec.size] = font;
138 /******************************************************************************/
139 unsigned int FontEngine::getTextHeight(const FontSpec &spec)
141 irr::gui::IGUIFont *font = getFont(spec);
143 // use current skin font as fallback
145 font = m_env->getSkin()->getFont();
147 FATAL_ERROR_IF(font == NULL, "Could not get skin font");
149 return font->getDimension(L"Some unimportant example String").Height;
152 /******************************************************************************/
153 unsigned int FontEngine::getTextWidth(const std::wstring &text, const FontSpec &spec)
155 irr::gui::IGUIFont *font = getFont(spec);
157 // use current skin font as fallback
159 font = m_env->getSkin()->getFont();
161 FATAL_ERROR_IF(font == NULL, "Could not get font");
163 return font->getDimension(text.c_str()).Width;
167 /** get line height for a specific font (including empty room between lines) */
168 unsigned int FontEngine::getLineHeight(const FontSpec &spec)
170 irr::gui::IGUIFont *font = getFont(spec);
172 // use current skin font as fallback
174 font = m_env->getSkin()->getFont();
176 FATAL_ERROR_IF(font == NULL, "Could not get font");
178 return font->getDimension(L"Some unimportant example String").Height
179 + font->getKerningHeight();
182 /******************************************************************************/
183 unsigned int FontEngine::getDefaultFontSize()
185 return m_default_size[m_currentMode];
188 unsigned int FontEngine::getFontSize(FontMode mode)
190 if (m_currentMode == FM_Simple) {
191 if (mode == FM_Mono || mode == FM_SimpleMono)
192 return m_default_size[FM_SimpleMono];
194 return m_default_size[FM_Simple];
197 if (mode == FM_Unspecified)
198 return m_default_size[FM_Standard];
200 return m_default_size[mode];
203 /******************************************************************************/
204 void FontEngine::readSettings()
206 if (USE_FREETYPE && g_settings->getBool("freetype")) {
207 m_default_size[FM_Standard] = g_settings->getU16("font_size");
208 m_default_size[FM_Fallback] = g_settings->getU16("fallback_font_size");
209 m_default_size[FM_Mono] = g_settings->getU16("mono_font_size");
211 /*~ DO NOT TRANSLATE THIS LITERALLY!
212 This is a special string. Put either "no" or "yes"
213 into the translation field (literally).
214 Choose "yes" if the language requires use of the fallback
215 font, "no" otherwise.
216 The fallback font is (normally) required for languages with
217 non-Latin script, like Chinese.
218 When in doubt, test your translation. */
219 m_currentMode = is_yes(gettext("needs_fallback_font")) ?
220 FM_Fallback : FM_Standard;
222 m_default_bold = g_settings->getBool("font_bold");
223 m_default_italic = g_settings->getBool("font_italic");
226 m_currentMode = FM_Simple;
229 m_default_size[FM_Simple] = g_settings->getU16("font_size");
230 m_default_size[FM_SimpleMono] = g_settings->getU16("mono_font_size");
237 /******************************************************************************/
238 void FontEngine::updateSkin()
240 gui::IGUIFont *font = getFont();
243 m_env->getSkin()->setFont(font);
245 errorstream << "FontEngine: Default font file: " <<
246 "\n\t\"" << g_settings->get("font_path") << "\"" <<
247 "\n\trequired for current screen configuration was not found" <<
248 " or was invalid file format." <<
249 "\n\tUsing irrlicht default font." << std::endl;
251 // If we did fail to create a font our own make irrlicht find a default one
252 font = m_env->getSkin()->getFont();
253 FATAL_ERROR_IF(font == NULL, "Could not create/get font");
255 u32 text_height = font->getDimension(L"Hello, world!").Height;
256 infostream << "FontEngine: measured text_height=" << text_height << std::endl;
259 /******************************************************************************/
260 void FontEngine::updateFontCache()
262 /* the only font to be initialized is default one,
263 * all others are re-initialized on demand */
264 getFont(FONT_SIZE_UNSPECIFIED, FM_Unspecified);
267 /******************************************************************************/
268 gui::IGUIFont *FontEngine::initFont(const FontSpec &spec)
270 assert(spec.mode != FM_Unspecified);
271 assert(spec.size != FONT_SIZE_UNSPECIFIED);
273 std::string setting_prefix = "";
277 setting_prefix = "fallback_";
281 setting_prefix = "mono_";
287 std::string setting_suffix = "";
289 setting_suffix.append("_bold");
291 setting_suffix.append("_italic");
293 u32 size = std::floor(RenderingEngine::getDisplayDensity() *
294 g_settings->getFloat("gui_scaling") * spec.size);
297 errorstream << "FontEngine: attempt to use font size 0" << std::endl;
298 errorstream << " display density: " << RenderingEngine::getDisplayDensity() << std::endl;
303 u16 font_shadow_alpha = 0;
304 g_settings->getU16NoEx(setting_prefix + "font_shadow", font_shadow);
305 g_settings->getU16NoEx(setting_prefix + "font_shadow_alpha",
308 std::string wanted_font_path;
309 wanted_font_path = g_settings->get(setting_prefix + "font_path" + setting_suffix);
311 std::string fallback_settings[] = {
313 g_settings->get("fallback_font_path"),
314 Settings::getLayer(SL_DEFAULTS)->get(setting_prefix + "font_path")
318 for (const std::string &font_path : fallback_settings) {
319 irr::gui::IGUIFont *font = gui::CGUITTFont::createTTFont(m_env,
320 font_path.c_str(), size, true, true, font_shadow,
326 errorstream << "FontEngine: Cannot load '" << font_path <<
327 "'. Trying to fall back to another path." << std::endl;
332 errorstream << "minetest can not continue without a valid font. "
333 "Please correct the 'font_path' setting or install the font "
334 "file in the proper location" << std::endl;
336 errorstream << "FontEngine: Tried to load freetype fonts but Minetest was"
337 " not compiled with that library." << std::endl;
342 /** initialize a font without freetype */
343 gui::IGUIFont *FontEngine::initSimpleFont(const FontSpec &spec)
345 assert(spec.mode == FM_Simple || spec.mode == FM_SimpleMono);
346 assert(spec.size != FONT_SIZE_UNSPECIFIED);
348 const std::string &font_path = g_settings->get(
349 (spec.mode == FM_SimpleMono) ? "mono_font_path" : "font_path");
351 size_t pos_dot = font_path.find_last_of('.');
352 std::string basename = font_path;
353 std::string ending = lowercase(font_path.substr(pos_dot));
355 if (ending == ".ttf") {
356 errorstream << "FontEngine: Found font \"" << font_path
357 << "\" but freetype is not available." << std::endl;
361 if (ending == ".xml" || ending == ".png")
362 basename = font_path.substr(0, pos_dot);
364 u32 size = std::floor(
365 RenderingEngine::getDisplayDensity() *
366 g_settings->getFloat("gui_scaling") *
369 irr::gui::IGUIFont *font = nullptr;
370 std::string font_extensions[] = { ".png", ".xml" };
372 // Find nearest matching font scale
373 // Does a "zig-zag motion" (positibe/negative), from 0 to MAX_FONT_SIZE_OFFSET
374 for (s32 zoffset = 0; zoffset < MAX_FONT_SIZE_OFFSET * 2; zoffset++) {
375 std::stringstream path;
378 s32 sign = (zoffset & 1) ? -1 : 1;
379 s32 offset = zoffset >> 1;
381 for (const std::string &ext : font_extensions) {
382 path.str(""); // Clear
383 path << basename << "_" << (size + offset * sign) << ext;
385 if (!fs::PathExists(path.str()))
388 font = m_env->getFont(path.str().c_str());
391 verbosestream << "FontEngine: found font: " << path.str() << std::endl;
402 if (fs::PathExists(font_path)) {
403 font = m_env->getFont(font_path.c_str());
405 verbosestream << "FontEngine: found font: " << font_path << std::endl;