diff --git a/.gitignore b/.gitignore
index c6b1e37..727b13c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,5 +27,6 @@ Thumbs.db
 Debug
 Release
 ipch
+* gitignore
 
 documentation
diff --git a/Demo/VisualStudio/Demo.vcxproj b/Demo/VisualStudio/Demo.vcxproj
index 47e443e..21e54d4 100644
--- a/Demo/VisualStudio/Demo.vcxproj
+++ b/Demo/VisualStudio/Demo.vcxproj
@@ -123,6 +123,7 @@
     <ClCompile Include="..\..\tinkerbell\src\tb_window.cpp" />
     <ClCompile Include="..\..\tinkerbell\src\tests\tb_test.cpp" />
     <ClCompile Include="..\..\tinkerbell\src\tests\test_tb_linklist.cpp" />
+    <ClCompile Include="..\..\tinkerbell\src\tests\test_tb_parser.cpp" />
     <ClCompile Include="..\..\tinkerbell\src\tests\test_tb_space_allocator.cpp" />
     <ClCompile Include="..\..\tinkerbell\src\tests\test_tb_style_edit.cpp" />
     <ClCompile Include="..\..\tinkerbell\src\tests\test_tb_tempbuffer.cpp" />
diff --git a/Demo/VisualStudio/Demo.vcxproj.filters b/Demo/VisualStudio/Demo.vcxproj.filters
index aa8a7e4..21df8cc 100644
--- a/Demo/VisualStudio/Demo.vcxproj.filters
+++ b/Demo/VisualStudio/Demo.vcxproj.filters
@@ -189,6 +189,9 @@
     <ClCompile Include="..\..\tinkerbell\src\tests\test_tb_tempbuffer.cpp">
       <Filter>Source Files\tinkerbell\tests</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\tinkerbell\src\tests\test_tb_parser.cpp">
+      <Filter>Source Files\tinkerbell\tests</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <Library Include="..\freeglut.lib">
diff --git a/Demo/ui_resources/test_textwindow.tb.txt b/Demo/ui_resources/test_textwindow.tb.txt
index 85f8989..60ba0ce 100644
--- a/Demo/ui_resources/test_textwindow.tb.txt
+++ b/Demo/ui_resources/test_textwindow.tb.txt
@@ -18,4 +18,15 @@ TBLayout: axis: y, distribution: available
 				TBTextField: text: Menu
 				skin TBButton.flat
 	TBEditField: multiline: 1, styling: 1, gravity: all, id: editfield, autofocus: 1
-		text <u>Tinkerbell UI Toolkit!</u>\n\nThings to test:\n • resizing\n • right clicking\n • searching\n • keyboard navigation\n • scrolling\n • mouse wheel & dragging.\n • This is a list item that should wrap in a smart way.\n • Embedded content in textfield, such as this button: <widget TBButton: text: ":)">\n • <color #f44>Red</color>, <color #6f6>Green</color>, <color #aaf>Blue</color>
+		text: "Tinkerbell UI Toolkit\n\n" \
+				"<color #0794f8>Test zone</color>\n" \
+				"The menu to the left spawns some examples of what tinkerbell can do.\n\n" \
+				"The code in Demo/* is more like a developers test zone than organized tutorial-like samples. " \
+				"This would be good to fix of course (help is welcome! ;)\n\n" \
+				"<color #0794f8>Some things to try out</color>\n" \
+				" • All layouts provide panning of content automatically when squashed below the minimal size, so try resizing windows and pan.\n" \
+				" • Lines starting with a bullet sequence (like this one) should wrap in a smart way.\n" \
+				" • <u>Underline</u>, <color #f44>Red</color>, <color #6f6>Green</color>, <color #aaf>Blue</color>\n\n" \
+				"<color #0794f8>Good to know</color>\n" \
+				" • The text component you're reading in handles styling and embedded content (such as this widget: <widget TBButton: text: ':)'>), but does not handle editing of those fully. Undo/Redo should work fine, but caret movement and position updates doesn't.\n" \
+				" • All resources are UTF-8."
diff --git a/Makefile b/Makefile
index c40ab8f..b96b3c7 100644
--- a/Makefile
+++ b/Makefile
@@ -65,6 +65,7 @@ SRC = tinkerbell/src/tb_layout.cpp \
       tinkerbell/src/tests/test_tb_space_allocator.cpp \
       tinkerbell/src/tests/test_tb_widget_value.cpp \
       tinkerbell/src/tests/test_tb_linklist.cpp \
