aboutsummaryrefslogtreecommitdiff
path: root/games
Commit message (Expand)AuthorAge
* games/minimal: Add menu/background.png and menu/icon.pngPerttu Ahola2013-05-02
* Add Mapgen V7, reorganize biomeskwolekr2013-04-07
* unkn own block -> unkn own nodekhonkhortisan2013-04-05
* Add different place sound for nodesPilzAdam2013-03-29
* Use minetest.register_ore() in minimalPilzAdam2013-03-24
* Mapgen indev: float islands, larger far biomesproller2013-03-24
* Liquid fine tuningproller2013-03-14
* new adjustable finite liquidproller2013-02-24
* Readded and optimized mapgen V6kwolekr2013-01-21
* Add initial Lua biomedef support, fixed biome selectionkwolekr2013-01-21
* Add the group attached_nodePilzAdam2012-12-01
* Swap out pixel-perfect nyan cat by request of Chris TorresPerttu Ahola2012-11-09
* Add functions to the default mod of minimal game to support old codePilzAdam2012-11-01
* Move falling to builtinPilzAdam2012-10-31
* Fix crash when furnace is full (minimal game)Perttu Ahola2012-08-12
* Deprecate minetest.add_to_creative_inventory and use group not_in_creative_in...Perttu Ahola2012-07-25
* Add notice in the minimal gamePerttu Ahola2012-07-25
* Improve inventory callbacks a bitPerttu Ahola2012-07-25
* Detached inventory callbacks and reworked node metadata callbacksPerttu Ahola2012-07-25
* Detached inventoriesPerttu Ahola2012-07-24
* Add node timer test in minimal/experimentalPerttu Ahola2012-07-24
* Move /give, /giveme, /spawnentity and /pulverize to builtin/chatcommands.luaPerttu Ahola2012-07-23
* Formspec button_exit[] and image_button_exit[]Perttu Ahola2012-07-22
* Add /test1 command to minimal for testing a more complicated player inventory...Perttu Ahola2012-07-22
* Implement formspecdarkrose2012-07-22
* Actually fix facedir-rotated nodes placed using minetest.env:place_node()Perttu Ahola2012-07-21
* Make lava buckets work as fuel in minimal gamedarkrose2012-07-21
* Allow defining player's inventory form in LuaPerttu Ahola2012-07-19
* Custom boxy nodes (stairs, slabs) and collision changesKahrl2012-06-17
* Revert back proper crack texturePerttu Ahola2012-06-16
* Allow node cracking animations of any lengthPerttu Ahola2012-06-16
* Update field names to non-deprecated ones in node definition prototypePerttu Ahola2012-06-16
* Use new field names and reorder fields a bit in minimal gamePerttu Ahola2012-06-16
* Node texture animationPerttu Ahola2012-06-16
* Add experimental_tester_tool_1.png to minimal game (was accidentally left out)Perttu Ahola2012-06-08
* Allow groups in crafting recipesPerttu Ahola2012-06-06
* Add after_destruct and cache the existence of on_construct, on_destruct and a...Perttu Ahola2012-06-05
* place_node, dig_node and punch_node; an in-game tester tool; remove old codePerttu Ahola2012-06-05
* Add InvRef:is_empty(listname) and make chests/furnaces not diggable if not em...darkrose2012-06-03
* fix locked chest to not destroy denied items (minimal game)darkrose2012-06-03
* Add fire visualization to minimal furnace menuPerttu Ahola2012-06-03
* Use proper furnace cook timePerttu Ahola2012-06-03
* Lua implementation of furnace with visible active statedarkrose2012-06-03
* Implement locked chest; add after_place_node and after_dig_node node callbacksPerttu Ahola2012-06-03
* minetest.get_craft_resultPerttu Ahola2012-06-03
* Implement sign using form field protocolPerttu Ahola2012-06-03
* Properly create metadata inventories in minimalPerttu Ahola2012-06-03
* Random node metadata thingsPerttu Ahola2012-06-03
* Attempt to begin to implement chests and furnace in Lua (with problems)Perttu Ahola2012-06-03
* Add missing mapgen.lua to games/minimalPerttu Ahola2012-04-06
431' href='#n431'>431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475
/*
Minetest
Copyright (C) 2010-2014 sapier <sapier at gmx dot net>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "fontengine.h"
#include "log.h"
#include "config.h"
#include "porting.h"
#include "constants.h"
#include "filesys.h"

#if USE_FREETYPE
#include "gettext.h"
#include "xCGUITTFont.h"
#endif

/** maximum size distance for getting a "similar" font size */
#define MAX_FONT_SIZE_OFFSET 10

