From 1d40385d4aacf0cbea4b19ff06940e8c9bebaf47 Mon Sep 17 00:00:00 2001
From: TriBlade9 <triblade9@mail.com>
Date: Fri, 16 Jan 2015 14:54:26 +0800
Subject: [PATCH] Colored chat working as expected for both freetype and
 non-freetype builds. @nerzhul improvements * Add unit tests * Fix coding
 style * move guiChatConsole.hpp to client/

---
 builtin/game/chatcommands.lua       |   2 +-
 builtin/game/misc.lua               |  17 +
 src/chat.cpp                        |  29 +-
 src/chat.h                          |   6 +-
 src/client/CMakeLists.txt           |   1 +
 src/{ => client}/guiChatConsole.cpp |  31 +-
 src/{ => client}/guiChatConsole.h   |   0
 src/game.cpp                        |  14 +-
 src/util/CMakeLists.txt             |  10 +
 src/util/coloredstring.cpp          |  68 +++
 src/util/coloredstring.h            |  44 ++
 src/util/statictext.cpp             | 654 ++++++++++++++++++++++++++++
 src/util/statictext.h               | 150 +++++++
 13 files changed, 998 insertions(+), 28 deletions(-)
 rename src/{ => client}/guiChatConsole.cpp (96%)
 rename src/{ => client}/guiChatConsole.h (100%)
 create mode 100644 src/util/coloredstring.cpp
 create mode 100644 src/util/coloredstring.h
 create mode 100644 src/util/statictext.cpp
 create mode 100644 src/util/statictext.h