+      tinkerbell/src/tests/test_tb_parser.cpp \
       tinkerbell/src/tests/test_tb_tempbuffer.cpp \
       tinkerbell/src/tests/test_tb_test.cpp \
       stb_image/tb_image_loader_stb.cpp \
diff --git a/tinkerbell/src/parser/TBParser.cpp b/tinkerbell/src/parser/TBParser.cpp
index 3d73040..a43d4f6 100644
--- a/tinkerbell/src/parser/TBParser.cpp
+++ b/tinkerbell/src/parser/TBParser.cpp
@@ -9,6 +9,65 @@
 
 namespace tinkerbell {
 
+// == Util functions ====================================================================
+
+void UnescapeString(char *str)
+{
+	char *dst = str, *src = str;
+	while (*src)
+	{
+		if (*src == '\\')
+		{
+			bool code_found = true;
+			if (src[1] == 'n')
+				*dst = '\n';
+			else if (src[1] == 'r')
+				*dst = '\r';
+			else if (src[1] == 't')
+				*dst = '\t';
+			else if (src[1] == '\"')
+				*dst = '\"';
+			else if (src[1] == '\'')
+				*dst = '\'';
+			else if (src[1] == '\\')
+				*dst = '\\';
+			else
+				code_found = false;
+			if (code_found)
+			{
+				src += 2;
+				dst++;
+				continue;
+			}
+		}
+		*dst = *src;
+		dst++;
+		src++;
+	}
+	*dst = 0;
+}
+
+bool is_white_space(const char *str)
+{
+	switch (*str)
+	{
+	case ' ':
+	case '\t':
+		return true;
+	default:
+		return false;
+	}
+}
+
+bool is_pending_multiline(const char *str)
+{
+	while (is_white_space(str))
+		str++;
+	return str[0] == '\\' && str[1] == 0;
+}
+
+// == Parser ============================================================================
+
 Parser::STATUS Parser::Read(ParserStream *stream, ParserTarget *target)
 {
 	TBTempBuffer line, work;
@@ -17,6 +76,8 @@ Parser::STATUS Parser::Read(ParserStream *stream, ParserTarget *target)
 
 	current_indent = 0;
 	current_line_nr = 1;
+	pending_multiline = false;
+	multi_line_sub_level = 0;
 
 	while (int read_len = stream->GetMoreData((char *)work.GetData(), work.GetCapacity()))
 	{
@@ -83,42 +144,6 @@ Parser::STATUS Parser::Read(ParserStream *stream, ParserTarget *target)
 	return STATUS_OK;
 }
 
-void UnescapeString(char *str)
-{
-	char *dst = str, *src = str;
-	while (*src)
-	{
-		if (*src == '\\')
-		{
-			bool code_found = true;
-			if (src[1] == 'n')
-				*dst = '\n';
-			else if (src[1] == 'r')
-				*dst = '\r';
-			else if (src[1] == 't')
-				*dst = '\t';
-			else if (src[1] == '\"')
-				*dst = '\"';
-			else if (src[1] == '\'')
-				*dst = '\'';
-			else if (src[1] == '\\')
-				*dst = '\\';
-			else
-				code_found = false;
-			if (code_found)
-			{
-				src += 2;
-				dst++;
-				continue;
-			}
-		}
-		*dst = *src;
-		dst++;
-		src++;
-	}
-	*dst = 0;
-}
-
 void Parser::OnLine(char *line, ParserTarget *target)
 {
 	if (*line == '#')
@@ -126,6 +151,11 @@ void Parser::OnLine(char *line, ParserTarget *target)
 		target->OnComment(line + 1);
 		return;
 	}
+	if (pending_multiline)
+	{
+		OnMultiline(line, target);
+		return;
+	}
 
 	// Check indent
 	int indent = 0;
@@ -161,11 +191,11 @@ void Parser::OnLine(char *line, ParserTarget *target)
 	{
 		char *token = line;
 		// Read line while consuming it and copy over to token buf
-		while (*line != ' ' && *line != 0)
+		while (!is_white_space(line) && *line != 0)
 			line++;
 		int token_len = line - token;
 		// Consume any white space after the token
-		while (*line == ' ')
+		while (is_white_space(line))
 			line++;
 
 		bool is_compact_line = token_len && token[token_len - 1] == ':';
@@ -175,6 +205,19 @@ void Parser::OnLine(char *line, ParserTarget *target)
 		{
 			token_len--;
 			token[token_len] = 0;
+
+			// Check if the first argument is not a child but the value for this token
+			if (is_number(line) || *line == '[' || *line == '\"' || *line == '\'' || *line == '@')
+			{
+				ConsumeValue(value, line);
+
+				if (pending_multiline)
+				{
+					// The value wrapped to the next line, so we should remember the token and continue.
+					multi_line_token.Set(token);
+					return;
+				}
+			}
 		}
 		else if (token[token_len])
 		{
@@ -192,9 +235,9 @@ void Parser::OnLine(char *line, ParserTarget *target)
 /** Check if buf is pointing at a end quote. It may need to iterate
 	buf backwards toward buf_start to check if any preceding backslashes
 	make it a escaped quote (which should not be the end quote) */
-bool IsEndQuote(const char *buf_start, const char *buf)
+bool IsEndQuote(const char *buf_start, const char *buf, const char quote_type)
 {
-	if (*buf != '\"')
+	if (*buf != quote_type)
 		return false;
 	int num_backslashes = 0;
 	while (buf_start < buf && *(buf-- - 1) == '\\')
@@ -208,7 +251,7 @@ void Parser::OnCompactLine(char *line, ParserTarget *target)
 	while (*line)
 	{
 		// consume any whitespace
-		while (*line == ' ')
+		while (is_white_space(line))
 			line++;
 
 		// Find token
@@ -220,46 +263,19 @@ void Parser::OnCompactLine(char *line, ParserTarget *target)
 		*line++ = 0;
 
 		// consume any whitespace
-		while (*line == ' ')
+		while (is_white_space(line))
 			line++;
 
 		TBValue v;
+		ConsumeValue(v, line);
 
-		// Find value (As quoted string, or as auto)
-		char *value = line;
-		if (*line == '\"')
+		if (pending_multiline)
 		{
-			// Consume starting quote
-			line++;
-			value++;
-			// Find ending quote or end
-			while (!IsEndQuote(value, line) && *line != 0)
-				line++;
-			// Terminate away the quote
-			if (*line == '\"')
-				*line++ = 0;
-
-			// consume any whitespace
-			while (*line == ' ')
-				line++;
-			// consume any comma
-			if (*line == ',')
-				line++;
-
-			UnescapeString(value);
-			v.SetString(value, TBValue::SET_AS_STATIC);
-		}
-		else
-		{
-			// Find next comma or end
-			while (*line != ',' && *line != 0)
-				line++;
-			// Terminate away the comma
-			if (*line == ',')
-				*line++ = 0;
-
-			UnescapeString(value);
-			v.SetFromStringAuto(value, TBValue::SET_AS_STATIC);
+			// The value wrapped to the next line, so we should remember the token and continue.
+			multi_line_token.Set(token);
+			// Since we need to call target->Leave when the multiline is ready, set multi_line_sub_level.
+			multi_line_sub_level = 1;
+			return;
 		}
 
 		// Ready
@@ -269,4 +285,77 @@ void Parser::OnCompactLine(char *line, ParserTarget *target)
 	target->Leave();
 }
 
+void Parser::OnMultiline(char *line, ParserTarget *target)
+{
+	// consume any whitespace
+	while (is_white_space(line))
+		line++;
+
+	TBValue value;
+	ConsumeValue(value, line);
+
+	if (!pending_multiline)
+	{
+		// Ready with all lines
+		value.SetString(multi_line_value.GetData(), TBValue::SET_AS_STATIC);
+		target->OnToken(multi_line_token, value);
+
+		if (multi_line_sub_level)
+			target->Leave();
+
+		// Reset
+		multi_line_value.SetAppendPos(0);
+		multi_line_sub_level = 0;
+	}
+}
+
+void Parser::ConsumeValue(TBValue &dst_value, char *&line)
+{
+	// Find value (As quoted string, or as auto)
+	char *value = line;
+	if (*line == '\"' || *line == '\'')
+	{
+		const char quote_type = *line;
+		// Consume starting quote
+		line++;
+		value++;
+		// Find ending quote or end
+		while (!IsEndQuote(value, line, quote_type) && *line != 0)
+			line++;
+		// Terminate away the quote
+		if (*line == quote_type)
+			*line++ = 0;
+
+		// consume any whitespace
+		while (is_white_space(line))
+			line++;
+		// consume any comma
+		if (*line == ',')
+			line++;
+
+		UnescapeString(value);
+		dst_value.SetString(value, TBValue::SET_AS_STATIC);
+	}
+	else
+	{
+		// Find next comma or end
+		while (*line != ',' && *line != 0)
+			line++;
+		// Terminate away the comma
+		if (*line == ',')
+			*line++ = 0;
+
+		UnescapeString(value);
+		dst_value.SetFromStringAuto(value, TBValue::SET_AS_STATIC);
+	}
+
+	// Check if we still have pending value data on the following line and set pending_multiline.
+	bool continuing_multiline = pending_multiline;
+	pending_multiline = is_pending_multiline(line);
+
+	// Append the multi line value to the buffer.
+	if (continuing_multiline || pending_multiline)
+		multi_line_value.AppendString(dst_value.GetString());
+}
+
 }; // namespace tinkerbell
diff --git a/tinkerbell/src/parser/TBParser.h b/tinkerbell/src/parser/TBParser.h
index 77df617..6dde56a 100644
--- a/tinkerbell/src/parser/TBParser.h
+++ b/tinkerbell/src/parser/TBParser.h
@@ -7,6 +7,7 @@
 #define TBParser_H
 
 #include "tb_value.h"
+#include "tb_tempbuffer.h"
 
 namespace tinkerbell {
 
@@ -41,8 +42,14 @@ public:
 private:
 	int current_indent;
 	int current_line_nr;
+	TBStr multi_line_token;
+	TBTempBuffer multi_line_value;
+	int multi_line_sub_level;
+	bool pending_multiline;
 	void OnLine(char *line, ParserTarget *target);
 	void OnCompactLine(char *line, ParserTarget *target);
+	void OnMultiline(char *line, ParserTarget *target);
+	void ConsumeValue(TBValue &dst_value, char *&line);
 };
 
 }; // namespace tinkerbell
diff --git a/tinkerbell/src/tb_value.cpp b/tinkerbell/src/tb_value.cpp
index c89e837..27863c3 100644
--- a/tinkerbell/src/tb_value.cpp
+++ b/tinkerbell/src/tb_value.cpp
@@ -19,14 +19,14 @@ namespace tinkerbell {
 
 // == Helper functions ============================
 
-bool p_is_number(const char *str)
+bool is_number(const char *str)
 {
-	if (*str == '-')
+	if (*str == '-' || *str == '.')
 		str++;
 	return *str >= '0' && *str <= '9';
 }
 
-bool p_is_number_float(const char *str)
+bool is_number_float(const char *str)
 {
 	while (*str) if (*str++ == '.') return true;
 	return false;
@@ -215,7 +215,7 @@ void TBValue::SetFromStringAuto(const char *str, SET set)
 {
 	if (!str)
 		SetNull();
-	else if (p_is_number(str))
+	else if (is_number(str))
 	{
 		// If the number has spaces, we'll assume a list of numbers (example: "10 -4 3.5")
 		if (strstr(str, " "))
@@ -238,7 +238,7 @@ void TBValue::SetFromStringAuto(const char *str, SET set)
 				SetArray(arr, SET_TAKE_OWNERSHIP);
 			}
 		}
-		else if (p_is_number_float(str))
+		else if (is_number_float(str))
 			SetFloat((float)atof(str));
 		else
 			SetInt(atoi(str));
diff --git a/tinkerbell/src/tb_value.h b/tinkerbell/src/tb_value.h
index 9c57a1e..69036ff 100644
--- a/tinkerbell/src/tb_value.h
+++ b/tinkerbell/src/tb_value.h
@@ -13,6 +13,13 @@ namespace tinkerbell {
 
 class TBValue;
 
+/** Return true if the given string starts with a number. */
+bool is_number(const char *str);
+
+/** Return true if the given number string is a float number.
+	Should only be called when you've verified it's a number with is_number(). */
+bool is_number_float(const char *str);
+
 /** TBValueArray is a array of TBValue */
 class TBValueArray
 {
diff --git a/tinkerbell/src/tests/test_tb_parser.cpp b/tinkerbell/src/tests/test_tb_parser.cpp
new file mode 100644
index 0000000..db2bd1e
--- /dev/null
+++ b/tinkerbell/src/tests/test_tb_parser.cpp
@@ -0,0 +1,128 @@
+// ================================================================================
+// == This file is a part of Tinkerbell UI Toolkit. (C) 2011-2012, Emil Seger�s ==
+// ==                   See tinkerbell.h for more information.                   ==
+// ================================================================================
+
+#include "tb_test.h"
+#include "parser/TBNodeTree.h"
+
+#ifdef TB_UNIT_TESTING
+
+using namespace tinkerbell;
+
+TB_TEST_GROUP(tb_parser)
+{
+	TBNode node;
+	TB_TEST(Init)
+	{
+		TB_VERIFY(node.ReadFile(TB_TEST_FILE("test_tb_parser.tb.txt")));
+	}
+
+	TB_TEST(strings)
+	{
+		TB_VERIFY_STR(node.GetValueString("strings>string1", ""), "A string");
+		TB_VERIFY_STR(node.GetValueString("strings>string2", ""), "\"A string\"");
+		TB_VERIFY_STR(node.GetValueString("strings>string3", ""), "\'A string\'");
+		TB_VERIFY_STR(node.GetValueString("strings>string4", ""), "\"\'A string\'\"");
+		TB_VERIFY_STR(node.GetValueString("strings>string5", ""), "Foo\nBar");
+	}
+
+	TB_TEST(strings_compact)
+	{
+		TB_VERIFY_STR(node.GetValueString("strings_compact>string1", ""), "");
+		TB_VERIFY_STR(node.GetValueString("strings_compact>string2", ""), "A string");
+		TB_VERIFY_STR(node.GetValueString("strings_compact>string3", ""), "A string");
+		TB_VERIFY_STR(node.GetValueString("strings_compact>string4", ""), "'A string'");
+		TB_VERIFY_STR(node.GetValueString("strings_compact>string5", ""), "\"A string\"");
+		TB_VERIFY_STR(node.GetValueString("strings_compact>string6", ""), "\"A string\"");
+		TB_VERIFY_STR(node.GetValueString("strings_compact>string7", ""), "\\");
+		TB_VERIFY_STR(node.GetValueString("strings_compact>string8", ""), "\"");
+		TB_VERIFY_STR(node.GetValueString("strings_compact>string9", ""), "\\\\\\\\");
+		TB_VERIFY_STR(node.GetValueString("strings_compact>string10", ""), "\\\\\"");
+		TB_VERIFY_STR(node.GetValueString("strings_compact>string11", ""), "\"\"\'\'");
+		TB_VERIFY_STR(node.GetValueString("strings_compact>string12", ""), "@language_string_token");
+	}
+
+	TB_TEST(numbers_compact)
+	{
+		TB_VERIFY(node.GetValueInt("numbers_compact>integer", 0) == -10);
+		TB_VERIFY_FLOAT(node.GetValueFloat("numbers_compact>float", 0), 1.0);
+	}
+
+	TB_TEST(compact_with_children)
+	{
+		TB_VERIFY_STR(node.GetValueString("compact_with_children>string", ""), "A string");
+		TB_VERIFY_STR(node.GetValueString("compact_with_children>string>child1", ""), "Child 1");
+		TB_VERIFY_STR(node.GetValueString("compact_with_children>string>child2", ""), "Child 2");
+
+		TB_VERIFY(node.GetValueInt("compact_with_children>integer", 0) == -10);
+		TB_VERIFY(node.GetValueInt("compact_with_children>integer>child1", 0) == 1);
+		TB_VERIFY(node.GetValueInt("compact_with_children>integer>child2", 0) == 2);
+
+		TB_VERIFY_FLOAT(node.GetValueFloat("compact_with_children>float", 0), 1);
+		TB_VERIFY_FLOAT(node.GetValueFloat("compact_with_children>float>child1", 0), 1);
+		TB_VERIFY_FLOAT(node.GetValueFloat("compact_with_children>float>child2", 0), 2);
+	}
+
+	TB_TEST(compact_no_value)
+	{
+		TB_VERIFY_STR(node.GetValueString("compact_no_value>string", ""), "A string");
+		TB_VERIFY(node.GetValueInt("compact_no_value>int", 0) == 42);
+		TB_VERIFY_FLOAT(node.GetValueFloat("compact_no_value>float", 0), 3.14);
+		TB_VERIFY_STR(node.GetValueString("compact_no_value>subgroup>string1", ""), "A string, with \"comma\"");
+		TB_VERIFY_STR(node.GetValueString("compact_no_value>subgroup>string2", ""), "'Another string'");
+		TB_VERIFY_STR(node.GetValueString("compact_no_value>subgroup>string3", ""), "And another string");
+	}
+
+	TB_TEST(arrays_numbers)
+	{
+		TBNode *arr_n = node.GetNode("arrays>numbers");
+		TB_VERIFY(arr_n);
+		TB_VERIFY(arr_n->GetValue().GetArrayLength() == 5);
+		TBValueArray *arr = arr_n->GetValue().GetArray();
+		TB_VERIFY(arr->GetValue(0)->GetInt() == 1);
+		TB_VERIFY(arr->GetValue(1)->GetInt() == 2);
+		TB_VERIFY_FLOAT(arr->GetValue(2)->GetFloat(), 0.5);
+		TB_VERIFY_FLOAT(arr->GetValue(3)->GetFloat(), 1.0E-8);
+		TB_VERIFY(arr->GetValue(4)->GetInt() == 1000000000);
+	}
+
+//FIX: Not supported yet
+//	TB_TEST(arrays_strings)
+//	{
+//		TBNode *arr_n = node.GetNode("arrays>strings");
+//		TB_VERIFY(arr_n);
+//		TB_VERIFY(arr_n->GetValue().GetArrayLength() == 5);
+//		TBValueArray *arr = arr_n->GetValue().GetArray();
+//		TB_VERIFY_STR(arr->GetValue(0)->GetString(), "Foo");
+//		TB_VERIFY_STR(arr->GetValue(1)->GetString(), "'Foo'");
+//		TB_VERIFY_STR(arr->GetValue(2)->GetString(), "Foo");
+//		TB_VERIFY_STR(arr->GetValue(3)->GetString(), "\"Foo\"");
+//		TB_VERIFY_STR(arr->GetValue(4)->GetString(), "Foo 'bar'");
+//	}
+//
+//	TB_TEST(arrays_mixed)
+//	{
+//		TBNode *arr_n = node.GetNode("arrays>mixed");
+//		TB_VERIFY(arr_n);
+//		TB_VERIFY(arr_n->GetValue().GetArrayLength() == 4);
+//		TBValueArray *arr = arr_n->GetValue().GetArray();
+//		TB_VERIFY_STR(arr->GetValue(0)->GetString(), "Foo");
+//		TB_VERIFY(arr->GetValue(1)->GetInt() == 2);
+//		TB_VERIFY_STR(arr->GetValue(2)->GetString(), "bar");
+//		TB_VERIFY(arr->GetValue(3)->GetFloat() == 4.0f);
+//	}
+
+	TB_TEST(strings_multiline)
+	{
+		TB_VERIFY_STR(node.GetValueString("strings_multiline>string1", ""), "Line 1\nLine 2\nLine 3");
+		TB_VERIFY_STR(node.GetValueString("strings_multiline>string2", ""), "abc");
+		TB_VERIFY_STR(node.GetValueString("strings_multiline>string3", ""), "AB");
+		TB_VERIFY_STR(node.GetValueString("strings_multiline>string4", ""), "Line 1\nLine 2\nLine 3\n");
+		TB_VERIFY_STR(node.GetValueString("strings_multiline>subgroup>first", ""), "Foo");
+		TB_VERIFY_STR(node.GetValueString("strings_multiline>subgroup>second", ""), "AB");
+		TB_VERIFY_STR(node.GetValueString("strings_multiline>string5", ""), "The last string");
+	}
+}
+
+#endif // TB_UNIT_TESTING
diff --git a/tinkerbell/src/tests/test_tb_parser.tb.txt b/tinkerbell/src/tests/test_tb_parser.tb.txt
new file mode 100644
index 0000000..5043048
--- /dev/null
+++ b/tinkerbell/src/tests/test_tb_parser.tb.txt
@@ -0,0 +1,64 @@
+
+# Test file for the text node system and its parser
+# Everything is a tree of nodes with a name and value.
+# The values may be strings, numbers or arrays.
+
+# If there is no colon, the entire line will be treated as a value and no quotes is needed around strings.
+# Any quotes will be part of the string.
+strings
+	string1 A string
+	string2 "A string"
+	string3 'A string'
+	string4 "'A string'"
+	string5 Foo\nBar
+
+# If there is a colon (compact mode), there must be quotes around strings.
+# First comes the value (optional)
+# Then comes other child nodes (separated by comma)
+strings_compact
+	string1: this should fail
+	string2: "A string"
+	string3: 'A string'
+	string4: "'A string'"
+	string5: '"A string"'
+	string6: "\"A string\""
+	string7: "\\"
+	string8: "\""
+	string9: "\\\\\\\\"
+	string10: "\\\\\""
+	string11: "\"\"\'\'"
+	string12: @language_string_token
+
+numbers_compact
+	integer: -10
+	float: 1.0
+
+compact_with_children
+	string: "A string", child1: "Child 1", child2: "Child 2"
+	integer: -10, child1: 1, child2: 2
+	float: 1.0, child1: 1.0, child2: 2.0
+
+compact_no_value: string: "A string", int: 42, float: 3.14
+	subgroup: string1: "A string, with \"comma\"", string2: "'Another string'"
+		string3: "And another string"
+
+arrays
+	numbers 1 2 .5 1.0E-8 1000000000
+# Not supported yet
+#	strings: "Foo" "'Foo'" 'Foo' '"Foo"' "Foo 'bar'"
+#	mixed: "foo" 2 "bar" 4.0
+
+# Strings can span over multiple lines by ending with a \
+strings_multiline
+	string1: "Line 1\nLine 2\nLine 3"
+	string2:	"a" \
+				"b" \
+				"c"
+	string3:	'A' \
+				'B'
+	string4:	'Line 1\n' \
+				'Line 2\n'\
+				'Line 3\n'
+	subgroup: first: "Foo", second:	"A" \
+									"B"
+	string5: "The last string"
diff --git a/stb_font/vera.ttf b/vera.ttf
similarity index 100%
rename from stb_font/vera.ttf
rename to vera.ttf