/** reference to access font engine, has to be initialized by main */
FontEngine* g_fontengine = NULL;

/** callback to be used on change of font size setting */
static void font_setting_changed(const std::string &name, void *userdata)
{
	g_fontengine->readSettings();
}

/******************************************************************************/
FontEngine::FontEngine(Settings* main_settings, gui::IGUIEnvironment* env) :
	m_settings(main_settings),
	m_env(env),
	m_font_cache(),
	m_currentMode(FM_Standard),
	m_lastMode(),
	m_lastSize(0),
	m_lastFont(NULL)
{

	for (unsigned int i = 0; i < FM_MaxMode; i++) {
		m_default_size[i] = (FontMode) FONT_SIZE_UNSPECIFIED;
	}

	assert(m_settings != NULL); // pre-condition
	assert(m_env != NULL); // pre-condition
	assert(m_env->getSkin() != NULL); // pre-condition

	m_currentMode = FM_Simple;

#if USE_FREETYPE
	if (g_settings->getBool("freetype")) {
		m_default_size[FM_Standard] = m_settings->getU16("font_size");
		m_default_size[FM_Fallback] = m_settings->getU16("fallback_font_size");
		m_default_size[FM_Mono]     = m_settings->getU16("mono_font_size");

		if (is_yes(gettext("needs_fallback_font"))) {
			m_currentMode = FM_Fallback;
		}
		else {
			m_currentMode = FM_Standard;
		}
	}

	// having freetype but not using it is quite a strange case so we need to do
	// special handling for it
	if (m_currentMode == FM_Simple) {
		std::stringstream fontsize;
		fontsize << DEFAULT_FONT_SIZE;
		m_settings->setDefault("font_size", fontsize.str());
		m_settings->setDefault("mono_font_size", fontsize.str());
	}
#endif

	m_default_size[FM_Simple]       = m_settings->getU16("font_size");
	m_default_size[FM_SimpleMono]   = m_settings->getU16("mono_font_size");

	updateSkin();

	if (m_currentMode == FM_Standard) {
		m_settings->registerChangedCallback("font_size", font_setting_changed, NULL);
		m_settings->registerChangedCallback("font_path", font_setting_changed, NULL);
		m_settings->registerChangedCallback("font_shadow", font_setting_changed, NULL);
		m_settings->registerChangedCallback("font_shadow_alpha", font_setting_changed, NULL);
	}
	else if (m_currentMode == FM_Fallback) {
		m_settings->registerChangedCallback("fallback_font_size", font_setting_changed, NULL);
		m_settings->registerChangedCallback("fallback_font_path", font_setting_changed, NULL);
		m_settings->registerChangedCallback("fallback_font_shadow", font_setting_changed, NULL);
		m_settings->registerChangedCallback("fallback_font_shadow_alpha", font_setting_changed, NULL);
	}

	m_settings->registerChangedCallback("mono_font_path", font_setting_changed, NULL);
	m_settings->registerChangedCallback("mono_font_size", font_setting_changed, NULL);
	m_settings->registerChangedCallback("screen_dpi", font_setting_changed, NULL);
	m_settings->registerChangedCallback("gui_scaling", font_setting_changed, NULL);
}

/******************************************************************************/
FontEngine::~FontEngine()
{
	cleanCache();
}

/******************************************************************************/
void FontEngine::cleanCache()
{
	for ( unsigned int i = 0; i < FM_MaxMode; i++) {

		for (std::map<unsigned int, irr::gui::IGUIFont*>::iterator iter
				= m_font_cache[i].begin();
				iter != m_font_cache[i].end(); ++iter) {
			iter->second->drop();
			iter->second = NULL;
		}
		m_font_cache[i].clear();
	}
}

/******************************************************************************/
irr::gui::IGUIFont* FontEngine::getFont(unsigned int font_size, FontMode mode)
{
	if (mode == FM_Unspecified) {
		mode = m_currentMode;
	}
	else if ((mode == FM_Mono) && (m_currentMode == FM_Simple)) {
		mode = FM_SimpleMono;
	}

	if (font_size == FONT_SIZE_UNSPECIFIED) {
		font_size = m_default_size[mode];
	}

	if ((font_size == m_lastSize) && (mode == m_lastMode)) {
		return m_lastFont;
	}

	if (m_font_cache[mode].find(font_size) == m_font_cache[mode].end()) {
		initFont(font_size, mode);
	}

	if (m_font_cache[mode].find(font_size) == m_font_cache[mode].end()) {
		return NULL;
	}

	m_lastSize = font_size;
	m_lastMode = mode;
	m_lastFont = m_font_cache[mode][font_size];

	return m_font_cache[mode][font_size];
}