diff --git a/builtin/game/chatcommands.lua b/builtin/game/chatcommands.lua
index 3350140ee..2627559a5 100644
--- a/builtin/game/chatcommands.lua
+++ b/builtin/game/chatcommands.lua
@@ -102,7 +102,7 @@ core.register_chatcommand("help", {
 	description = "Get help for commands or list privileges",
 	func = function(name, param)
 		local function format_help_line(cmd, def)
-			local msg = "/"..cmd
+			local msg = core.colorize("00ffff", "/"..cmd)
 			if def.params and def.params ~= "" then
 				msg = msg .. " " .. def.params
 			end
diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua
index de41cfc91..8d5c80216 100644
--- a/builtin/game/misc.lua
+++ b/builtin/game/misc.lua
@@ -197,3 +197,20 @@ function core.http_add_fetch(httpenv)
 
 	return httpenv
 end
+
+function core.get_color_escape_sequence(color)
+	--if string.len(color) == 3 then
+	--	local r = string.sub(color, 1, 1)
+	--	local g = string.sub(color, 2, 2)
+	--	local b = string.sub(color, 3, 3)
+	--	color = r ..  r .. g .. g .. b .. b
+	--end
+
+	--assert(#color == 6, "Color must be six characters in length.")
+	--return "\v" .. color
+	return "\v(color;" .. color .. ")"
+end
+
+function core.colorize(color, message)
+	return core.get_color_escape_sequence(color) .. message .. core.get_color_escape_sequence("ffffff")
+end
diff --git a/src/chat.cpp b/src/chat.cpp
index cebe31225..958389df5 100644
--- a/src/chat.cpp
+++ b/src/chat.cpp
@@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "chat.h"
 #include "debug.h"
+#include "config.h"
 #include "util/strfnd.h"
 #include <cctype>
 #include <sstream>
@@ -251,8 +252,7 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols,
 	u32 hanging_indentation = 0;
 
 	// Format the sender name and produce fragments
-	if (!line.name.empty())
-	{
+	if (!line.name.empty()) {
 		temp_frag.text = L"<";
 		temp_frag.column = 0;
 		//temp_frag.bold = 0;
@@ -267,28 +267,28 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols,
 		next_frags.push_back(temp_frag);
 	}
 
+	std::wstring name_sanitized = removeEscapes(line.name);
+
 	// Choose an indentation level
-	if (line.name.empty())
-	{
+	if (line.name.empty()) {
 		// Server messages
 		hanging_indentation = 0;
 	}
-	else if (line.name.size() + 3 <= cols/2)
-	{
+	else if (name_sanitized.size() + 3 <= cols/2) {
 		// Names shorter than about half the console width
 		hanging_indentation = line.name.size() + 3;
 	}
-	else
-	{
+	else {
 		// Very long names
 		hanging_indentation = 2;
 	}
+	ColoredString line_text(line.text);
 
 	next_line.first = true;
 	bool text_processing = false;
 
 	// Produce fragments and layout them into lines
-	while (!next_frags.empty() || in_pos < line.text.size())
+	while (!next_frags.empty() || in_pos < line_text.size())
 	{
 		// Layout fragments into lines
 		while (!next_frags.empty())
@@ -326,9 +326,9 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols,
 		}
 
 		// Produce fragment
-		if (in_pos < line.text.size())
+		if (in_pos < line_text.size())
 		{
-			u32 remaining_in_input = line.text.size() - in_pos;
+			u32 remaining_in_input = line_text.size() - in_pos;
 			u32 remaining_in_output = cols - out_column;
 
 			// Determine a fragment length <= the minimum of
@@ -338,14 +338,14 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols,
 			while (frag_length < remaining_in_input &&
 					frag_length < remaining_in_output)
 			{
-				if (isspace(line.text[in_pos + frag_length]))
+				if (isspace(line_text[in_pos + frag_length]))
 					space_pos = frag_length;
 				++frag_length;
 			}
 			if (space_pos != 0 && frag_length < remaining_in_input)
 				frag_length = space_pos + 1;
 
-			temp_frag.text = line.text.substr(in_pos, frag_length);
+			temp_frag.text = line_text.substr(in_pos, frag_length);
 			temp_frag.column = 0;
 			//temp_frag.bold = 0;
 			next_frags.push_back(temp_frag);
@@ -686,9 +686,6 @@ ChatBackend::~ChatBackend()
 
 void ChatBackend::addMessage(std::wstring name, std::wstring text)
 {
-	name = unescape_enriched(name);
-	text = unescape_enriched(text);
-
 	// Note: A message may consist of multiple lines, for example the MOTD.
 	WStrfnd fnd(text);
 	while (!fnd.at_end())
diff --git a/src/chat.h b/src/chat.h
index db4146d35..661cafc82 100644
--- a/src/chat.h
+++ b/src/chat.h
@@ -20,11 +20,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #ifndef CHAT_HEADER
 #define CHAT_HEADER
 
-#include "irrlichttypes.h"
 #include <string>
 #include <vector>
 #include <list>
 
+#include "irrlichttypes.h"
+#include "util/coloredstring.h"
+
 // Chat console related classes
 
 struct ChatLine
@@ -34,7 +36,7 @@ struct ChatLine
 	// name of sending player, or empty if sent by server
 	std::wstring name;
 	// message text
-	std::wstring text;
+	ColoredString text;
 
 	ChatLine(std::wstring a_name, std::wstring a_text):
 		age(0.0),
diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt
index a1ec37fe3..bcf114760 100644
--- a/src/client/CMakeLists.txt
+++ b/src/client/CMakeLists.txt
@@ -1,5 +1,6 @@
 set(client_SRCS
 	${CMAKE_CURRENT_SOURCE_DIR}/clientlauncher.cpp
+	${CMAKE_CURRENT_SOURCE_DIR}/guiChatConsole.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/tile.cpp
 	PARENT_SCOPE
 )
diff --git a/src/guiChatConsole.cpp b/src/client/guiChatConsole.cpp
similarity index 96%
rename from src/guiChatConsole.cpp
rename to src/client/guiChatConsole.cpp
index 17a1689c7..d8837556a 100644
--- a/src/guiChatConsole.cpp
+++ b/src/client/guiChatConsole.cpp
@@ -32,7 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include <string>
 
 #if USE_FREETYPE
-#include "xCGUITTFont.h"
+	#include "xCGUITTFont.h"
 #endif
 
 inline u32 clamp_u8(s32 value)
@@ -340,13 +340,28 @@ void GUIChatConsole::drawText()
 			s32 x = (fragment.column + 1) * m_fontsize.X;
 			core::rect<s32> destrect(
 				x, y, x + m_fontsize.X * fragment.text.size(), y + m_fontsize.Y);
-			m_font->draw(
-				fragment.text.c_str(),
-				destrect,
-				video::SColor(255, 255, 255, 255),
-				false,
-				false,
-				&AbsoluteClippingRect);
+
+
+			#if USE_FREETYPE
+			// Draw colored text if FreeType is enabled
+				irr::gui::CGUITTFont *tmp = static_cast<irr::gui::CGUITTFont*>(m_font);
+				tmp->draw(
+					fragment.text.c_str(),
+					destrect,
+					fragment.text.getColors(),
+					false,
+					false,
+					&AbsoluteClippingRect);
+			#else
+			// Otherwise use standard text
+				m_font->draw(
+					fragment.text.c_str(),
+					destrect,
+					video::SColor(255, 255, 255, 255),
+					false,
+					false,
+					&AbsoluteClippingRect);
+			#endif
 		}
 	}
 }
diff --git a/src/guiChatConsole.h b/src/client/guiChatConsole.h
similarity index 100%
rename from src/guiChatConsole.h
rename to src/client/guiChatConsole.h
diff --git a/src/game.cpp b/src/game.cpp
index c5211a042..71a04aef5 100644
--- a/src/game.cpp
+++ b/src/game.cpp
@@ -34,7 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "log.h"
 #include "filesys.h"
 #include "gettext.h"
-#include "guiChatConsole.h"
+#include "client/guiChatConsole.h"
 #include "guiFormSpecMenu.h"
 #include "guiKeyChangeMenu.h"
 #include "guiPasswordChange.h"
@@ -59,6 +59,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "minimap.h"
 #include "mapblock_mesh.h"
 
+#if USE_FREETYPE
+	#include "util/statictext.h"
+#endif
+
 #include "sound.h"
 
 #if USE_SOUND
@@ -2239,12 +2243,20 @@ bool Game::initGui()
 			false, false, guiroot);
 	guitext_status->setVisible(false);
 
+#if USE_FREETYPE
+	// Colored chat support when using FreeType
+	guitext_chat = new gui::StaticText(L"", false, guienv, guiroot, -1, core::rect<s32>(0, 0, 0, 0), false);
+	guitext_chat->setWordWrap(true);
+	guitext_chat->drop();
+#else
+	// Standard chat when FreeType is disabled
 	// Chat text
 	guitext_chat = guienv->addStaticText(
 			L"",
 			core::rect<s32>(0, 0, 0, 0),
 			//false, false); // Disable word wrap as of now
 			false, true, guiroot);
+#endif
 	// Remove stale "recent" chat messages from previous connections
 	chat_backend->clearRecentChat();
 
diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt
index 0e7cbad07..e028a0435 100644
--- a/src/util/CMakeLists.txt
+++ b/src/util/CMakeLists.txt
@@ -1,7 +1,16 @@
+if(USE_FREETYPE)
+	set(UTIL_FREETYPEDEP_SRCS
+		${CMAKE_CURRENT_SOURCE_DIR}/statictext.cpp
+	)
+else()
+	set(UTIL_FREETYPEDEP_SRCS )
+endif(USE_FREETYPE)
+
 set(UTIL_SRCS
 	${CMAKE_CURRENT_SOURCE_DIR}/areastore.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/auth.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/base64.cpp
+	${CMAKE_CURRENT_SOURCE_DIR}/coloredstring.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/directiontables.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/numeric.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/pointedthing.cpp
@@ -11,5 +20,6 @@ set(UTIL_SRCS
 	${CMAKE_CURRENT_SOURCE_DIR}/string.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/srp.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/timetaker.cpp
+	${UTIL_FREETYPEDEP_SRCS}
 	PARENT_SCOPE)
 
diff --git a/src/util/coloredstring.cpp b/src/util/coloredstring.cpp
new file mode 100644
index 000000000..7db586550
--- /dev/null
+++ b/src/util/coloredstring.cpp
@@ -0,0 +1,68 @@
+/*
+Copyright (C) 2013 xyz, Ilya Zhuravlev <whatever@xyz.is>
+
+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 "coloredstring.h"
+#include "util/string.h"
+
+ColoredString::ColoredString()
+{}
+
+ColoredString::ColoredString(const std::wstring &string, const std::vector<SColor> &colors):
+	m_string(string),
+	m_colors(colors)
+{}
+
+ColoredString::ColoredString(const std::wstring &s) {
+	m_string = colorizeText(s, m_colors, SColor(255, 255, 255, 255));
+}
+
+void ColoredString::operator=(const wchar_t *str) {
+	m_string = colorizeText(str, m_colors, SColor(255, 255, 255, 255));
+}
+
+size_t ColoredString::size() const {
+	return m_string.size();
+}
+
+ColoredString ColoredString::substr(size_t pos, size_t len) const {
+	if (pos == m_string.length())
+		return ColoredString();
+	if (len == std::string::npos || pos + len > m_string.length()) {
+		return ColoredString(
+		           m_string.substr(pos, std::string::npos),
+		           std::vector<SColor>(m_colors.begin() + pos, m_colors.end())
+		       );
+	} else {
+		return ColoredString(
+		           m_string.substr(pos, len),
+		           std::vector<SColor>(m_colors.begin() + pos, m_colors.begin() + pos + len)
+		       );
+	}
+}
+
+const wchar_t *ColoredString::c_str() const {
+	return m_string.c_str();
+}
+
+const std::vector<SColor> &ColoredString::getColors() const {
+	return m_colors;
+}
+
+const std::wstring &ColoredString::getString() const {
+	return m_string;
+}
diff --git a/src/util/coloredstring.h b/src/util/coloredstring.h
new file mode 100644
index 000000000..a6d98db30
--- /dev/null
+++ b/src/util/coloredstring.h
@@ -0,0 +1,44 @@
+/*
+Copyright (C) 2013 xyz, Ilya Zhuravlev <whatever@xyz.is>
+
+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.
+*/
+
+#ifndef COLOREDSTRING_HEADER
+#define COLOREDSTRING_HEADER
+
+#include <string>
+#include <vector>
+#include <SColor.h>
+
+using namespace irr::video;
+
+class ColoredString {
+public:
+	ColoredString();
+	ColoredString(const std::wstring &s);
+	ColoredString(const std::wstring &string, const std::vector<SColor> &colors);
+	void operator=(const wchar_t *str);
+	size_t size() const;
+	ColoredString substr(size_t pos = 0, size_t len = std::string::npos) const;
+	const wchar_t *c_str() const;
+	const std::vector<SColor> &getColors() const;
+	const std::wstring &getString() const;
+private:
+	std::wstring m_string;
+	std::vector<SColor> m_colors;
+};
+
+#endif
diff --git a/src/util/statictext.cpp b/src/util/statictext.cpp
new file mode 100644
index 000000000..b534b560e
--- /dev/null
+++ b/src/util/statictext.cpp
@@ -0,0 +1,654 @@
+// Copyright (C) 2002-2012 Nikolaus Gebhardt
+// This file is part of the "Irrlicht Engine".
+// For conditions of distribution and use, see copyright notice in irrlicht.h
+
+#include "statictext.h"
+#ifdef _IRR_COMPILE_WITH_GUI_
+
+//Only compile this if freetype is enabled.
+
+#include <vector>
+#include <string>
+#include <iostream>
+#include <IGUISkin.h>
+#include <IGUIEnvironment.h>
+#include <IGUIFont.h>
+#include <IVideoDriver.h>
+#include <rect.h>
+#include <SColor.h>
+
+#include "cguittfont/xCGUITTFont.h"
+#include "util/string.h"
+
+namespace irr
+{
+namespace gui
+{
+//! constructor
+StaticText::StaticText(const wchar_t* text, bool border,
+			IGUIEnvironment* environment, IGUIElement* parent,
+			s32 id, const core::rect<s32>& rectangle,
+			bool background)
+: IGUIStaticText(environment, parent, id, rectangle),
+	HAlign(EGUIA_UPPERLEFT), VAlign(EGUIA_UPPERLEFT),
+	Border(border), OverrideColorEnabled(false), OverrideBGColorEnabled(false), WordWrap(false), Background(background),
+	RestrainTextInside(true), RightToLeft(false),
+	OverrideColor(video::SColor(101,255,255,255)), BGColor(video::SColor(101,210,210,210)),
+	OverrideFont(0), LastBreakFont(0)
+{
+	#ifdef _DEBUG
+	setDebugName("StaticText");
+	#endif
+
+	Text = text;
+	if (environment && environment->getSkin())
+	{
+		BGColor = environment->getSkin()->getColor(gui::EGDC_3D_FACE);
+	}
+}
+
+
+//! destructor
+StaticText::~StaticText()
+{
+	if (OverrideFont)
+		OverrideFont->drop();
+}
+
+
+//! draws the element and its children
+void StaticText::draw()
+{
+	if (!IsVisible)
+		return;
+
+	IGUISkin* skin = Environment->getSkin();
+	if (!skin)
+		return;
+	video::IVideoDriver* driver = Environment->getVideoDriver();
+
+	core::rect<s32> frameRect(AbsoluteRect);
+
+	// draw background
+
+	if (Background)
+	{
+		if ( !OverrideBGColorEnabled )	// skin-colors can change
+			BGColor = skin->getColor(gui::EGDC_3D_FACE);
+
+		driver->draw2DRectangle(BGColor, frameRect, &AbsoluteClippingRect);
+	}
+
+	// draw the border
+
+	if (Border)
+	{
+		skin->draw3DSunkenPane(this, 0, true, false, frameRect, &AbsoluteClippingRect);
+		frameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X);
+	}
+
+	// draw the text
+	if (Text.size())
+	{
+		IGUIFont* font = getActiveFont();
+
+		if (font)
+		{
+			if (!WordWrap)
+			{
+				// TODO: add colors here
+				if (VAlign == EGUIA_LOWERRIGHT)
+				{
+					frameRect.UpperLeftCorner.Y = frameRect.LowerRightCorner.Y -
+						font->getDimension(L"A").Height - font->getKerningHeight();
+				}
+				if (HAlign == EGUIA_LOWERRIGHT)
+				{
+					frameRect.UpperLeftCorner.X = frameRect.LowerRightCorner.X -
+						font->getDimension(Text.c_str()).Width;
+				}
+
+				font->draw(Text.c_str(), frameRect,
+					OverrideColorEnabled ? OverrideColor : skin->getColor(isEnabled() ? EGDC_BUTTON_TEXT : EGDC_GRAY_TEXT),
+					HAlign == EGUIA_CENTER, VAlign == EGUIA_CENTER, (RestrainTextInside ? &AbsoluteClippingRect : NULL));
+			}
+			else
+			{
+				if (font != LastBreakFont)
+					breakText();
+
+				core::rect<s32> r = frameRect;
+				s32 height = font->getDimension(L"A").Height + font->getKerningHeight();
+				s32 totalHeight = height * BrokenText.size();
+				if (VAlign == EGUIA_CENTER)
+				{
+					r.UpperLeftCorner.Y = r.getCenter().Y - (totalHeight / 2);
+				}
+				else if (VAlign == EGUIA_LOWERRIGHT)
+				{
+					r.UpperLeftCorner.Y = r.LowerRightCorner.Y - totalHeight;
+				}
+
+				irr::video::SColor previous_color(255, 255, 255, 255);
+				for (u32 i=0; i<BrokenText.size(); ++i)
+				{
+					if (HAlign == EGUIA_LOWERRIGHT)
+					{
+						r.UpperLeftCorner.X = frameRect.LowerRightCorner.X -
+							font->getDimension(BrokenText[i].c_str()).Width;
+					}
+
+					std::vector<irr::video::SColor> colors;
+					std::wstring str;
+
+					str = colorizeText(BrokenText[i].c_str(), colors, previous_color);
+					if (!colors.empty())
+						previous_color = colors[colors.size() - 1];
+
+					irr::gui::CGUITTFont *tmp = static_cast<irr::gui::CGUITTFont*>(font);
+					tmp->draw(str.c_str(), r,
+						colors,
+						HAlign == EGUIA_CENTER, false, (RestrainTextInside ? &AbsoluteClippingRect : NULL));
+
+					r.LowerRightCorner.Y += height;
+					r.UpperLeftCorner.Y += height;
+				}
+			}
+		}
+	}
+
+	IGUIElement::draw();
+}
+
+
+//! Sets another skin independent font.
+void StaticText::setOverrideFont(IGUIFont* font)
+{
+	if (OverrideFont == font)
+		return;
+
+	if (OverrideFont)
+		OverrideFont->drop();
+
+	OverrideFont = font;
+
+	if (OverrideFont)
+		OverrideFont->grab();
+
+	breakText();
+}
+
+//! Gets the override font (if any)
+IGUIFont * StaticText::getOverrideFont() const
+{
+	return OverrideFont;
+}
+
+//! Get the font which is used right now for drawing
+IGUIFont* StaticText::getActiveFont() const
+{
+	if ( OverrideFont )
+		return OverrideFont;
+	IGUISkin* skin = Environment->getSkin();
+	if (skin)
+		return skin->getFont();
+	return 0;
+}
+
+//! Sets another color for the text.
+void StaticText::setOverrideColor(video::SColor color)
+{
+	OverrideColor = color;
+	OverrideColorEnabled = true;
+}
+
+
+//! Sets another color for the text.
+void StaticText::setBackgroundColor(video::SColor color)
+{
+	BGColor = color;
+	OverrideBGColorEnabled = true;
+	Background = true;
+}
+
+
+//! Sets whether to draw the background
+void StaticText::setDrawBackground(bool draw)
+{
+	Background = draw;
+}
+
+
+//! Gets the background color
+video::SColor StaticText::getBackgroundColor() const
+{
+	return BGColor;
+}
+
+
+//! Checks if background drawing is enabled
+bool StaticText::isDrawBackgroundEnabled() const
+{
+	_IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+	return Background;
+}
+
+
+//! Sets whether to draw the border
+void StaticText::setDrawBorder(bool draw)
+{
+	Border = draw;
+}
+
+
+//! Checks if border drawing is enabled
+bool StaticText::isDrawBorderEnabled() const
+{
+	_IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+	return Border;
+}
+
+
+void StaticText::setTextRestrainedInside(bool restrainTextInside)
+{
+	RestrainTextInside = restrainTextInside;
+}
+
+
+bool StaticText::isTextRestrainedInside() const
+{
+	return RestrainTextInside;
+}
+
+
+void StaticText::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical)
+{
+	HAlign = horizontal;
+	VAlign = vertical;
+}
+
+
+#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 7
+const video::SColor& StaticText::getOverrideColor() const
+#else
+video::SColor StaticText::getOverrideColor() const
+#endif
+{
+	return OverrideColor;
+}
+
+
+//! Sets if the static text should use the overide color or the
+//! color in the gui skin.
+void StaticText::enableOverrideColor(bool enable)
+{
+	OverrideColorEnabled = enable;
+}
+
+
+bool StaticText::isOverrideColorEnabled() const
+{
+	_IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+	return OverrideColorEnabled;
+}
+
+
+//! Enables or disables word wrap for using the static text as
+//! multiline text control.
+void StaticText::setWordWrap(bool enable)
+{
+	WordWrap = enable;
+	breakText();
+}
+
+
+bool StaticText::isWordWrapEnabled() const
+{
+	_IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+	return WordWrap;
+}
+
+
+void StaticText::setRightToLeft(bool rtl)
+{
+	if (RightToLeft != rtl)
+	{
+		RightToLeft = rtl;
+		breakText();
+	}
+}
+
+
+bool StaticText::isRightToLeft() const
+{
+	return RightToLeft;
+}
+
+
+//! Breaks the single text line.
+void StaticText::breakText()
+{
+	if (!WordWrap)
+		return;
+
+	BrokenText.clear();
+
+	IGUISkin* skin = Environment->getSkin();
+	IGUIFont* font = getActiveFont();
+	if (!font)
+		return;
+
+	LastBreakFont = font;
+
+	core::stringw line;
+	core::stringw word;
+	core::stringw whitespace;
+	s32 size = Text.size();
+	s32 length = 0;
+	s32 elWidth = RelativeRect.getWidth();
+	if (Border)
+		elWidth -= 2*skin->getSize(EGDS_TEXT_DISTANCE_X);
+	wchar_t c;
+
+	std::vector<irr::video::SColor> colors;
+
+	// We have to deal with right-to-left and left-to-right differently
+	// However, most parts of the following code is the same, it's just
+	// some order and boundaries which change.
+	if (!RightToLeft)
+	{
+		// regular (left-to-right)
+		for (s32 i=0; i<size; ++i)
+		{
+			c = Text[i];
+			bool lineBreak = false;
+
+			if (c == L'\r') // Mac or Windows breaks
+			{
+				lineBreak = true;
+				if (Text[i+1] == L'\n') // Windows breaks
+				{
+					Text.erase(i+1);
+					--size;
+				}
+				c = '\0';
+			}
+			else if (c == L'\n') // Unix breaks
+			{
+				lineBreak = true;
+				c = '\0';
+			}
+
+			bool isWhitespace = (c == L' ' || c == 0);
+			if ( !isWhitespace )
+			{
+				// part of a word
+				word += c;
+			}
+
+			if ( isWhitespace || i == (size-1))
+			{
+				if (word.size())
+				{
+					// here comes the next whitespace, look if
+					// we must break the last word to the next line.
+					const s32 whitelgth = font->getDimension(whitespace.c_str()).Width;
+					const std::wstring sanitized = removeEscapes(word.c_str());
+					const s32 wordlgth = font->getDimension(sanitized.c_str()).Width;
+
+					if (wordlgth > elWidth)
+					{
+						// This word is too long to fit in the available space, look for
+						// the Unicode Soft HYphen (SHY / 00AD) character for a place to
+						// break the word at
+						int where = word.findFirst( wchar_t(0x00AD) );
+						if (where != -1)
+						{
+							core::stringw first  = word.subString(0, where);
+							core::stringw second = word.subString(where, word.size() - where);
+							BrokenText.push_back(line + first + L"-");
+							const s32 secondLength = font->getDimension(second.c_str()).Width;
+
+							length = secondLength;
+							line = second;
+						}
+						else
+						{
+							// No soft hyphen found, so there's nothing more we can do
+							// break to next line
+							if (length)
+								BrokenText.push_back(line);
+							length = wordlgth;
+							line = word;
+						}
+					}
+					else if (length && (length + wordlgth + whitelgth > elWidth))
+					{
+						// break to next line
+						BrokenText.push_back(line);
+						length = wordlgth;
+						line = word;
+					}
+					else
+					{
+						// add word to line
+						line += whitespace;
+						line += word;
+						length += whitelgth + wordlgth;
+					}
+
+					word = L"";
+					whitespace = L"";
+				}
+
+				if ( isWhitespace )
+				{
+					whitespace += c;
+				}
+
+				// compute line break
+				if (lineBreak)
+				{
+					line += whitespace;
+					line += word;
+					BrokenText.push_back(line);
+					line = L"";
+					word = L"";
+					whitespace = L"";
+					length = 0;
+				}
+			}
+		}
+
+		line += whitespace;
+		line += word;
+		BrokenText.push_back(line);
+	}
+	else
+	{
+		// right-to-left
+		for (s32 i=size; i>=0; --i)
+		{
+			c = Text[i];
+			bool lineBreak = false;
+
+			if (c == L'\r') // Mac or Windows breaks
+			{
+				lineBreak = true;
+				if ((i>0) && Text[i-1] == L'\n') // Windows breaks
+				{
+					Text.erase(i-1);
+					--size;
+				}
+				c = '\0';
+			}
+			else if (c == L'\n') // Unix breaks
+			{
+				lineBreak = true;
+				c = '\0';
+			}
+
+			if (c==L' ' || c==0 || i==0)
+			{
+				if (word.size())
+				{
+					// here comes the next whitespace, look if
+					// we must break the last word to the next line.
+					const s32 whitelgth = font->getDimension(whitespace.c_str()).Width;
+					const s32 wordlgth = font->getDimension(word.c_str()).Width;
+
+					if (length && (length + wordlgth + whitelgth > elWidth))
+					{
+						// break to next line
+						BrokenText.push_back(line);
+						length = wordlgth;
+						line = word;
+					}
+					else
+					{
+						// add word to line
+						line = whitespace + line;
+						line = word + line;
+						length += whitelgth + wordlgth;
+					}
+
+					word = L"";
+					whitespace = L"";
+				}
+
+				if (c != 0)
+					whitespace = core::stringw(&c, 1) + whitespace;
+
+				// compute line break
+				if (lineBreak)
+				{
+					line = whitespace + line;
+					line = word + line;
+					BrokenText.push_back(line);
+					line = L"";
+					word = L"";
+					whitespace = L"";
+					length = 0;
+				}
+			}
+			else
+			{
+				// yippee this is a word..
+				word = core::stringw(&c, 1) + word;
+			}
+		}
+
+		line = whitespace + line;
+		line = word + line;
+		BrokenText.push_back(line);
+	}
+}
+
+
+//! Sets the new caption of this element.
+void StaticText::setText(const wchar_t* text)
+{
+	IGUIElement::setText(text);
+	breakText();
+}
+
+
+void StaticText::updateAbsolutePosition()
+{
+	IGUIElement::updateAbsolutePosition();
+	breakText();
+}
+
+
+//! Returns the height of the text in pixels when it is drawn.
+s32 StaticText::getTextHeight() const
+{
+	IGUIFont* font = getActiveFont();
+	if (!font)
+		return 0;
+
+	s32 height = font->getDimension(L"A").Height + font->getKerningHeight();
+
+	if (WordWrap)
+		height *= BrokenText.size();
+
+	return height;
+}
+
+
+s32 StaticText::getTextWidth() const
+{
+	IGUIFont * font = getActiveFont();
+	if(!font)
+		return 0;
+
+	if(WordWrap)
+	{
+		s32 widest = 0;
+
+		for(u32 line = 0; line < BrokenText.size(); ++line)
+		{
+			s32 width = font->getDimension(BrokenText[line].c_str()).Width;
+
+			if(width > widest)
+				widest = width;
+		}
+
+		return widest;
+	}
+	else
+	{
+		return font->getDimension(Text.c_str()).Width;
+	}
+}
+
+
+//! Writes attributes of the element.
+//! Implement this to expose the attributes of your element for
+//! scripting languages, editors, debuggers or xml serialization purposes.
+void StaticText::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const
+{
+	IGUIStaticText::serializeAttributes(out,options);
+
+	out->addBool	("Border",              Border);
+	out->addBool	("OverrideColorEnabled",OverrideColorEnabled);
+	out->addBool	("OverrideBGColorEnabled",OverrideBGColorEnabled);
+	out->addBool	("WordWrap",		WordWrap);
+	out->addBool	("Background",          Background);
+	out->addBool	("RightToLeft",         RightToLeft);
+	out->addBool	("RestrainTextInside",  RestrainTextInside);
+	out->addColor	("OverrideColor",       OverrideColor);
+	out->addColor	("BGColor",       	BGColor);
+	out->addEnum	("HTextAlign",          HAlign, GUIAlignmentNames);
+	out->addEnum	("VTextAlign",          VAlign, GUIAlignmentNames);
+
+	// out->addFont ("OverrideFont",	OverrideFont);
+}
+
+
+//! Reads attributes of the element
+void StaticText::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0)
+{
+	IGUIStaticText::deserializeAttributes(in,options);
+
+	Border = in->getAttributeAsBool("Border");
+	enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled"));
+	OverrideBGColorEnabled = in->getAttributeAsBool("OverrideBGColorEnabled");
+	setWordWrap(in->getAttributeAsBool("WordWrap"));
+	Background = in->getAttributeAsBool("Background");
+	RightToLeft = in->getAttributeAsBool("RightToLeft");
+	RestrainTextInside = in->getAttributeAsBool("RestrainTextInside");
+	OverrideColor = in->getAttributeAsColor("OverrideColor");
+	BGColor = in->getAttributeAsColor("BGColor");
+
+	setTextAlignment( (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames),
+                      (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames));
+
+	// OverrideFont = in->getAttributeAsFont("OverrideFont");
+}
+
+} // end namespace gui
+} // end namespace irr
+
+
+#endif // _IRR_COMPILE_WITH_GUI_
diff --git a/src/util/statictext.h b/src/util/statictext.h
new file mode 100644
index 000000000..8d2f879e7
--- /dev/null
+++ b/src/util/statictext.h
@@ -0,0 +1,150 @@
+// Copyright (C) 2002-2012 Nikolaus Gebhardt
+// This file is part of the "Irrlicht Engine".
+// For conditions of distribution and use, see copyright notice in irrlicht.h
+
+#ifndef __C_GUI_STATIC_TEXT_H_INCLUDED__
+#define __C_GUI_STATIC_TEXT_H_INCLUDED__
+
+#include "IrrCompileConfig.h"
+#ifdef _IRR_COMPILE_WITH_GUI_
+
+#include "IGUIStaticText.h"
+#include "irrArray.h"
+
+#include <vector>
+
+namespace irr
+{
+namespace gui
+{
+	class StaticText : public IGUIStaticText
+	{
+	public:
+
+		//! constructor
+		StaticText(const wchar_t* text, bool border, IGUIEnvironment* environment,
+			IGUIElement* parent, s32 id, const core::rect<s32>& rectangle,
+			bool background = false);
+
+		//! destructor
+		virtual ~StaticText();
+
+		//! draws the element and its children
+		virtual void draw();
+
+		//! Sets another skin independent font.
+		virtual void setOverrideFont(IGUIFont* font=0);
+
+		//! Gets the override font (if any)
+		virtual IGUIFont* getOverrideFont() const;
+
+		//! Get the font which is used right now for drawing
+		virtual IGUIFont* getActiveFont() const;
+
+		//! Sets another color for the text.
+		virtual void setOverrideColor(video::SColor color);
+
+		//! Sets another color for the background.
+		virtual void setBackgroundColor(video::SColor color);
+
+		//! Sets whether to draw the background
+		virtual void setDrawBackground(bool draw);
+
+		//! Gets the background color
+		virtual video::SColor getBackgroundColor() const;
+
+		//! Checks if background drawing is enabled
+		virtual bool isDrawBackgroundEnabled() const;
+
+		//! Sets whether to draw the border
+		virtual void setDrawBorder(bool draw);
+
+		//! Checks if border drawing is enabled
+		virtual bool isDrawBorderEnabled() const;
+
+		//! Sets alignment mode for text
+		virtual void setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical);
+
+		//! Gets the override color
+		#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 7
+		virtual const video::SColor& getOverrideColor() const;
+		#else
+		virtual video::SColor getOverrideColor() const;
+		#endif
+
+		//! Sets if the static text should use the overide color or the
+		//! color in the gui skin.
+		virtual void enableOverrideColor(bool enable);
+
+		//! Checks if an override color is enabled
+		virtual bool isOverrideColorEnabled() const;
+
+		//! Set whether the text in this label should be clipped if it goes outside bounds
+		virtual void setTextRestrainedInside(bool restrainedInside);
+
+		//! Checks if the text in this label should be clipped if it goes outside bounds
+		virtual bool isTextRestrainedInside() const;
+
+		//! Enables or disables word wrap for using the static text as
+		//! multiline text control.
+		virtual void setWordWrap(bool enable);
+
+		//! Checks if word wrap is enabled
+		virtual bool isWordWrapEnabled() const;
+
+		//! Sets the new caption of this element.
+		virtual void setText(const wchar_t* text);
+
+		//! Returns the height of the text in pixels when it is drawn.
+		virtual s32 getTextHeight() const;
+
+		//! Returns the width of the current text, in the current font
+		virtual s32 getTextWidth() const;
+
+		//! Updates the absolute position, splits text if word wrap is enabled
+		virtual void updateAbsolutePosition();
+
+		//! Set whether the string should be interpreted as right-to-left (RTL) text
+		/** \note This component does not implement the Unicode bidi standard, the
+		text of the component should be already RTL if you call this. The
+		main difference when RTL is enabled is that the linebreaks for multiline
+		elements are performed starting from the end.
+		*/
+		virtual void setRightToLeft(bool rtl);
+
+		//! Checks if the text should be interpreted as right-to-left text
+		virtual bool isRightToLeft() const;
+
+		//! Writes attributes of the element.
+		virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const;
+
+		//! Reads attributes of the element
+		virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options);
+
+	private:
+
+		//! Breaks the single text line.
+		void breakText();
+
+		EGUI_ALIGNMENT HAlign, VAlign;
+		bool Border;
+		bool OverrideColorEnabled;
+		bool OverrideBGColorEnabled;
+		bool WordWrap;
+		bool Background;
+		bool RestrainTextInside;
+		bool RightToLeft;
+
+		video::SColor OverrideColor, BGColor;
+		gui::IGUIFont* OverrideFont;
+		gui::IGUIFont* LastBreakFont; // stored because: if skin changes, line break must be recalculated.
+
+		core::array< core::stringw > BrokenText;
+	};
+
+} // end namespace gui
+} // end namespace irr
+
+#endif // _IRR_COMPILE_WITH_GUI_
+
+#endif // C_GUI_STATIC_TEXT_H_INCLUDED
-- 
GitLab