/******************************************************************************/
unsigned int FontEngine::getTextHeight(unsigned int font_size, FontMode mode)
{
	irr::gui::IGUIFont* font = getFont(font_size, mode);

	// use current skin font as fallback
	if (font == NULL) {
		font = m_env->getSkin()->getFont();
	}
	FATAL_ERROR_IF(font == NULL, "Could not get skin font");

	return font->getDimension(L"Some unimportant example String").Height;
}

/******************************************************************************/
unsigned int FontEngine::getTextWidth(const std::wstring& text,
		unsigned int font_size, FontMode mode)
{
	irr::gui::IGUIFont* font = getFont(font_size, mode);

	// use current skin font as fallback
	if (font == NULL) {
		font = m_env->getSkin()->getFont();
	}
	FATAL_ERROR_IF(font == NULL, "Could not get font");

	return font->getDimension(text.c_str()).Width;
}


/** get line height for a specific font (including empty room between lines) */
unsigned int FontEngine::getLineHeight(unsigned int font_size, FontMode mode)
{
	irr::gui::IGUIFont* font = getFont(font_size, mode);

	// use current skin font as fallback
	if (font == NULL) {
		font = m_env->getSkin()->getFont();
	}
	FATAL_ERROR_IF(font == NULL, "Could not get font");

	return font->getDimension(L"Some unimportant example String").Height
			+ font->getKerningHeight();
}

/******************************************************************************/
unsigned int FontEngine::getDefaultFontSize()
{
	return m_default_size[m_currentMode];
}

/******************************************************************************/
void FontEngine::readSettings()
{
#if USE_FREETYPE
	if (g_settings->getBool("freetype")) {
		m_default_size[FM_Standard] = m_settings->getU16("font_size");
		m_default_size[FM_Fallback] = m_settings->getU16("fallback_font_size");
		m_default_size[FM_Mono]     = m_settings->getU16("mono_font_size");

		if (is_yes(gettext("needs_fallback_font"))) {
			m_currentMode = FM_Fallback;
		}
		else {
			m_currentMode = FM_Standard;
		}
	}
#endif
	m_default_size[FM_Simple]       = m_settings->getU16("font_size");
	m_default_size[FM_SimpleMono]   = m_settings->getU16("mono_font_size");

	cleanCache();
	updateFontCache();
	updateSkin();
}

/******************************************************************************/
void FontEngine::updateSkin()
{
	gui::IGUIFont *font = getFont();

	if (font)
		m_env->getSkin()->setFont(font);
	else
		errorstream << "FontEngine: Default font file: " <<
				"\n\t\"" << m_settings->get("font_path") << "\"" <<
				"\n\trequired for current screen configuration was not found" <<
				" or was invalid file format." <<
				"\n\tUsing irrlicht default font." << std::endl;

	// If we did fail to create a font our own make irrlicht find a default one
	font = m_env->getSkin()->getFont();
	FATAL_ERROR_IF(font == NULL, "Could not create/get font");

	u32 text_height = font->getDimension(L"Hello, world!").Height;
	infostream << "text_height=" << text_height << std::endl;
}

/******************************************************************************/
void FontEngine::updateFontCache()
{
	/* the only font to be initialized is default one,
	 * all others are re-initialized on demand */
	initFont(m_default_size[m_currentMode], m_currentMode);

	/* reset font quick access */
	m_lastMode = FM_Unspecified;
	m_lastSize = 0;
	m_lastFont = NULL;
}

/******************************************************************************/
void FontEngine::initFont(unsigned int basesize, FontMode mode)
{

	std::string font_config_prefix;

	if (mode == FM_Unspecified) {
		mode = m_currentMode;
	}

	switch (mode) {

		case FM_Standard:
			font_config_prefix = "";
			break;

		case FM_Fallback:
			font_config_prefix = "fallback_";
			break;

		case FM_Mono:
			font_config_prefix = "mono_";
			if (m_currentMode == FM_Simple)
				mode = FM_SimpleMono;
			break;

		case FM_Simple: /* Fallthrough */
		case FM_SimpleMono: /* Fallthrough */
		default:
			font_config_prefix = "";

	}

	if (m_font_cache[mode].find(basesize) != m_font_cache[mode].end())
		return;

	if ((mode == FM_Simple) || (mode == FM_SimpleMono)) {
		initSimpleFont(basesize, mode);
		return;
	}
#if USE_FREETYPE
	else {
		if (! is_yes(m_settings->get("freetype"))) {
			return;
		}
		unsigned int size = floor(
				porting::getDisplayDensity() *
				m_settings->getFloat("gui_scaling") *
				basesize);
		u32 font_shadow       = 0;
		u32 font_shadow_alpha = 0;

		try {
			font_shadow =
					g_settings->getU16(font_config_prefix + "font_shadow");
		} catch (SettingNotFoundException&) {}
		try {
			font_shadow_alpha =
					g_settings->getU16(font_config_prefix + "font_shadow_alpha");
		} catch (SettingNotFoundException&) {}

		std::string font_path = g_settings->get(font_config_prefix + "font_path");

		irr::gui::IGUIFont* font = gui::CGUITTFont::createTTFont(m_env,
				font_path.c_str(), size, true, true, font_shadow,
				font_shadow_alpha);

		if (font != NULL) {
			m_font_cache[mode][basesize] = font;
			return;
		}

		// try fallback font
		errorstream << "FontEngine: failed to load: " << font_path << ", trying to fall back "
				"to fallback font" << std::endl;

		font_path = g_settings->get(font_config_prefix + "fallback_font_path");

		font = gui::CGUITTFont::createTTFont(m_env,
			font_path.c_str(), size, true, true, font_shadow,
			font_shadow_alpha);

		if (font != NULL) {
			m_font_cache[mode][basesize] = font;
			return;
		}

		// give up
		errorstream << "FontEngine: failed to load freetype font: "
				<< font_path << std::endl;
		errorstream << "minetest can not continue without a valid font. Please correct "
				"the 'font_path' setting or install the font file in the proper "
				"location" << std::endl;
		abort();
	}
#endif
}

/** initialize a font without freetype */
void FontEngine::initSimpleFont(unsigned int basesize, FontMode mode)
{
	assert(mode == FM_Simple || mode == FM_SimpleMono); // pre-condition

	std::string font_path = "";
	if (mode == FM_Simple) {
		font_path = m_settings->get("font_path");
	} else {
		font_path = m_settings->get("mono_font_path");
	}
	std::string basename = font_path;
	std::string ending = font_path.substr(font_path.length() -4);

	if (ending == ".ttf") {
		errorstream << "FontEngine: Not trying to open \"" << font_path
				<< "\" which seems to be a truetype font." << std::endl;
		return;
	}

	if ((ending == ".xml") || (ending == ".png")) {
		basename = font_path.substr(0,font_path.length()-4);
	}

	if (basesize == FONT_SIZE_UNSPECIFIED)
		basesize = DEFAULT_FONT_SIZE;

	unsigned int size = floor(
			porting::getDisplayDensity() *
			m_settings->getFloat("gui_scaling") *
			basesize);

	irr::gui::IGUIFont* font = NULL;

	for(unsigned int offset = 0; offset < MAX_FONT_SIZE_OFFSET; offset++) {

		// try opening positive offset
		std::stringstream fontsize_plus_png;
		fontsize_plus_png << basename << "_" << (size + offset) << ".png";

		if (fs::PathExists(fontsize_plus_png.str())) {
			font = m_env->getFont(fontsize_plus_png.str().c_str());

			if (font) {
				verbosestream << "FontEngine: found font: " << fontsize_plus_png.str() << std::endl;
				break;
			}
		}

		std::stringstream fontsize_plus_xml;
		fontsize_plus_xml << basename << "_" << (size + offset) << ".xml";

		if (fs::PathExists(fontsize_plus_xml.str())) {
			font = m_env->getFont(fontsize_plus_xml.str().c_str());

			if (font) {
				verbosestream << "FontEngine: found font: " << fontsize_plus_xml.str() << std::endl;
				break;
			}
		}

		// try negative offset
		std::stringstream fontsize_minus_png;
		fontsize_minus_png << basename << "_" << (size - offset) << ".png";

		if (fs::PathExists(fontsize_minus_png.str())) {
			font = m_env->getFont(fontsize_minus_png.str().c_str());

			if (font) {
				verbosestream << "FontEngine: found font: " << fontsize_minus_png.str() << std::endl;
				break;
			}
		}

		std::stringstream fontsize_minus_xml;
		fontsize_minus_xml << basename << "_" << (size - offset) << ".xml";

		if (fs::PathExists(fontsize_minus_xml.str())) {
			font = m_env->getFont(fontsize_minus_xml.str().c_str());

			if (font) {
				verbosestream << "FontEngine: found font: " << fontsize_minus_xml.str() << std::endl;
				break;
			}
		}
	}

	// try name direct
	if (font == NULL) {
		if (fs::PathExists(font_path)) {
			font = m_env->getFont(font_path.c_str());
			if (font)
				verbosestream << "FontEngine: found font: " << font_path << std::endl;
		}
	}

	if (font != NULL) {
		font->grab();
		m_font_cache[mode][basesize] = font;
	}
}