浏览代码

Fixes #3011. TextFormatter doesn't handle combining and tab runes. (#3012)

* Add horizontal and vertical support for combining glyphs.

* Fix text and auto size behavior.

* Add TabWidth property.

* Add unit test for WordWrap.

* Add MultiLine property and improve more code.

* Fix word wrap on MessageBox.

* Fix label unit test.

* Rename to GetTextFormatterSizeNeededForTextAndHotKey

* Proves that TextFormatter.Size not must to have the same View.Bounds.Size.

* Fix fails unit tests.

* Updates AutoSize document.

* Updates MultiLine document.

* Removes Application dependency from the TextFormatter class.

* Fix Draw XML comment.
BDisp 1 年之前
父节点
当前提交
3f4d96bec7

+ 297 - 113
Terminal.Gui/Text/TextFormatter.cs

@@ -1,6 +1,5 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.Globalization;
 using System.Linq;
 using System.Linq;
 using System.Text;
 using System.Text;
 
 
@@ -169,6 +168,15 @@ namespace Terminal.Gui {
 			return StringExtensions.ToString (runes);
 			return StringExtensions.ToString (runes);
 		}
 		}
 
 
+		static string ReplaceTABWithSpaces (string str, int tabWidth)
+		{
+			if (tabWidth == 0) {
+				return str.Replace ("\t", "");
+			}
+
+			return str.Replace ("\t", new string (' ', tabWidth));
+		}
+
 		/// <summary>
 		/// <summary>
 		/// Splits all newlines in the <paramref name="text"/> into a list
 		/// Splits all newlines in the <paramref name="text"/> into a list
 		/// and supports both CRLF and LF, preserving the ending newline.
 		/// and supports both CRLF and LF, preserving the ending newline.
@@ -342,13 +350,13 @@ namespace Terminal.Gui {
 					//	}
 					//	}
 					//}
 					//}
 
 
-					while ((end = start + Math.Max (GetLengthThatFits (runes.GetRange (start, runes.Count - start), width), 1)) < runes.Count) {
+					while ((end = start + GetLengthThatFits (runes.GetRange (start, runes.Count - start), width, tabWidth)) < runes.Count) {
 						while (runes [end].Value != ' ' && end > start)
 						while (runes [end].Value != ' ' && end > start)
 							end--;
 							end--;
 						if (end == start)
 						if (end == start)
-							end = start + GetLengthThatFits (runes.GetRange (end, runes.Count - end), width);
+							end = start + GetLengthThatFits (runes.GetRange (end, runes.Count - end), width, tabWidth);
 						var str = StringExtensions.ToString (runes.GetRange (start, end - start));
 						var str = StringExtensions.ToString (runes.GetRange (start, end - start));
-						if (end > start && str.GetColumns () <= width) {
+						if (end > start && GetRuneWidth (str, tabWidth) <= width) {
 							lines.Add (str);
 							lines.Add (str);
 							start = end;
 							start = end;
 							if (runes [end].Value == ' ') {
 							if (runes [end].Value == ' ') {
@@ -368,7 +376,17 @@ namespace Terminal.Gui {
 						if (end == start) {
 						if (end == start) {
 							end = start + width;
 							end = start + width;
 						}
 						}
-						lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start)));
+						var zeroLength = 0;
+						for (int i = end; i < runes.Count - start; i++) {
+							var r = runes [i];
+							if (r.GetColumns () == 0) {
+								zeroLength++;
+							} else {
+								break;
+							}
+						}
+						lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start + zeroLength)));
+						end += zeroLength;
 						start = end;
 						start = end;
 						if (runes [end].Value == ' ') {
 						if (runes [end].Value == ' ') {
 							start++;
 							start++;
@@ -427,7 +445,7 @@ namespace Terminal.Gui {
 			}
 			}
 
 
 			if (start < text.GetRuneCount ()) {
 			if (start < text.GetRuneCount ()) {
-				var str = StringExtensions.ToString (runes.GetRange (start, runes.Count - start));
+				var str = ReplaceTABWithSpaces (StringExtensions.ToString (runes.GetRange (start, runes.Count - start)), tabWidth);
 				if (IsVerticalDirection (textDirection) || preserveTrailingSpaces || (!preserveTrailingSpaces && str.GetColumns () <= width)) {
 				if (IsVerticalDirection (textDirection) || preserveTrailingSpaces || (!preserveTrailingSpaces && str.GetColumns () <= width)) {
 					lines.Add (str);
 					lines.Add (str);
 				}
 				}
@@ -443,10 +461,11 @@ namespace Terminal.Gui {
 		/// <param name="width">The number of columns to clip the text to. Text longer than <paramref name="width"/> will be clipped.</param>
 		/// <param name="width">The number of columns to clip the text to. Text longer than <paramref name="width"/> will be clipped.</param>
 		/// <param name="talign">Alignment.</param>
 		/// <param name="talign">Alignment.</param>
 		/// <param name="textDirection">The text direction.</param>
 		/// <param name="textDirection">The text direction.</param>
+		/// <param name="tabWidth">The number of columns used for a tab.</param>
 		/// <returns>Justified and clipped text.</returns>
 		/// <returns>Justified and clipped text.</returns>
-		public static string ClipAndJustify (string text, int width, TextAlignment talign, TextDirection textDirection = TextDirection.LeftRight_TopBottom)
+		public static string ClipAndJustify (string text, int width, TextAlignment talign, TextDirection textDirection = TextDirection.LeftRight_TopBottom, int tabWidth = 0)
 		{
 		{
-			return ClipAndJustify (text, width, talign == TextAlignment.Justified, textDirection);
+			return ClipAndJustify (text, width, talign == TextAlignment.Justified, textDirection, tabWidth);
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -456,8 +475,9 @@ namespace Terminal.Gui {
 		/// <param name="width">The number of columns to clip the text to. Text longer than <paramref name="width"/> will be clipped.</param>
 		/// <param name="width">The number of columns to clip the text to. Text longer than <paramref name="width"/> will be clipped.</param>
 		/// <param name="justify">Justify.</param>
 		/// <param name="justify">Justify.</param>
 		/// <param name="textDirection">The text direction.</param>
 		/// <param name="textDirection">The text direction.</param>
+		/// <param name="tabWidth">The number of columns used for a tab.</param>
 		/// <returns>Justified and clipped text.</returns>
 		/// <returns>Justified and clipped text.</returns>
-		public static string ClipAndJustify (string text, int width, bool justify, TextDirection textDirection = TextDirection.LeftRight_TopBottom)
+		public static string ClipAndJustify (string text, int width, bool justify, TextDirection textDirection = TextDirection.LeftRight_TopBottom, int tabWidth = 0)
 		{
 		{
 			if (width < 0) {
 			if (width < 0) {
 				throw new ArgumentOutOfRangeException ("Width cannot be negative.");
 				throw new ArgumentOutOfRangeException ("Width cannot be negative.");
@@ -466,19 +486,21 @@ namespace Terminal.Gui {
 				return text;
 				return text;
 			}
 			}
 
 
+			text = ReplaceTABWithSpaces (text, tabWidth);
 			var runes = text.ToRuneList ();
 			var runes = text.ToRuneList ();
 			int slen = runes.Count;
 			int slen = runes.Count;
 			if (slen > width) {
 			if (slen > width) {
 				if (IsHorizontalDirection (textDirection)) {
 				if (IsHorizontalDirection (textDirection)) {
-					return StringExtensions.ToString (runes.GetRange (0, GetLengthThatFits (text, width)));
+					return StringExtensions.ToString (runes.GetRange (0, GetLengthThatFits (text, width, tabWidth)));
 				} else {
 				} else {
-					return StringExtensions.ToString (runes.GetRange (0, width));
+					var zeroLength = runes.Sum (r => r.GetColumns () == 0 ? 1 : 0);
+					return StringExtensions.ToString (runes.GetRange (0, width + zeroLength));
 				}
 				}
 			} else {
 			} else {
 				if (justify) {
 				if (justify) {
-					return Justify (text, width, ' ', textDirection);
-				} else if (IsHorizontalDirection (textDirection) && text.GetColumns () > width) {
-					return StringExtensions.ToString (runes.GetRange (0, GetLengthThatFits (text, width)));
+					return Justify (text, width, ' ', textDirection, tabWidth);
+				} else if (IsHorizontalDirection (textDirection) && GetRuneWidth (text, tabWidth) > width) {
+					return StringExtensions.ToString (runes.GetRange (0, GetLengthThatFits (text, width, tabWidth)));
 				}
 				}
 				return text;
 				return text;
 			}
 			}
@@ -492,8 +514,9 @@ namespace Terminal.Gui {
 		/// <param name="width"></param>
 		/// <param name="width"></param>
 		/// <param name="spaceChar">Character to replace whitespace and pad with. For debugging purposes.</param>
 		/// <param name="spaceChar">Character to replace whitespace and pad with. For debugging purposes.</param>
 		/// <param name="textDirection">The text direction.</param>
 		/// <param name="textDirection">The text direction.</param>
+		/// <param name="tabWidth">The number of columns used for a tab.</param>
 		/// <returns>The justified text.</returns>
 		/// <returns>The justified text.</returns>
-		public static string Justify (string text, int width, char spaceChar = ' ', TextDirection textDirection = TextDirection.LeftRight_TopBottom)
+		public static string Justify (string text, int width, char spaceChar = ' ', TextDirection textDirection = TextDirection.LeftRight_TopBottom, int tabWidth = 0)
 		{
 		{
 			if (width < 0) {
 			if (width < 0) {
 				throw new ArgumentOutOfRangeException ("Width cannot be negative.");
 				throw new ArgumentOutOfRangeException ("Width cannot be negative.");
@@ -502,10 +525,11 @@ namespace Terminal.Gui {
 				return text;
 				return text;
 			}
 			}
 
 
+			text = ReplaceTABWithSpaces (text, tabWidth);
 			var words = text.Split (' ');
 			var words = text.Split (' ');
 			int textCount;
 			int textCount;
 			if (IsHorizontalDirection (textDirection)) {
 			if (IsHorizontalDirection (textDirection)) {
-				textCount = words.Sum (arg => arg.GetColumns ());
+				textCount = words.Sum (arg => GetRuneWidth (arg, tabWidth));
 			} else {
 			} else {
 				textCount = words.Sum (arg => arg.GetRuneCount ());
 				textCount = words.Sum (arg => arg.GetRuneCount ());
 			}
 			}
@@ -532,7 +556,7 @@ namespace Terminal.Gui {
 			return s.ToString ();
 			return s.ToString ();
 		}
 		}
 
 
-		static char [] whitespace = new char [] { ' ', '\t' };
+		//static char [] whitespace = new char [] { ' ', '\t' };
 
 
 		/// <summary>
 		/// <summary>
 		/// Reformats text into lines, applying text alignment and optionally wrapping text to new lines on word boundaries.
 		/// Reformats text into lines, applying text alignment and optionally wrapping text to new lines on word boundaries.
@@ -546,6 +570,7 @@ namespace Terminal.Gui {
 		///  If <see langword="false"/>, trailing spaces at the end of wrapped lines will be trimmed.</param>
 		///  If <see langword="false"/>, trailing spaces at the end of wrapped lines will be trimmed.</param>
 		/// <param name="tabWidth">The number of columns used for a tab.</param>
 		/// <param name="tabWidth">The number of columns used for a tab.</param>
 		/// <param name="textDirection">The text direction.</param>
 		/// <param name="textDirection">The text direction.</param>
+		/// <param name="multiLine">If <see langword="true"/> new lines are allowed.</param>
 		/// <returns>A list of word wrapped lines.</returns>
 		/// <returns>A list of word wrapped lines.</returns>
 		/// <remarks>
 		/// <remarks>
 		/// <para>
 		/// <para>
@@ -558,9 +583,9 @@ namespace Terminal.Gui {
 		/// If <paramref name="width"/> is int.MaxValue, the text will be formatted to the maximum width possible. 
 		/// If <paramref name="width"/> is int.MaxValue, the text will be formatted to the maximum width possible. 
 		/// </para>
 		/// </para>
 		/// </remarks>
 		/// </remarks>
-		public static List<string> Format (string text, int width, TextAlignment talign, bool wordWrap, bool preserveTrailingSpaces = false, int tabWidth = 0, TextDirection textDirection = TextDirection.LeftRight_TopBottom)
+		public static List<string> Format (string text, int width, TextAlignment talign, bool wordWrap, bool preserveTrailingSpaces = false, int tabWidth = 0, TextDirection textDirection = TextDirection.LeftRight_TopBottom, bool multiLine = false)
 		{
 		{
-			return Format (text, width, talign == TextAlignment.Justified, wordWrap, preserveTrailingSpaces, tabWidth, textDirection);
+			return Format (text, width, talign == TextAlignment.Justified, wordWrap, preserveTrailingSpaces, tabWidth, textDirection, multiLine);
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -575,6 +600,7 @@ namespace Terminal.Gui {
 		///  If <see langword="false"/>, trailing spaces at the end of wrapped lines will be trimmed.</param>
 		///  If <see langword="false"/>, trailing spaces at the end of wrapped lines will be trimmed.</param>
 		/// <param name="tabWidth">The number of columns used for a tab.</param>
 		/// <param name="tabWidth">The number of columns used for a tab.</param>
 		/// <param name="textDirection">The text direction.</param>
 		/// <param name="textDirection">The text direction.</param>
+		/// <param name="multiLine">If <see langword="true"/> new lines are allowed.</param>
 		/// <returns>A list of word wrapped lines.</returns>
 		/// <returns>A list of word wrapped lines.</returns>
 		/// <remarks>
 		/// <remarks>
 		/// <para>
 		/// <para>
@@ -588,7 +614,7 @@ namespace Terminal.Gui {
 		/// </para>
 		/// </para>
 		/// </remarks>
 		/// </remarks>
 		public static List<string> Format (string text, int width, bool justify, bool wordWrap,
 		public static List<string> Format (string text, int width, bool justify, bool wordWrap,
-			bool preserveTrailingSpaces = false, int tabWidth = 0, TextDirection textDirection = TextDirection.LeftRight_TopBottom)
+			bool preserveTrailingSpaces = false, int tabWidth = 0, TextDirection textDirection = TextDirection.LeftRight_TopBottom, bool multiLine = false)
 		{
 		{
 			if (width < 0) {
 			if (width < 0) {
 				throw new ArgumentOutOfRangeException ("width cannot be negative");
 				throw new ArgumentOutOfRangeException ("width cannot be negative");
@@ -600,10 +626,27 @@ namespace Terminal.Gui {
 				return lineResult;
 				return lineResult;
 			}
 			}
 
 
-			if (wordWrap == false) {
-				text = ReplaceCRLFWithSpace (text);
-				lineResult.Add (ClipAndJustify (text, width, justify, textDirection));
-				return lineResult;
+			if (!wordWrap) {
+				text = ReplaceTABWithSpaces (text, tabWidth);
+				if (multiLine) {
+					string [] lines = null;
+					if (text.Contains ("\r\n")) {
+						lines = text.Split ("\r\n");
+					} else if (text.Contains ('\n')) {
+						lines = text.Split ('\n');
+					}
+					if (lines == null) {
+						lines = new [] { text };
+					}
+					foreach (var line in lines) {
+						lineResult.Add (ClipAndJustify (line, width, justify, textDirection, tabWidth));
+					}
+					return lineResult;
+				} else {
+					text = ReplaceCRLFWithSpace (text);
+					lineResult.Add (ClipAndJustify (text, width, justify, textDirection, tabWidth));
+					return lineResult;
+				}
 			}
 			}
 
 
 			var runes = StripCRLF (text, true).ToRuneList ();
 			var runes = StripCRLF (text, true).ToRuneList ();
@@ -614,7 +657,7 @@ namespace Terminal.Gui {
 				if (c.Value == '\n') {
 				if (c.Value == '\n') {
 					var wrappedLines = WordWrapText (StringExtensions.ToString (runes.GetRange (lp, i - lp)), width, preserveTrailingSpaces, tabWidth, textDirection);
 					var wrappedLines = WordWrapText (StringExtensions.ToString (runes.GetRange (lp, i - lp)), width, preserveTrailingSpaces, tabWidth, textDirection);
 					foreach (var line in wrappedLines) {
 					foreach (var line in wrappedLines) {
-						lineResult.Add (ClipAndJustify (line, width, justify, textDirection));
+						lineResult.Add (ClipAndJustify (line, width, justify, textDirection, tabWidth));
 					}
 					}
 					if (wrappedLines.Count == 0) {
 					if (wrappedLines.Count == 0) {
 						lineResult.Add (string.Empty);
 						lineResult.Add (string.Empty);
@@ -623,7 +666,7 @@ namespace Terminal.Gui {
 				}
 				}
 			}
 			}
 			foreach (var line in WordWrapText (StringExtensions.ToString (runes.GetRange (lp, runeCount - lp)), width, preserveTrailingSpaces, tabWidth, textDirection)) {
 			foreach (var line in WordWrapText (StringExtensions.ToString (runes.GetRange (lp, runeCount - lp)), width, preserveTrailingSpaces, tabWidth, textDirection)) {
-				lineResult.Add (ClipAndJustify (line, width, justify, textDirection));
+				lineResult.Add (ClipAndJustify (line, width, justify, textDirection, tabWidth));
 			}
 			}
 
 
 			return lineResult;
 			return lineResult;
@@ -648,13 +691,14 @@ namespace Terminal.Gui {
 		/// <returns>Width of the longest line after formatting the text constrained by <paramref name="maxColumns"/>.</returns>
 		/// <returns>Width of the longest line after formatting the text constrained by <paramref name="maxColumns"/>.</returns>
 		/// <param name="text">Text, may contain newlines.</param>
 		/// <param name="text">Text, may contain newlines.</param>
 		/// <param name="maxColumns">The number of columns to constrain the text to for formatting.</param>
 		/// <param name="maxColumns">The number of columns to constrain the text to for formatting.</param>
-		public static int MaxWidth (string text, int maxColumns)
+		/// <param name="tabWidth">The number of columns used for a tab.</param>
+		public static int MaxWidth (string text, int maxColumns, int tabWidth = 0)
 		{
 		{
 			var result = TextFormatter.Format (text: text, width: maxColumns, justify: false, wordWrap: true);
 			var result = TextFormatter.Format (text: text, width: maxColumns, justify: false, wordWrap: true);
 			var max = 0;
 			var max = 0;
 			result.ForEach (s => {
 			result.ForEach (s => {
 				var m = 0;
 				var m = 0;
-				s.ToRuneList ().ForEach (r => m += Math.Max (r.GetColumns (), 1));
+				s.ToRuneList ().ForEach (r => m += GetRuneWidth (r, tabWidth));
 				if (m > max) {
 				if (m > max) {
 					max = m;
 					max = m;
 				}
 				}
@@ -667,11 +711,12 @@ namespace Terminal.Gui {
 		/// <paramref name="text"/> if it contains newlines.
 		/// <paramref name="text"/> if it contains newlines.
 		/// </summary>
 		/// </summary>
 		/// <param name="text">Text, may contain newlines.</param>
 		/// <param name="text">Text, may contain newlines.</param>
+		/// <param name="tabWidth">The number of columns used for a tab.</param>
 		/// <returns>The length of the longest line.</returns>
 		/// <returns>The length of the longest line.</returns>
-		public static int MaxWidthLine (string text)
+		public static int MaxWidthLine (string text, int tabWidth = 0)
 		{
 		{
 			var result = TextFormatter.SplitNewLine (text);
 			var result = TextFormatter.SplitNewLine (text);
-			return result.Max (x => x.GetColumns ());
+			return result.Max (x => GetRuneWidth (x, tabWidth));
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -681,14 +726,15 @@ namespace Terminal.Gui {
 		/// <param name="lines">The lines.</param>
 		/// <param name="lines">The lines.</param>
 		/// <param name="startIndex">The start index.</param>
 		/// <param name="startIndex">The start index.</param>
 		/// <param name="length">The length.</param>
 		/// <param name="length">The length.</param>
+		/// <param name="tabWidth">The number of columns used for a tab.</param>
 		/// <returns>The maximum characters width.</returns>
 		/// <returns>The maximum characters width.</returns>
-		public static int GetSumMaxCharWidth (List<string> lines, int startIndex = -1, int length = -1)
+		public static int GetSumMaxCharWidth (List<string> lines, int startIndex = -1, int length = -1, int tabWidth = 0)
 		{
 		{
 			var max = 0;
 			var max = 0;
 			for (int i = (startIndex == -1 ? 0 : startIndex); i < (length == -1 ? lines.Count : startIndex + length); i++) {
 			for (int i = (startIndex == -1 ? 0 : startIndex); i < (length == -1 ? lines.Count : startIndex + length); i++) {
 				var runes = lines [i];
 				var runes = lines [i];
 				if (runes.Length > 0)
 				if (runes.Length > 0)
-					max += runes.EnumerateRunes ().Max (r => Math.Max (r.GetColumns (), 1));
+					max += runes.EnumerateRunes ().Max (r => GetRuneWidth (r, tabWidth));
 			}
 			}
 			return max;
 			return max;
 		}
 		}
@@ -700,13 +746,14 @@ namespace Terminal.Gui {
 		/// <param name="text">The text.</param>
 		/// <param name="text">The text.</param>
 		/// <param name="startIndex">The start index.</param>
 		/// <param name="startIndex">The start index.</param>
 		/// <param name="length">The length.</param>
 		/// <param name="length">The length.</param>
+		/// <param name="tabWidth">The number of columns used for a tab.</param>
 		/// <returns>The maximum characters width.</returns>
 		/// <returns>The maximum characters width.</returns>
-		public static int GetSumMaxCharWidth (string text, int startIndex = -1, int length = -1)
+		public static int GetSumMaxCharWidth (string text, int startIndex = -1, int length = -1, int tabWidth = 0)
 		{
 		{
 			var max = 0;
 			var max = 0;
 			var runes = text.ToRunes ();
 			var runes = text.ToRunes ();
 			for (int i = (startIndex == -1 ? 0 : startIndex); i < (length == -1 ? runes.Length : startIndex + length); i++) {
 			for (int i = (startIndex == -1 ? 0 : startIndex); i < (length == -1 ? runes.Length : startIndex + length); i++) {
-				max += Math.Max (runes [i].GetColumns (), 1);
+				max += GetRuneWidth (runes [i], tabWidth);
 			}
 			}
 			return max;
 			return max;
 		}
 		}
@@ -716,16 +763,18 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		/// <param name="text">The text.</param>
 		/// <param name="text">The text.</param>
 		/// <param name="columns">The width.</param>
 		/// <param name="columns">The width.</param>
+		/// <param name="tabWidth">The number of columns used for a tab.</param>
 		/// <returns>The index of the text that fit the width.</returns>
 		/// <returns>The index of the text that fit the width.</returns>
-		public static int GetLengthThatFits (string text, int columns) => GetLengthThatFits (text?.ToRuneList (), columns);
+		public static int GetLengthThatFits (string text, int columns, int tabWidth = 0) => GetLengthThatFits (text?.ToRuneList (), columns, tabWidth);
 
 
 		/// <summary>
 		/// <summary>
 		/// Gets the number of the Runes in a list of Runes that will fit in <paramref name="columns"/>.
 		/// Gets the number of the Runes in a list of Runes that will fit in <paramref name="columns"/>.
 		/// </summary>
 		/// </summary>
 		/// <param name="runes">The list of runes.</param>
 		/// <param name="runes">The list of runes.</param>
 		/// <param name="columns">The width.</param>
 		/// <param name="columns">The width.</param>
+		/// <param name="tabWidth">The number of columns used for a tab.</param>
 		/// <returns>The index of the last Rune in <paramref name="runes"/> that fit in <paramref name="columns"/>.</returns>
 		/// <returns>The index of the last Rune in <paramref name="runes"/> that fit in <paramref name="columns"/>.</returns>
-		public static int GetLengthThatFits (List<Rune> runes, int columns)
+		public static int GetLengthThatFits (List<Rune> runes, int columns, int tabWidth = 0)
 		{
 		{
 			if (runes == null || runes.Count == 0) {
 			if (runes == null || runes.Count == 0) {
 				return 0;
 				return 0;
@@ -734,7 +783,7 @@ namespace Terminal.Gui {
 			var runesLength = 0;
 			var runesLength = 0;
 			var runeIdx = 0;
 			var runeIdx = 0;
 			for (; runeIdx < runes.Count; runeIdx++) {
 			for (; runeIdx < runes.Count; runeIdx++) {
-				var runeWidth = Math.Max (runes [runeIdx].GetColumns (), 1);
+				var runeWidth = GetRuneWidth (runes [runeIdx], tabWidth);
 				if (runesLength + runeWidth > columns) {
 				if (runesLength + runeWidth > columns) {
 					break;
 					break;
 				}
 				}
@@ -743,20 +792,44 @@ namespace Terminal.Gui {
 			return runeIdx;
 			return runeIdx;
 		}
 		}
 
 
+		private static int GetRuneWidth (string str, int tabWidth)
+		{
+			return GetRuneWidth (str.EnumerateRunes ().ToList (), tabWidth);
+		}
+
+		private static int GetRuneWidth (List<Rune> runes, int tabWidth)
+		{
+			return runes.Sum (r => GetRuneWidth (r, tabWidth));
+		}
+
+		private static int GetRuneWidth (Rune rune, int tabWidth)
+		{
+			var runeWidth = rune.GetColumns ();
+			if (rune.Value == '\t') {
+				return tabWidth;
+			}
+			if (runeWidth < 0 || runeWidth > 0) {
+				return Math.Max (runeWidth, 1);
+			}
+
+			return runeWidth;
+		}
+
 		/// <summary>
 		/// <summary>
 		/// Gets the index position from the list based on the <paramref name="width"/>.
 		/// Gets the index position from the list based on the <paramref name="width"/>.
 		/// </summary>
 		/// </summary>
 		/// <param name="lines">The lines.</param>
 		/// <param name="lines">The lines.</param>
 		/// <param name="width">The width.</param>
 		/// <param name="width">The width.</param>
+		/// <param name="tabWidth">The number of columns used for a tab.</param>
 		/// <returns>The index of the list that fit the width.</returns>
 		/// <returns>The index of the list that fit the width.</returns>
-		public static int GetMaxColsForWidth (List<string> lines, int width)
+		public static int GetMaxColsForWidth (List<string> lines, int width, int tabWidth = 0)
 		{
 		{
 			var runesLength = 0;
 			var runesLength = 0;
 			var lineIdx = 0;
 			var lineIdx = 0;
 			for (; lineIdx < lines.Count; lineIdx++) {
 			for (; lineIdx < lines.Count; lineIdx++) {
 				var runes = lines [lineIdx].ToRuneList ();
 				var runes = lines [lineIdx].ToRuneList ();
 				var maxRruneWidth = runes.Count > 0
 				var maxRruneWidth = runes.Count > 0
-					? runes.Max (r => Math.Max (r.GetColumns (), 1)) : 1;
+					? runes.Max (r => GetRuneWidth (r, tabWidth)) : 1;
 				if (runesLength + maxRruneWidth > width) {
 				if (runesLength + maxRruneWidth > width) {
 					break;
 					break;
 				}
 				}
@@ -772,8 +845,9 @@ namespace Terminal.Gui {
 		/// <param name="y">The y location of the rectangle</param>
 		/// <param name="y">The y location of the rectangle</param>
 		/// <param name="text">The text to measure</param>
 		/// <param name="text">The text to measure</param>
 		/// <param name="direction">The text direction.</param>
 		/// <param name="direction">The text direction.</param>
+		/// <param name="tabWidth">The number of columns used for a tab.</param>
 		/// <returns></returns>
 		/// <returns></returns>
-		public static Rect CalcRect (int x, int y, string text, TextDirection direction = TextDirection.LeftRight_TopBottom)
+		public static Rect CalcRect (int x, int y, string text, TextDirection direction = TextDirection.LeftRight_TopBottom, int tabWidth = 0)
 		{
 		{
 			if (string.IsNullOrEmpty (text)) {
 			if (string.IsNullOrEmpty (text)) {
 				return new Rect (new Point (x, y), Size.Empty);
 				return new Rect (new Point (x, y), Size.Empty);
@@ -795,9 +869,16 @@ namespace Terminal.Gui {
 						cols = 0;
 						cols = 0;
 					} else if (rune.Value != '\r') {
 					} else if (rune.Value != '\r') {
 						cols++;
 						cols++;
-						var rw = ((Rune)rune).GetColumns ();
-						if (rw > 0) {
-							rw--;
+						var rw = 0;
+						if (rune.Value == '\t') {
+							rw += tabWidth - 1;
+						} else {
+							rw = ((Rune)rune).GetColumns ();
+							if (rw > 0) {
+								rw--;
+							} else if (rw == 0) {
+								cols--;
+							}
 						}
 						}
 						cols += rw;
 						cols += rw;
 					}
 					}
@@ -822,10 +903,18 @@ namespace Terminal.Gui {
 						cw = 1;
 						cw = 1;
 					} else if (rune.Value != '\r') {
 					} else if (rune.Value != '\r') {
 						rows++;
 						rows++;
-						var rw = ((Rune)rune).GetColumns ();
-						if (cw < rw) {
-							cw = rw;
-							vw++;
+						var rw = 0;
+						if (rune.Value == '\t') {
+							rw += tabWidth - 1;
+							rows += rw;
+						} else {
+							rw = ((Rune)rune).GetColumns ();
+							if (rw == 0) {
+								rows--;
+							} else if (cw < rw) {
+								cw = rw;
+								vw++;
+							}
 						}
 						}
 					}
 					}
 				}
 				}
@@ -954,13 +1043,18 @@ namespace Terminal.Gui {
 		#endregion // Static Members
 		#endregion // Static Members
 
 
 		List<string> _lines = new List<string> ();
 		List<string> _lines = new List<string> ();
-		string _text;
+		string _text = null;
 		TextAlignment _textAlignment;
 		TextAlignment _textAlignment;
 		VerticalTextAlignment _textVerticalAlignment;
 		VerticalTextAlignment _textVerticalAlignment;
 		TextDirection _textDirection;
 		TextDirection _textDirection;
 		Key _hotKey;
 		Key _hotKey;
 		int _hotKeyPos = -1;
 		int _hotKeyPos = -1;
 		Size _size;
 		Size _size;
+		private bool _autoSize;
+		private bool _preserveTrailingSpaces;
+		private int _tabWidth = 4;
+		private bool _wordWrap = true;
+		private bool _multiLine;
 
 
 		/// <summary>
 		/// <summary>
 		/// Event invoked when the <see cref="HotKey"/> is changed.
 		/// Event invoked when the <see cref="HotKey"/> is changed.
@@ -973,15 +1067,18 @@ namespace Terminal.Gui {
 		public virtual string Text {
 		public virtual string Text {
 			get => _text;
 			get => _text;
 			set {
 			set {
-				_text = value;
+				var textWasNull = _text == null && value != null;
+				_text = EnableNeedsFormat (value);
 
 
-				if (_text != null && _text.GetRuneCount () > 0 && (Size.Width == 0 || Size.Height == 0 || Size.Width != _text.GetColumns ())) {
-					// Provide a default size (width = length of longest line, height = 1)
-					// TODO: It might makes more sense for the default to be width = length of first line?
-					Size = new Size (TextFormatter.MaxWidth (Text, int.MaxValue), 1);
+				if ((AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) || (textWasNull && Size.IsEmpty)) {
+					Size = CalcRect (0, 0, _text, _textDirection, TabWidth).Size;
 				}
 				}
 
 
-				NeedsFormat = true;
+				//if (_text != null && _text.GetRuneCount () > 0 && (Size.Width == 0 || Size.Height == 0 || Size.Width != _text.GetColumns ())) {
+				//	// Provide a default size (width = length of longest line, height = 1)
+				//	// TODO: It might makes more sense for the default to be width = length of first line?
+				//	Size = new Size (TextFormatter.MaxWidth (Text, int.MaxValue), 1);
+				//}
 			}
 			}
 		}
 		}
 
 
@@ -991,7 +1088,18 @@ namespace Terminal.Gui {
 		///   <see cref="LayoutStyle.Absolute"/> values and doesn't work with <see cref="LayoutStyle.Computed"/> layout,
 		///   <see cref="LayoutStyle.Absolute"/> values and doesn't work with <see cref="LayoutStyle.Computed"/> layout,
 		///   to avoid breaking the <see cref="Pos"/> and <see cref="Dim"/> settings.
 		///   to avoid breaking the <see cref="Pos"/> and <see cref="Dim"/> settings.
 		/// </summary>
 		/// </summary>
-		public bool AutoSize { get; set; }
+		/// <remarks>
+		///   Auto size is ignored if the <see cref="TextAlignment.Justified"/> and <see cref="VerticalTextAlignment.Justified"/> are used.
+		/// </remarks>
+		public bool AutoSize {
+			get => _autoSize;
+			set {
+				_autoSize = EnableNeedsFormat (value);
+				if (_autoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) {
+					Size = CalcRect (0, 0, Text, _textDirection, TabWidth).Size;
+				}
+			}
+		}
 
 
 		/// <summary>
 		/// <summary>
 		/// Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved
 		/// Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved
@@ -999,7 +1107,10 @@ namespace Terminal.Gui {
 		/// If <see langword="true"/> trailing spaces at the end of wrapped lines will be removed when 
 		/// If <see langword="true"/> trailing spaces at the end of wrapped lines will be removed when 
 		/// <see cref="Text"/> is formatted for display. The default is <see langword="false"/>.
 		/// <see cref="Text"/> is formatted for display. The default is <see langword="false"/>.
 		/// </summary>
 		/// </summary>
-		public bool PreserveTrailingSpaces { get; set; }
+		public bool PreserveTrailingSpaces {
+			get => _preserveTrailingSpaces;
+			set => _preserveTrailingSpaces = EnableNeedsFormat (value);
+		}
 
 
 		/// <summary>
 		/// <summary>
 		/// Controls the horizontal text-alignment property.
 		/// Controls the horizontal text-alignment property.
@@ -1007,10 +1118,7 @@ namespace Terminal.Gui {
 		/// <value>The text alignment.</value>
 		/// <value>The text alignment.</value>
 		public TextAlignment Alignment {
 		public TextAlignment Alignment {
 			get => _textAlignment;
 			get => _textAlignment;
-			set {
-				_textAlignment = value;
-				NeedsFormat = true;
-			}
+			set => _textAlignment = EnableNeedsFormat (value);
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -1019,10 +1127,7 @@ namespace Terminal.Gui {
 		/// <value>The text vertical alignment.</value>
 		/// <value>The text vertical alignment.</value>
 		public VerticalTextAlignment VerticalAlignment {
 		public VerticalTextAlignment VerticalAlignment {
 			get => _textVerticalAlignment;
 			get => _textVerticalAlignment;
-			set {
-				_textVerticalAlignment = value;
-				NeedsFormat = true;
-			}
+			set => _textVerticalAlignment = EnableNeedsFormat (value);
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -1031,10 +1136,7 @@ namespace Terminal.Gui {
 		/// <value>The text vertical alignment.</value>
 		/// <value>The text vertical alignment.</value>
 		public TextDirection Direction {
 		public TextDirection Direction {
 			get => _textDirection;
 			get => _textDirection;
-			set {
-				_textDirection = value;
-				NeedsFormat = true;
-			}
+			set => _textDirection = EnableNeedsFormat (value);
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -1097,11 +1199,13 @@ namespace Terminal.Gui {
 			}
 			}
 		}
 		}
 
 
-		// TODO: This is not implemented!
 		/// <summary>
 		/// <summary>
-		/// 
+		/// Allows word wrap the to fit the available container width.
 		/// </summary>
 		/// </summary>
-		public bool WordWrap { get; set; } = false;
+		public bool WordWrap {
+			get => _wordWrap;
+			set => _wordWrap = EnableNeedsFormat (value);
+		}
 
 
 		/// <summary>
 		/// <summary>
 		/// Gets or sets the size of the area the text will be constrained to when formatted.
 		/// Gets or sets the size of the area the text will be constrained to when formatted.
@@ -1110,12 +1214,13 @@ namespace Terminal.Gui {
 		/// Does not return the size the formatted text; just the value that was set.
 		/// Does not return the size the formatted text; just the value that was set.
 		/// </remarks>
 		/// </remarks>
 		public Size Size {
 		public Size Size {
-			get {
-				return _size;
-			}
+			get => _size;
 			set {
 			set {
-				_size = value;
-				NeedsFormat = true;
+				if (AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) {
+					_size = EnableNeedsFormat (CalcRect (0, 0, Text, _textDirection, TabWidth).Size);
+				} else {
+					_size = EnableNeedsFormat (value);
+				}
 			}
 			}
 		}
 		}
 
 
@@ -1127,7 +1232,7 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// <summary>
 		/// The position in the text of the hotkey. The hotkey will be rendered using the hot color.
 		/// The position in the text of the hotkey. The hotkey will be rendered using the hot color.
 		/// </summary>
 		/// </summary>
-		public int HotKeyPos { get => _hotKeyPos; set => _hotKeyPos = value; }
+		public int HotKeyPos { get => _hotKeyPos; internal set => _hotKeyPos = value; }
 
 
 		/// <summary>
 		/// <summary>
 		/// Gets the hotkey. Will be an upper case letter or digit.
 		/// Gets the hotkey. Will be an upper case letter or digit.
@@ -1146,7 +1251,7 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// <summary>
 		/// Gets the cursor position from <see cref="HotKey"/>. If the <see cref="HotKey"/> is defined, the cursor will be positioned over it.
 		/// Gets the cursor position from <see cref="HotKey"/>. If the <see cref="HotKey"/> is defined, the cursor will be positioned over it.
 		/// </summary>
 		/// </summary>
-		public int CursorPosition { get; set; }
+		public int CursorPosition { get; internal set; }
 
 
 		/// <summary>
 		/// <summary>
 		/// Gets the size required to hold the formatted text, given the constraints placed by <see cref="Size"/>.
 		/// Gets the size required to hold the formatted text, given the constraints placed by <see cref="Size"/>.
@@ -1169,7 +1274,7 @@ namespace Terminal.Gui {
 		/// <remarks>
 		/// <remarks>
 		/// <para>
 		/// <para>
 		/// Upon a 'get' of this property, if the text needs to be formatted (if <see cref="NeedsFormat"/> is <c>true</c>)
 		/// Upon a 'get' of this property, if the text needs to be formatted (if <see cref="NeedsFormat"/> is <c>true</c>)
-		/// <see cref="Format(string, int, bool, bool, bool, int, TextDirection)"/> will be called internally. 
+		/// <see cref="Format(string, int, bool, bool, bool, int, TextDirection, bool)"/> will be called internally. 
 		/// </para>
 		/// </para>
 		/// </remarks>
 		/// </remarks>
 		public List<string> Lines {
 		public List<string> Lines {
@@ -1192,18 +1297,18 @@ namespace Terminal.Gui {
 					}
 					}
 
 
 					if (IsVerticalDirection (_textDirection)) {
 					if (IsVerticalDirection (_textDirection)) {
-						var colsWidth = GetSumMaxCharWidth (shown_text, 0, 1);
-						_lines = Format (shown_text, Size.Height, _textVerticalAlignment == VerticalTextAlignment.Justified, Size.Width > colsWidth,
-							PreserveTrailingSpaces, 0, _textDirection);
+						var colsWidth = GetSumMaxCharWidth (shown_text, 0, 1, TabWidth);
+						_lines = Format (shown_text, Size.Height, VerticalAlignment == VerticalTextAlignment.Justified, Size.Width > colsWidth && WordWrap,
+							PreserveTrailingSpaces, TabWidth, Direction, MultiLine);
 						if (!AutoSize) {
 						if (!AutoSize) {
-							colsWidth = GetMaxColsForWidth (_lines, Size.Width);
+							colsWidth = GetMaxColsForWidth (_lines, Size.Width, TabWidth);
 							if (_lines.Count > colsWidth) {
 							if (_lines.Count > colsWidth) {
 								_lines.RemoveRange (colsWidth, _lines.Count - colsWidth);
 								_lines.RemoveRange (colsWidth, _lines.Count - colsWidth);
 							}
 							}
 						}
 						}
 					} else {
 					} else {
-						_lines = Format (shown_text, Size.Width, _textAlignment == TextAlignment.Justified, Size.Height > 1,
-							PreserveTrailingSpaces, 0, _textDirection);
+						_lines = Format (shown_text, Size.Width, Alignment == TextAlignment.Justified, Size.Height > 1 && WordWrap,
+							PreserveTrailingSpaces, TabWidth, Direction, MultiLine);
 						if (!AutoSize && _lines.Count > Size.Height) {
 						if (!AutoSize && _lines.Count > Size.Height) {
 							_lines.RemoveRange (Size.Height, _lines.Count - Size.Height);
 							_lines.RemoveRange (Size.Height, _lines.Count - Size.Height);
 						}
 						}
@@ -1216,7 +1321,7 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// Gets or sets whether the <see cref="TextFormatter"/> needs to format the text when <see cref="Draw(Rect, Attribute, Attribute, Rect, bool)"/> is called.
+		/// Gets or sets whether the <see cref="TextFormatter"/> needs to format the text when <see cref="Draw(Rect, Attribute, Attribute, Rect, bool, ConsoleDriver)"/> is called.
 		/// If it is <c>false</c> when Draw is called, the Draw call will be faster.
 		/// If it is <c>false</c> when Draw is called, the Draw call will be faster.
 		/// </summary>
 		/// </summary>
 		/// <remarks>
 		/// <remarks>
@@ -1226,6 +1331,33 @@ namespace Terminal.Gui {
 		/// </remarks>
 		/// </remarks>
 		public bool NeedsFormat { get; set; }
 		public bool NeedsFormat { get; set; }
 
 
+		/// <summary>
+		/// Gets or sets the number of columns used for a tab.
+		/// </summary>
+		public int TabWidth {
+			get => _tabWidth;
+			set => _tabWidth = EnableNeedsFormat (value);
+		}
+
+		/// <summary>
+		/// Gets or sets a value indicating whether multi line is allowed.
+		/// </summary>
+		/// <remarks>
+		///   Multi line is ignored if <see cref="WordWrap"/> is <see langword="true"/>.
+		/// </remarks>
+		public bool MultiLine {
+			get => _multiLine;
+			set {
+				_multiLine = EnableNeedsFormat (value);
+			}
+		}
+
+		private T EnableNeedsFormat<T> (T value)
+		{
+			NeedsFormat = true;
+			return value;
+		}
+
 		/// <summary>
 		/// <summary>
 		/// Causes the <see cref="TextFormatter"/> to reformat the text. 
 		/// Causes the <see cref="TextFormatter"/> to reformat the text. 
 		/// </summary>
 		/// </summary>
@@ -1235,27 +1367,31 @@ namespace Terminal.Gui {
 			var sb = new StringBuilder ();
 			var sb = new StringBuilder ();
 			// Lines_get causes a Format
 			// Lines_get causes a Format
 			foreach (var line in Lines) {
 			foreach (var line in Lines) {
-				sb.AppendLine (line.ToString ());
+				sb.AppendLine (line);
 			}
 			}
 			return sb.ToString ();
 			return sb.ToString ();
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// Draws the text held by <see cref="TextFormatter"/> to <see cref="Application.Driver"/> using the colors specified.
+		/// Draws the text held by <see cref="TextFormatter"/> to <see cref="ConsoleDriver"/> using the colors specified.
 		/// </summary>
 		/// </summary>
 		/// <param name="bounds">Specifies the screen-relative location and maximum size for drawing the text.</param>
 		/// <param name="bounds">Specifies the screen-relative location and maximum size for drawing the text.</param>
 		/// <param name="normalColor">The color to use for all text except the hotkey</param>
 		/// <param name="normalColor">The color to use for all text except the hotkey</param>
 		/// <param name="hotColor">The color to use to draw the hotkey</param>
 		/// <param name="hotColor">The color to use to draw the hotkey</param>
 		/// <param name="containerBounds">Specifies the screen-relative location and maximum container size.</param>
 		/// <param name="containerBounds">Specifies the screen-relative location and maximum container size.</param>
 		/// <param name="fillRemaining">Determines if the bounds width will be used (default) or only the text width will be used.</param>
 		/// <param name="fillRemaining">Determines if the bounds width will be used (default) or only the text width will be used.</param>
-		public void Draw (Rect bounds, Attribute normalColor, Attribute hotColor, Rect containerBounds = default, bool fillRemaining = true)
+		/// <param name="driver">The console driver currently used by the application.</param>
+		public void Draw (Rect bounds, Attribute normalColor, Attribute hotColor, Rect containerBounds = default, bool fillRemaining = true, ConsoleDriver driver = null)
 		{
 		{
 			// With this check, we protect against subclasses with overrides of Text (like Button)
 			// With this check, we protect against subclasses with overrides of Text (like Button)
 			if (string.IsNullOrEmpty (_text)) {
 			if (string.IsNullOrEmpty (_text)) {
 				return;
 				return;
 			}
 			}
 
 
-			Application.Driver?.SetAttribute (normalColor);
+			if (driver == null) {
+				driver = Application.Driver;
+			}
+			driver?.SetAttribute (normalColor);
 
 
 			// Use "Lines" to ensure a Format (don't use "lines"))
 			// Use "Lines" to ensure a Format (don't use "lines"))
 
 
@@ -1271,7 +1407,7 @@ namespace Terminal.Gui {
 
 
 			var isVertical = IsVerticalDirection (_textDirection);
 			var isVertical = IsVerticalDirection (_textDirection);
 			var maxBounds = bounds;
 			var maxBounds = bounds;
-			if (Application.Driver != null) {
+			if (driver != null) {
 				maxBounds = containerBounds == default
 				maxBounds = containerBounds == default
 					? bounds
 					? bounds
 					: new Rect (Math.Max (containerBounds.X, bounds.X),
 					: new Rect (Math.Max (containerBounds.X, bounds.X),
@@ -1316,7 +1452,7 @@ namespace Terminal.Gui {
 				// Horizontal Alignment
 				// Horizontal Alignment
 				if (_textAlignment == TextAlignment.Right || (_textAlignment == TextAlignment.Justified && !IsLeftToRight (_textDirection))) {
 				if (_textAlignment == TextAlignment.Right || (_textAlignment == TextAlignment.Justified && !IsLeftToRight (_textDirection))) {
 					if (isVertical) {
 					if (isVertical) {
-						var runesWidth = GetSumMaxCharWidth (Lines, line);
+						var runesWidth = GetSumMaxCharWidth (Lines, line, TabWidth);
 						x = bounds.Right - runesWidth;
 						x = bounds.Right - runesWidth;
 						CursorPosition = bounds.Width - runesWidth + (_hotKeyPos > -1 ? _hotKeyPos : 0);
 						CursorPosition = bounds.Width - runesWidth + (_hotKeyPos > -1 ? _hotKeyPos : 0);
 					} else {
 					} else {
@@ -1326,7 +1462,7 @@ namespace Terminal.Gui {
 					}
 					}
 				} else if (_textAlignment == TextAlignment.Left || _textAlignment == TextAlignment.Justified) {
 				} else if (_textAlignment == TextAlignment.Left || _textAlignment == TextAlignment.Justified) {
 					if (isVertical) {
 					if (isVertical) {
-						var runesWidth = line > 0 ? GetSumMaxCharWidth (Lines, 0, line) : 0;
+						var runesWidth = line > 0 ? GetSumMaxCharWidth (Lines, 0, line, TabWidth) : 0;
 						x = bounds.Left + runesWidth;
 						x = bounds.Left + runesWidth;
 					} else {
 					} else {
 						x = bounds.Left;
 						x = bounds.Left;
@@ -1334,7 +1470,7 @@ namespace Terminal.Gui {
 					CursorPosition = _hotKeyPos > -1 ? _hotKeyPos : 0;
 					CursorPosition = _hotKeyPos > -1 ? _hotKeyPos : 0;
 				} else if (_textAlignment == TextAlignment.Centered) {
 				} else if (_textAlignment == TextAlignment.Centered) {
 					if (isVertical) {
 					if (isVertical) {
-						var runesWidth = GetSumMaxCharWidth (Lines, line);
+						var runesWidth = GetSumMaxCharWidth (Lines, line, TabWidth);
 						x = bounds.Left + line + ((bounds.Width - runesWidth) / 2);
 						x = bounds.Left + line + ((bounds.Width - runesWidth) / 2);
 						CursorPosition = (bounds.Width - runesWidth) / 2 + (_hotKeyPos > -1 ? _hotKeyPos : 0);
 						CursorPosition = (bounds.Width - runesWidth) / 2 + (_hotKeyPos > -1 ? _hotKeyPos : 0);
 					} else {
 					} else {
@@ -1375,46 +1511,94 @@ namespace Terminal.Gui {
 				var start = isVertical ? bounds.Top : bounds.Left;
 				var start = isVertical ? bounds.Top : bounds.Left;
 				var size = isVertical ? bounds.Height : bounds.Width;
 				var size = isVertical ? bounds.Height : bounds.Width;
 				var current = start + colOffset;
 				var current = start + colOffset;
+				List<Point?> lastZeroWidthPos = null;
+				Rune rune = default;
+				Rune lastRuneUsed;
+				var zeroLengthCount = isVertical ? runes.Sum (r => r.GetColumns () == 0 ? 1 : 0) : 0;
+
+				for (var idx = (isVertical ? start - y : start - x) + colOffset; current < start + size + zeroLengthCount; idx++) {
+					lastRuneUsed = rune;
+					if (lastZeroWidthPos == null) {
+						if (idx < 0 || x + current + colOffset < 0) {
+							current++;
+							continue;
+						} else if (!fillRemaining && idx > runes.Length - 1) {
+							break;
+						}
+						if ((!isVertical && current - start > maxBounds.Left + maxBounds.Width - bounds.X + colOffset)
+							|| (isVertical && idx > maxBounds.Top + maxBounds.Height - bounds.Y)) {
 
 
-				for (var idx = (isVertical ? start - y : start - x) + colOffset; current < start + size; idx++) {
-					if (idx < 0 || x + current + colOffset < 0) {
-						current++;
-						continue;
-					} else if (!fillRemaining && idx > runes.Length - 1) {
-						break;
+							break;
+						}
 					}
 					}
-					if ((!isVertical && idx > maxBounds.Left + maxBounds.Width - bounds.X + colOffset)
-						|| (isVertical && idx > maxBounds.Top + maxBounds.Height - bounds.Y))
+					//if ((!isVertical && idx > maxBounds.Left + maxBounds.Width - bounds.X + colOffset)
+					//	|| (isVertical && idx > maxBounds.Top + maxBounds.Height - bounds.Y))
 
 
-						break;
+					//	break;
 
 
-					var rune = (Rune)' ';
+					rune = (Rune)' ';
 					if (isVertical) {
 					if (isVertical) {
-						Application.Driver?.Move (x, current);
 						if (idx >= 0 && idx < runes.Length) {
 						if (idx >= 0 && idx < runes.Length) {
 							rune = runes [idx];
 							rune = runes [idx];
 						}
 						}
+						if (lastZeroWidthPos == null) {
+							driver?.Move (x, current);
+						} else {
+							var foundIdx = lastZeroWidthPos.IndexOf (p => p.Value.Y == current);
+							if (foundIdx > -1) {
+								if (rune.IsCombiningMark ()) {
+									lastZeroWidthPos [foundIdx] = (new Point (lastZeroWidthPos [foundIdx].Value.X + 1, current));
+
+									driver?.Move (lastZeroWidthPos [foundIdx].Value.X, current);
+								} else if (!rune.IsCombiningMark () && lastRuneUsed.IsCombiningMark ()) {
+									current++;
+									driver?.Move (x, current);
+								} else {
+									driver?.Move (x, current);
+								}
+							} else {
+								driver?.Move (x, current);
+							}
+						}
 					} else {
 					} else {
-						Application.Driver?.Move (current, y);
+						driver?.Move (current, y);
 						if (idx >= 0 && idx < runes.Length) {
 						if (idx >= 0 && idx < runes.Length) {
 							rune = runes [idx];
 							rune = runes [idx];
 						}
 						}
 					}
 					}
+
+					var runeWidth = GetRuneWidth (rune, TabWidth);
+
 					if (HotKeyPos > -1 && idx == HotKeyPos) {
 					if (HotKeyPos > -1 && idx == HotKeyPos) {
 						if ((isVertical && _textVerticalAlignment == VerticalTextAlignment.Justified) ||
 						if ((isVertical && _textVerticalAlignment == VerticalTextAlignment.Justified) ||
 						(!isVertical && _textAlignment == TextAlignment.Justified)) {
 						(!isVertical && _textAlignment == TextAlignment.Justified)) {
 							CursorPosition = idx - start;
 							CursorPosition = idx - start;
 						}
 						}
-						Application.Driver?.SetAttribute (hotColor);
-						Application.Driver?.AddRune (rune);
-						Application.Driver?.SetAttribute (normalColor);
+						driver?.SetAttribute (hotColor);
+						driver?.AddRune (rune);
+						driver?.SetAttribute (normalColor);
 					} else {
 					} else {
-						Application.Driver?.AddRune (rune);
+						if (isVertical) {
+							if (runeWidth == 0) {
+								if (lastZeroWidthPos == null) {
+									lastZeroWidthPos = new List<Point?> ();
+								}
+								var foundIdx = lastZeroWidthPos.IndexOf (p => p.Value.Y == current);
+								if (foundIdx == -1) {
+									current--;
+									lastZeroWidthPos.Add ((new Point (x + 1, current)));
+								}
+								driver?.Move (x + 1, current);
+							}
+						}
+
+						driver?.AddRune (rune);
 					}
 					}
-					// BUGBUG: I think this is a bug. If rune is a combining mark current should not be incremented.
-					var runeWidth = rune.GetColumns (); //Math.Max (rune.GetColumns (), 1);
+
 					if (isVertical) {
 					if (isVertical) {
-						current++;
+						if (runeWidth > 0) {
+							current++;
+						}
 					} else {
 					} else {
 						current += runeWidth;
 						current += runeWidth;
 					}
 					}

+ 18 - 6
Terminal.Gui/View/ViewLayout.cs

@@ -50,7 +50,7 @@ namespace Terminal.Gui {
 				_frame = new Rect (value.X, value.Y, Math.Max (value.Width, 0), Math.Max (value.Height, 0));
 				_frame = new Rect (value.X, value.Y, Math.Max (value.Width, 0), Math.Max (value.Height, 0));
 				if (IsInitialized || LayoutStyle == LayoutStyle.Absolute) {
 				if (IsInitialized || LayoutStyle == LayoutStyle.Absolute) {
 					LayoutFrames ();
 					LayoutFrames ();
-					TextFormatter.Size = GetSizeNeededForTextAndHotKey ();
+					TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
 					SetNeedsLayout ();
 					SetNeedsLayout ();
 					SetNeedsDisplay ();
 					SetNeedsDisplay ();
 				}
 				}
@@ -213,9 +213,11 @@ namespace Terminal.Gui {
 #if DEBUG
 #if DEBUG
 				if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) {
 				if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) {
 					Debug.WriteLine ($"WARNING: Bounds is being accessed before the View has been initialized. This is likely a bug. View: {this}");
 					Debug.WriteLine ($"WARNING: Bounds is being accessed before the View has been initialized. This is likely a bug. View: {this}");
+					Debug.WriteLine ($"The Frame is set before the View has been initialized. So it isn't a bug.Is by design.");
 				}
 				}
 #endif // DEBUG
 #endif // DEBUG
-				var frameRelativeBounds = Padding?.Thickness.GetInside (Padding.Frame) ?? new Rect (default, Frame.Size);
+				//var frameRelativeBounds = Padding?.Thickness.GetInside (Padding.Frame) ?? new Rect (default, Frame.Size);
+				var frameRelativeBounds = FrameGetInsideBounds ();
 				return new Rect (default, frameRelativeBounds.Size);
 				return new Rect (default, frameRelativeBounds.Size);
 			}
 			}
 			set {
 			set {
@@ -229,6 +231,16 @@ namespace Terminal.Gui {
 			}
 			}
 		}
 		}
 
 
+		private Rect FrameGetInsideBounds ()
+		{
+			if (Margin == null || Border == null || Padding == null) {
+				return new Rect (default, Frame.Size);
+			}
+			var width = Math.Max (0, Frame.Size.Width - Margin.Thickness.Horizontal - Border.Thickness.Horizontal - Padding.Thickness.Horizontal);
+			var height = Math.Max (0, Frame.Size.Height - Margin.Thickness.Vertical - Border.Thickness.Vertical - Padding.Thickness.Vertical);
+			return new Rect (Point.Empty, new Size (width, height));
+		}
+
 		// Diagnostics to highlight when X or Y is read before the view has been initialized
 		// Diagnostics to highlight when X or Y is read before the view has been initialized
 		private Pos VerifyIsIntialized (Pos pos)
 		private Pos VerifyIsIntialized (Pos pos)
 		{
 		{
@@ -460,7 +472,7 @@ namespace Terminal.Gui {
 			if (IsInitialized || LayoutStyle == LayoutStyle.Absolute) {
 			if (IsInitialized || LayoutStyle == LayoutStyle.Absolute) {
 				SetMinWidthHeight ();
 				SetMinWidthHeight ();
 				LayoutFrames ();
 				LayoutFrames ();
-				TextFormatter.Size = GetSizeNeededForTextAndHotKey ();
+				TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
 				SetNeedsLayout ();
 				SetNeedsLayout ();
 				SetNeedsDisplay ();
 				SetNeedsDisplay ();
 			}
 			}
@@ -657,7 +669,7 @@ namespace Terminal.Gui {
 				Frame = r;
 				Frame = r;
 				// BUGBUG: Why is this AFTER setting Frame? Seems duplicative.
 				// BUGBUG: Why is this AFTER setting Frame? Seems duplicative.
 				if (!SetMinWidthHeight ()) {
 				if (!SetMinWidthHeight ()) {
-					TextFormatter.Size = GetSizeNeededForTextAndHotKey ();
+					TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
 				}
 				}
 			}
 			}
 		}
 		}
@@ -878,7 +890,7 @@ namespace Terminal.Gui {
 			var oldBounds = Bounds;
 			var oldBounds = Bounds;
 			OnLayoutStarted (new LayoutEventArgs () { OldBounds = oldBounds });
 			OnLayoutStarted (new LayoutEventArgs () { OldBounds = oldBounds });
 
 
-			TextFormatter.Size = GetSizeNeededForTextAndHotKey ();
+			TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
 
 
 			// Sort out the dependencies of the X, Y, Width, Height properties
 			// Sort out the dependencies of the X, Y, Width, Height properties
 			var nodes = new HashSet<View> ();
 			var nodes = new HashSet<View> ();
@@ -958,7 +970,7 @@ namespace Terminal.Gui {
 				}
 				}
 			}
 			}
 			// BUGBUG: This call may be redundant
 			// BUGBUG: This call may be redundant
-			TextFormatter.Size = GetSizeNeededForTextAndHotKey ();
+			TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
 			return aSize;
 			return aSize;
 		}
 		}
 
 

+ 5 - 6
Terminal.Gui/View/ViewText.cs

@@ -136,7 +136,7 @@ namespace Terminal.Gui {
 			} else {
 			} else {
 				SetMinWidthHeight ();
 				SetMinWidthHeight ();
 			}
 			}
-			TextFormatter.Size = GetSizeNeededForTextAndHotKey ();
+			TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
 			SetNeedsDisplay ();
 			SetNeedsDisplay ();
 		}
 		}
 
 
@@ -159,7 +159,7 @@ namespace Terminal.Gui {
 			} else {
 			} else {
 				return TextFormatter.IsVerticalDirection (TextDirection) &&
 				return TextFormatter.IsVerticalDirection (TextDirection) &&
 				    TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
 				    TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
-				    ? Math.Max (HotKeySpecifier.GetColumns(), 0) : 0;
+				    ? Math.Max (HotKeySpecifier.GetColumns (), 0) : 0;
 			}
 			}
 		}
 		}
 
 
@@ -177,7 +177,7 @@ namespace Terminal.Gui {
 		/// Gets the dimensions required for <see cref="Text"/> accounting for a <see cref="Terminal.Gui.TextFormatter.HotKeySpecifier"/> .
 		/// Gets the dimensions required for <see cref="Text"/> accounting for a <see cref="Terminal.Gui.TextFormatter.HotKeySpecifier"/> .
 		/// </summary>
 		/// </summary>
 		/// <returns></returns>
 		/// <returns></returns>
-		public Size GetSizeNeededForTextAndHotKey ()
+		public Size GetTextFormatterSizeNeededForTextAndHotKey ()
 		{
 		{
 			if (string.IsNullOrEmpty (TextFormatter.Text)) {
 			if (string.IsNullOrEmpty (TextFormatter.Text)) {
 
 
@@ -188,9 +188,8 @@ namespace Terminal.Gui {
 
 
 			// BUGBUG: This IGNORES what Text is set to, using on only the current View size. This doesn't seem to make sense.
 			// BUGBUG: This IGNORES what Text is set to, using on only the current View size. This doesn't seem to make sense.
 			// BUGBUG: This uses Frame; in v2 it should be Bounds
 			// BUGBUG: This uses Frame; in v2 it should be Bounds
-			return new Size (_frame.Size.Width + GetHotKeySpecifierLength (),
-					 _frame.Size.Height + GetHotKeySpecifierLength (false));
+			return new Size (Bounds.Size.Width + GetHotKeySpecifierLength (),
+					 Bounds.Size.Height + GetHotKeySpecifierLength (false));
 		}
 		}
-
 	}
 	}
 }
 }

+ 14 - 12
Terminal.Gui/Views/MessageBox.cs

@@ -1,7 +1,5 @@
-using System.Text;
-using System;
+using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using Terminal.Gui;
 using static Terminal.Gui.ConfigurationManager;
 using static Terminal.Gui.ConfigurationManager;
 
 
 namespace Terminal.Gui {
 namespace Terminal.Gui {
@@ -262,7 +260,7 @@ namespace Terminal.Gui {
 				}
 				}
 
 
 			}
 			}
-			
+
 			Dialog d;
 			Dialog d;
 			d = new Dialog (buttonList.ToArray ()) {
 			d = new Dialog (buttonList.ToArray ()) {
 				Title = title,
 				Title = title,
@@ -273,12 +271,12 @@ namespace Terminal.Gui {
 
 
 			if (width != 0) {
 			if (width != 0) {
 				d.Width = width;
 				d.Width = width;
-			} 
-			
+			}
+
 			if (height != 0) {
 			if (height != 0) {
 				d.Height = height;
 				d.Height = height;
 			}
 			}
-		
+
 			if (useErrorColors) {
 			if (useErrorColors) {
 				d.ColorScheme = Colors.Error;
 				d.ColorScheme = Colors.Error;
 			} else {
 			} else {
@@ -286,7 +284,7 @@ namespace Terminal.Gui {
 			}
 			}
 
 
 			var messageLabel = new Label () {
 			var messageLabel = new Label () {
-				AutoSize = false,
+				AutoSize = wrapMessage ? false : true,
 				Text = message,
 				Text = message,
 				TextAlignment = TextAlignment.Centered,
 				TextAlignment = TextAlignment.Centered,
 				X = 0,
 				X = 0,
@@ -294,16 +292,19 @@ namespace Terminal.Gui {
 				Width = Dim.Fill (0),
 				Width = Dim.Fill (0),
 				Height = Dim.Fill (1)
 				Height = Dim.Fill (1)
 			};
 			};
-			messageLabel.TextFormatter.WordWrap = wrapMessage; // BUGBUG: This does nothing as it's not implemented by TextFormatter!
+			messageLabel.TextFormatter.WordWrap = wrapMessage;
+			messageLabel.TextFormatter.MultiLine = wrapMessage ? false : true;
 			d.Add (messageLabel);
 			d.Add (messageLabel);
-			
+
 			d.Loaded += (s, e) => {
 			d.Loaded += (s, e) => {
 				if (width != 0 || height != 0) {
 				if (width != 0 || height != 0) {
 					return;
 					return;
 				}
 				}
 				// TODO: replace with Dim.Fit when implemented
 				// TODO: replace with Dim.Fit when implemented
 				var maxBounds = d.SuperView?.Bounds ?? Application.Top.Bounds;
 				var maxBounds = d.SuperView?.Bounds ?? Application.Top.Bounds;
-				messageLabel.TextFormatter.Size = new Size (maxBounds.Size.Width - d.GetFramesThickness ().Horizontal, maxBounds.Size.Height - d.GetFramesThickness ().Vertical);
+				if (wrapMessage) {
+					messageLabel.TextFormatter.Size = new Size (maxBounds.Size.Width - d.GetFramesThickness ().Horizontal, maxBounds.Size.Height - d.GetFramesThickness ().Vertical);
+				} 
 				var msg = messageLabel.TextFormatter.Format ();
 				var msg = messageLabel.TextFormatter.Format ();
 				var messageSize = messageLabel.TextFormatter.GetFormattedSize ();
 				var messageSize = messageLabel.TextFormatter.GetFormattedSize ();
 
 
@@ -314,7 +315,8 @@ namespace Terminal.Gui {
 					d.Width = newWidth;
 					d.Width = newWidth;
 				}
 				}
 				// Ensure height fits the text + vspace + buttons
 				// Ensure height fits the text + vspace + buttons
-				d.Height = Math.Max (height, messageSize.Height + 2 + d.GetFramesThickness ().Vertical);
+				var lastLine = messageLabel.TextFormatter.Lines [^1];
+				d.Height = Math.Max (height, messageSize.Height + (lastLine.EndsWith ("\r\n") || lastLine.EndsWith ('\n') ? 1 : 2) + d.GetFramesThickness ().Vertical);
 				d.SetRelativeLayout (d.SuperView?.Frame ?? Application.Top.Frame);
 				d.SetRelativeLayout (d.SuperView?.Frame ?? Application.Top.Frame);
 			};
 			};
 
 

+ 59 - 49
UnitTests/Dialogs/MessageBoxTests.cs

@@ -439,40 +439,40 @@ namespace Terminal.Gui.DialogTests {
 				iterations++;
 				iterations++;
 
 
 				if (iterations == 0) {
 				if (iterations == 0) {
-					MessageBox.Query (string.Empty, new string ('f', 50), defaultButton: 0, wrapMessage: true, "btn");
+					MessageBox.Query (string.Empty, new string ('f', 50), defaultButton: 0, wrapMessage: false, "btn");
 
 
 					Application.RequestStop ();
 					Application.RequestStop ();
 				} else if (iterations == 1) {
 				} else if (iterations == 1) {
 					Application.Refresh ();
 					Application.Refresh ();
 					TestHelpers.AssertDriverContentsWithFrameAre (@$"
 					TestHelpers.AssertDriverContentsWithFrameAre (@$"
 ╔══════════════════╗
 ╔══════════════════╗
-║┌────────────────┐
-║│ffffffffffffffff│║
-║│ffffffffffffffff│║
-║│ffffffffffffffff│║
-║│       ff       │║
-║│                │║
-║│    {btn}   │
-║└────────────────┘
+║                  
+────────────────────
+ffffffffffffffffffff
+                    
+      ⟦► btn ◄⟧     
+────────────────────
+║                  
+║                  
 ╚══════════════════╝", output);
 ╚══════════════════╝", output);
 
 
 					Application.RequestStop ();
 					Application.RequestStop ();
 
 
 					// Really long text
 					// Really long text
-					MessageBox.Query (string.Empty, new string ('f', 500), defaultButton: 0, wrapMessage: true, "btn");
+					MessageBox.Query (string.Empty, new string ('f', 500), defaultButton: 0, wrapMessage: false, "btn");
 				} else if (iterations == 2) {
 				} else if (iterations == 2) {
 					Application.Refresh ();
 					Application.Refresh ();
 					TestHelpers.AssertDriverContentsWithFrameAre (@$"
 					TestHelpers.AssertDriverContentsWithFrameAre (@$"
-╔┌────────────────┐
-║│ffffffffffffffff│
-║│ffffffffffffffff│║
-║│ffffffffffffffff│║
-║│ffffffffffffffff│║
-║│ffffffffffffffff│║
-║│ffffffffffffffff│║
-║│ffffffffffffffff│
-║│    {btn}   │
-╚└────────────────┘╝", output);
+╔══════════════════
+║                  
+────────────────────
+ffffffffffffffffffff
+                    
+      ⟦► btn ◄⟧     
+────────────────────
+║                  
+║                  
+╚══════════════════╝", output);
 
 
 					Application.RequestStop ();
 					Application.RequestStop ();
 				}
 				}
@@ -505,14 +505,14 @@ namespace Terminal.Gui.DialogTests {
 					Application.Refresh ();
 					Application.Refresh ();
 					TestHelpers.AssertDriverContentsWithFrameAre (@$"
 					TestHelpers.AssertDriverContentsWithFrameAre (@$"
 ╔══════════════════╗
 ╔══════════════════╗
-║ ┌──────────────┐
-║ │ff ff ff ff ff│ ║
-║ │ff ff ff ff ff│ ║
-║ │ff ff ff ff ff│ ║
-║ │    ff ff     │ ║
-║ │              │ ║
-║ │   {btn}  │
-║ └──────────────┘
+║                 
+────────────────────
+ff ff ff ff ff ff ff
+                    
+      ⟦► btn ◄⟧     
+────────────────────
+║                 
+║                 
 ╚══════════════════╝", output);
 ╚══════════════════╝", output);
 					Application.RequestStop ();
 					Application.RequestStop ();
 
 
@@ -521,16 +521,16 @@ namespace Terminal.Gui.DialogTests {
 				} else if (iterations == 2) {
 				} else if (iterations == 2) {
 					Application.Refresh ();
 					Application.Refresh ();
 					TestHelpers.AssertDriverContentsWithFrameAre (@$"
 					TestHelpers.AssertDriverContentsWithFrameAre (@$"
-╔┌────────────────┐
-║│ffffffffffffffff│
-║│ffffffffffffffff│║
-║│ffffffffffffffff│║
-║│ffffffffffffffff│║
-║│ffffffffffffffff│║
-║│ffffffffffffffff│║
-║│ffffffffffffffff│
-║│    {btn}   │
-╚└────────────────┘╝", output);
+╔══════════════════
+║                  
+────────────────────
+ffffffffffffffffffff
+                    
+      ⟦► btn ◄⟧     
+────────────────────
+║                  
+║                  
+╚══════════════════╝", output);
 					Application.RequestStop ();
 					Application.RequestStop ();
 				}
 				}
 			};
 			};
@@ -539,15 +539,15 @@ namespace Terminal.Gui.DialogTests {
 		}
 		}
 
 
 		[Theory, AutoInitShutdown]
 		[Theory, AutoInitShutdown]
-		[InlineData (" ", true)]
-		[InlineData (" ", false)]
-		[InlineData ("", true)]
-		[InlineData ("", false)]
-		[InlineData ("\n", true)]
-		[InlineData ("\n", false)]
-		[InlineData (" \n", true)]
-		[InlineData (" \n", false)]
-		public void Message_Empty_Or_A_NewLline_WrapMessagge_True_Or_False (string message, bool wrapMessage)
+		[InlineData (" ", true, 1)]
+		[InlineData (" ", false, 1)]
+		[InlineData ("", true, 1)]
+		[InlineData ("", false, 1)]
+		[InlineData ("\n", true, 1)]
+		[InlineData ("\n", false, 1)]
+		[InlineData (" \n", true, 1)]
+		[InlineData (" \n", false, 2)]
+		public void Message_Empty_Or_A_NewLline_WrapMessagge_True_Or_False (string message, bool wrapMessage, int linesLength)
 		{
 		{
 			var iterations = -1;
 			var iterations = -1;
 			Application.Begin (Application.Top);
 			Application.Begin (Application.Top);
@@ -561,12 +561,22 @@ namespace Terminal.Gui.DialogTests {
 					Application.RequestStop ();
 					Application.RequestStop ();
 				} else if (iterations == 1) {
 				} else if (iterations == 1) {
 					Application.Refresh ();
 					Application.Refresh ();
-					TestHelpers.AssertDriverContentsWithFrameAre (@$"
+					if (linesLength == 1) {
+						TestHelpers.AssertDriverContentsWithFrameAre (@$"
                 ┌──────────────────────────────────────────────┐
                 ┌──────────────────────────────────────────────┐
                 │                                              │
                 │                                              │
                 │                                              │
                 │                                              │
                 │                   {CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} ok {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}                   │
                 │                   {CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} ok {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}                   │
                 └──────────────────────────────────────────────┘", output);
                 └──────────────────────────────────────────────┘", output);
+					} else {
+						TestHelpers.AssertDriverContentsWithFrameAre (@$"
+                ┌──────────────────────────────────────────────┐
+                │                                              │
+                │                                              │
+                │                                              │
+                │                   {CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} ok {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}                   │
+                └──────────────────────────────────────────────┘", output);
+					}
 					Application.RequestStop ();
 					Application.RequestStop ();
 				}
 				}
 			};
 			};
@@ -574,4 +584,4 @@ namespace Terminal.Gui.DialogTests {
 			Application.Run ();
 			Application.Run ();
 		}
 		}
 	}
 	}
-}
+}

+ 358 - 28
UnitTests/Text/TextFormatterTests.cs

@@ -2,7 +2,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
-using Terminal.Gui;
 using Xunit;
 using Xunit;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 
 
@@ -65,13 +64,130 @@ namespace Terminal.Gui.TextTests {
 			Assert.NotEmpty (tf.Lines);
 			Assert.NotEmpty (tf.Lines);
 		}
 		}
 
 
-		[Fact]
-		public void TestSize_TextChange ()
+		[Theory]
+		[InlineData (TextDirection.LeftRight_TopBottom, false)]
+		[InlineData (TextDirection.LeftRight_TopBottom, true)]
+		[InlineData (TextDirection.TopBottom_LeftRight, false)]
+		[InlineData (TextDirection.TopBottom_LeftRight, true)]
+		public void TestSize_TextChange (TextDirection textDirection, bool autoSize)
 		{
 		{
-			var tf = new TextFormatter () { Text = "你" };
+			var tf = new TextFormatter () { Direction = textDirection, Text = "你", AutoSize = autoSize };
 			Assert.Equal (2, tf.Size.Width);
 			Assert.Equal (2, tf.Size.Width);
+			Assert.Equal (1, tf.Size.Height);
 			tf.Text = "你你";
 			tf.Text = "你你";
-			Assert.Equal (4, tf.Size.Width);
+			if (autoSize) {
+				if (textDirection == TextDirection.LeftRight_TopBottom) {
+					Assert.Equal (4, tf.Size.Width);
+					Assert.Equal (1, tf.Size.Height);
+				} else {
+					Assert.Equal (2, tf.Size.Width);
+					Assert.Equal (2, tf.Size.Height);
+				}
+			} else {
+				Assert.Equal (2, tf.Size.Width);
+				Assert.Equal (1, tf.Size.Height);
+			}
+		}
+
+		[Theory]
+		[InlineData (TextDirection.LeftRight_TopBottom)]
+		[InlineData (TextDirection.TopBottom_LeftRight)]
+		public void TestSize_AutoSizeChange (TextDirection textDirection)
+		{
+			var tf = new TextFormatter () { Direction = textDirection, Text = "你你" };
+			if (textDirection == TextDirection.LeftRight_TopBottom) {
+				Assert.Equal (4, tf.Size.Width);
+				Assert.Equal (1, tf.Size.Height);
+			} else {
+				Assert.Equal (2, tf.Size.Width);
+				Assert.Equal (2, tf.Size.Height);
+			}
+			Assert.False (tf.AutoSize);
+
+			tf.Size = new Size (1, 1);
+			Assert.Equal (1, tf.Size.Width);
+			Assert.Equal (1, tf.Size.Height);
+			tf.AutoSize = true;
+			if (textDirection == TextDirection.LeftRight_TopBottom) {
+				Assert.Equal (4, tf.Size.Width);
+				Assert.Equal (1, tf.Size.Height);
+			} else {
+				Assert.Equal (2, tf.Size.Width);
+				Assert.Equal (2, tf.Size.Height);
+			}
+		}
+
+		[Theory]
+		[InlineData (TextDirection.LeftRight_TopBottom, false)]
+		[InlineData (TextDirection.LeftRight_TopBottom, true)]
+		[InlineData (TextDirection.TopBottom_LeftRight, false)]
+		[InlineData (TextDirection.TopBottom_LeftRight, true)]
+		public void TestSize_SizeChange_AutoSize_True_Or_False (TextDirection textDirection, bool autoSize)
+		{
+			var tf = new TextFormatter () { Direction = textDirection, Text = "你你", AutoSize = autoSize };
+			if (textDirection == TextDirection.LeftRight_TopBottom) {
+				Assert.Equal (4, tf.Size.Width);
+				Assert.Equal (1, tf.Size.Height);
+			} else {
+				Assert.Equal (2, tf.Size.Width);
+				Assert.Equal (2, tf.Size.Height);
+			}
+
+			tf.Size = new Size (1, 1);
+			if (autoSize) {
+				if (textDirection == TextDirection.LeftRight_TopBottom) {
+					Assert.Equal (4, tf.Size.Width);
+					Assert.Equal (1, tf.Size.Height);
+				} else {
+					Assert.Equal (2, tf.Size.Width);
+					Assert.Equal (2, tf.Size.Height);
+				}
+			} else {
+				Assert.Equal (1, tf.Size.Width);
+				Assert.Equal (1, tf.Size.Height);
+			}
+		}
+
+		[Theory]
+		[InlineData (TextAlignment.Left, false)]
+		[InlineData (TextAlignment.Centered, true)]
+		[InlineData (TextAlignment.Right, false)]
+		[InlineData (TextAlignment.Justified, true)]
+		public void TestSize_SizeChange_AutoSize_True_Or_False_Horizontal (TextAlignment textAlignment, bool autoSize)
+		{
+			var tf = new TextFormatter () { Direction = TextDirection.LeftRight_TopBottom, Text = "你你", Alignment = textAlignment, AutoSize = autoSize };
+				Assert.Equal (4, tf.Size.Width);
+				Assert.Equal (1, tf.Size.Height);
+
+			tf.Size = new Size (1, 1);
+			if (autoSize && textAlignment != TextAlignment.Justified) {
+				Assert.Equal (4, tf.Size.Width);
+				Assert.Equal (1, tf.Size.Height);
+			} else {
+				Assert.Equal (1, tf.Size.Width);
+				Assert.Equal (1, tf.Size.Height);
+			}
+		}
+
+		[Theory]
+		[InlineData (VerticalTextAlignment.Top, false)]
+		[InlineData (VerticalTextAlignment.Middle, true)]
+		[InlineData (VerticalTextAlignment.Bottom, false)]
+		[InlineData (VerticalTextAlignment.Justified, true)]
+		public void TestSize_SizeChange_AutoSize_True_Or_False_Vertical (VerticalTextAlignment textAlignment, bool autoSize)
+		{
+			var tf = new TextFormatter () { Direction = TextDirection.TopBottom_LeftRight, Text = "你你", VerticalAlignment = textAlignment, AutoSize = autoSize };
+			Assert.Equal (2, tf.Size.Width);
+			Assert.Equal (2, tf.Size.Height);
+
+			tf.Size = new Size (1, 1);
+			if (autoSize && textAlignment != VerticalTextAlignment.Justified) {
+				Assert.Equal (2, tf.Size.Width);
+				Assert.Equal (2, tf.Size.Height);
+			} else {
+				Assert.Equal (1, tf.Size.Width);
+				Assert.Equal (1, tf.Size.Height);
+			}
 		}
 		}
 
 
 		[Fact]
 		[Fact]
@@ -361,8 +477,8 @@ namespace Terminal.Gui.TextTests {
 		[InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit
 		[InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit
 		[InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit
 		[InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit
 		[InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit
 		[InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit
-		[InlineData ("A\tsentence\thas\twords.", "A\tsentence\thas\twords.", int.MaxValue)]
-		[InlineData ("A\tsentence\thas\twords.", "A\tsentence", 10)]
+		[InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)]
+		[InlineData ("A\tsentence\thas\twords.", "A sentence", 10)]
 		[InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)]
 		[InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)]
 		[InlineData ("line1\nline2\nline3long!", "line1\nline", 10)]
 		[InlineData ("line1\nline2\nline3long!", "line1\nline", 10)]
 		[InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode
 		[InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode
@@ -372,10 +488,12 @@ namespace Terminal.Gui.TextTests {
 		public void ClipAndJustify_Valid_Left (string text, string justifiedText, int maxWidth)
 		public void ClipAndJustify_Valid_Left (string text, string justifiedText, int maxWidth)
 		{
 		{
 			var align = TextAlignment.Left;
 			var align = TextAlignment.Left;
+			var textDirection = TextDirection.LeftRight_BottomTop;
+			var tabWidth = 1;
 
 
-			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align));
+			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
 			var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
 			var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
-			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align));
+			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
 			Assert.True (justifiedText.GetRuneCount () <= maxWidth);
 			Assert.True (justifiedText.GetRuneCount () <= maxWidth);
 			Assert.True (justifiedText.GetColumns () <= maxWidth);
 			Assert.True (justifiedText.GetColumns () <= maxWidth);
 			Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
 			Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
@@ -393,8 +511,8 @@ namespace Terminal.Gui.TextTests {
 		[InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit
 		[InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit
 		[InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit
 		[InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit
 		[InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit
 		[InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit
-		[InlineData ("A\tsentence\thas\twords.", "A\tsentence\thas\twords.", int.MaxValue)]
-		[InlineData ("A\tsentence\thas\twords.", "A\tsentence", 10)]
+		[InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)]
+		[InlineData ("A\tsentence\thas\twords.", "A sentence", 10)]
 		[InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)]
 		[InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)]
 		[InlineData ("line1\nline2\nline3long!", "line1\nline", 10)]
 		[InlineData ("line1\nline2\nline3long!", "line1\nline", 10)]
 		[InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode
 		[InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode
@@ -404,10 +522,12 @@ namespace Terminal.Gui.TextTests {
 		public void ClipAndJustify_Valid_Right (string text, string justifiedText, int maxWidth)
 		public void ClipAndJustify_Valid_Right (string text, string justifiedText, int maxWidth)
 		{
 		{
 			var align = TextAlignment.Right;
 			var align = TextAlignment.Right;
+			var textDirection = TextDirection.LeftRight_BottomTop;
+			var tabWidth = 1;
 
 
-			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align));
+			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
 			var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
 			var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
-			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align));
+			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
 			Assert.True (justifiedText.GetRuneCount () <= maxWidth);
 			Assert.True (justifiedText.GetRuneCount () <= maxWidth);
 			Assert.True (justifiedText.GetColumns () <= maxWidth);
 			Assert.True (justifiedText.GetColumns () <= maxWidth);
 			Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
 			Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
@@ -425,8 +545,8 @@ namespace Terminal.Gui.TextTests {
 		[InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit
 		[InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit
 		[InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit
 		[InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit
 		[InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit
 		[InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit
-		[InlineData ("A\tsentence\thas\twords.", "A\tsentence\thas\twords.", int.MaxValue)]
-		[InlineData ("A\tsentence\thas\twords.", "A\tsentence", 10)]
+		[InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)]
+		[InlineData ("A\tsentence\thas\twords.", "A sentence", 10)]
 		[InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)]
 		[InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)]
 		[InlineData ("line1\nline2\nline3long!", "line1\nline", 10)]
 		[InlineData ("line1\nline2\nline3long!", "line1\nline", 10)]
 		[InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode
 		[InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode
@@ -436,10 +556,12 @@ namespace Terminal.Gui.TextTests {
 		public void ClipAndJustify_Valid_Centered (string text, string justifiedText, int maxWidth)
 		public void ClipAndJustify_Valid_Centered (string text, string justifiedText, int maxWidth)
 		{
 		{
 			var align = TextAlignment.Centered;
 			var align = TextAlignment.Centered;
+			var textDirection = TextDirection.LeftRight_TopBottom;
+			var tabWidth = 1;
 
 
-			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align));
+			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
 			var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
 			var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
-			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align));
+			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
 			Assert.True (justifiedText.GetRuneCount () <= maxWidth);
 			Assert.True (justifiedText.GetRuneCount () <= maxWidth);
 			Assert.True (justifiedText.GetColumns () <= maxWidth);
 			Assert.True (justifiedText.GetColumns () <= maxWidth);
 			Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
 			Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
@@ -451,15 +573,16 @@ namespace Terminal.Gui.TextTests {
 		[Theory]
 		[Theory]
 		[InlineData ("test", "", 0)]
 		[InlineData ("test", "", 0)]
 		[InlineData ("test", "te", 2)]
 		[InlineData ("test", "te", 2)]
-		[InlineData ("test", "test", int.MaxValue)]
+		[InlineData ("test", "test", int.MaxValue)] // This doesn't throw because it only create a word with length 1
 		[InlineData ("A sentence has words.", "A  sentence has words.", 22)] // should fit
 		[InlineData ("A sentence has words.", "A  sentence has words.", 22)] // should fit
 		[InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit
 		[InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit
 		[InlineData ("A sentence has words.", "A                                                                                                                                                                 sentence                                                                                                                                                                 has                                                                                                                                                                words.", 500)] // should fit
 		[InlineData ("A sentence has words.", "A                                                                                                                                                                 sentence                                                                                                                                                                 has                                                                                                                                                                words.", 500)] // should fit
 		[InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit
 		[InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit
 		[InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit
 		[InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit
-		[InlineData ("A\tsentence\thas\twords.", "A\tsentence\thas\twords.", int.MaxValue)]
-		[InlineData ("A\tsentence\thas\twords.", "A\tsentence", 10)]
-		[InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)]
+									 // Now throw System.OutOfMemoryException. See https://stackoverflow.com/questions/20672920/maxcapacity-of-stringbuilder
+									 //[InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)]
+		[InlineData ("A\tsentence\thas\twords.", "A sentence", 10)]
+		[InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)] // This doesn't throw because it only create a line with length 1
 		[InlineData ("line1\nline2\nline3long!", "line1\nline", 10)]
 		[InlineData ("line1\nline2\nline3long!", "line1\nline", 10)]
 		[InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode
 		[InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode
 		[InlineData ("Ð ÑÐ", "Ð  ÑÐ", 5)] // should fit
 		[InlineData ("Ð ÑÐ", "Ð  ÑÐ", 5)] // should fit
@@ -468,10 +591,12 @@ namespace Terminal.Gui.TextTests {
 		public void ClipAndJustify_Valid_Justified (string text, string justifiedText, int maxWidth)
 		public void ClipAndJustify_Valid_Justified (string text, string justifiedText, int maxWidth)
 		{
 		{
 			var align = TextAlignment.Justified;
 			var align = TextAlignment.Justified;
+			var textDirection = TextDirection.LeftRight_TopBottom;
+			var tabWidth = 1;
 
 
-			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align));
+			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
 			var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
 			var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
-			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align));
+			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
 			Assert.True (justifiedText.GetRuneCount () <= maxWidth);
 			Assert.True (justifiedText.GetRuneCount () <= maxWidth);
 			Assert.True (justifiedText.GetColumns () <= maxWidth);
 			Assert.True (justifiedText.GetColumns () <= maxWidth);
 			Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
 			Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
@@ -620,21 +745,24 @@ namespace Terminal.Gui.TextTests {
 
 
 		[Theory]
 		[Theory]
 		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 51, 0, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })]
 		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 51, 0, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })]
-		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 50, -1, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัา", "ำ" })]
+		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 50, -1, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })]
 		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 46, -5, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮ", "ฯะัาำ" })]
 		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 46, -5, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮ", "ฯะัาำ" })]
 		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 26, -25, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })]
 		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 26, -25, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })]
 		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 17, -34, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑ", "ฒณดตถทธนบปผฝพฟภมย", "รฤลฦวศษสหฬอฮฯะัาำ" })]
 		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 17, -34, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑ", "ฒณดตถทธนบปผฝพฟภมย", "รฤลฦวศษสหฬอฮฯะัาำ" })]
 		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 13, -38, new string [] { "กขฃคฅฆงจฉชซฌญ", "ฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦว", "ศษสหฬอฮฯะัาำ" })]
 		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 13, -38, new string [] { "กขฃคฅฆงจฉชซฌญ", "ฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦว", "ศษสหฬอฮฯะัาำ" })]
-		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 1, -50, new string [] { "ก", "ข", "ฃ", "ค", "ฅ", "ฆ", "ง", "จ", "ฉ", "ช", "ซ", "ฌ", "ญ", "ฎ", "ฏ", "ฐ", "ฑ", "ฒ", "ณ", "ด", "ต", "ถ", "ท", "ธ", "น", "บ", "ป", "ผ", "ฝ", "พ", "ฟ", "ภ", "ม", "ย", "ร", "ฤ", "ล", "ฦ", "ว", "ศ", "ษ", "ส", "ห", "ฬ", "อ", "ฮ", "ฯ", "ะ", "ั", "า", "ำ" })]
+		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 1, -50, new string [] { "ก", "ข", "ฃ", "ค", "ฅ", "ฆ", "ง", "จ", "ฉ", "ช", "ซ", "ฌ", "ญ", "ฎ", "ฏ", "ฐ", "ฑ", "ฒ", "ณ", "ด", "ต", "ถ", "ท", "ธ", "น", "บ", "ป", "ผ", "ฝ", "พ", "ฟ", "ภ", "ม", "ย", "ร", "ฤ", "ล", "ฦ", "ว", "ศ", "ษ", "ส", "ห", "ฬ", "อ", "ฮ", "ฯ", "ะั", "า", "ำ" })]
 		public void WordWrap_Unicode_SingleWordLine (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
 		public void WordWrap_Unicode_SingleWordLine (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
 		{
 		{
 			List<string> wrappedLines;
 			List<string> wrappedLines;
 
 
+			var zeroWidth = text.EnumerateRunes ().Where (r => r.GetColumns () == 0);
+			Assert.Single (zeroWidth);
+			Assert.Equal ('ั', zeroWidth.ElementAt (0).Value);
 			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
 			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
 			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
 			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
 			wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
 			wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
 			Assert.Equal (wrappedLines.Count, resultLines.Count ());
 			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
+			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount () + zeroWidth.Count () - 1 + widthOffset) : 0));
 			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
 			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
 			Assert.Equal (resultLines, wrappedLines);
 			Assert.Equal (resultLines, wrappedLines);
 		}
 		}
@@ -793,7 +921,7 @@ namespace Terminal.Gui.TextTests {
 		[InlineData ("文に は言葉 があり ます。", 14, 0, new string [] { "文に は言葉", "があり ます。" })]
 		[InlineData ("文に は言葉 があり ます。", 14, 0, new string [] { "文に は言葉", "があり ます。" })]
 		[InlineData ("文に は言葉 があり ます。", 3, -11, new string [] { "文", "に", "は", "言", "葉", "が", "あ", "り", "ま", "す", "。" })]
 		[InlineData ("文に は言葉 があり ます。", 3, -11, new string [] { "文", "に", "は", "言", "葉", "が", "あ", "り", "ま", "す", "。" })]
 		[InlineData ("文に は言葉 があり ます。", 2, -12, new string [] { "文", "に", "は", "言", "葉", "が", "あ", "り", "ま", "す", "。" })]
 		[InlineData ("文に は言葉 があり ます。", 2, -12, new string [] { "文", "に", "は", "言", "葉", "が", "あ", "り", "ま", "す", "。" })]
-		[InlineData ("文に は言葉 があり ます。", 1, -13, new string [] { })]
+		[InlineData ("文に は言葉 があり ます。", 1, -13, new string [] { " ", " ", " " })] // Just Spaces; should result in a single space for each line
 		public void WordWrap_PreserveTrailingSpaces_False_Wide_Runes (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
 		public void WordWrap_PreserveTrailingSpaces_False_Wide_Runes (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
 		{
 		{
 			List<string> wrappedLines;
 			List<string> wrappedLines;
@@ -1071,7 +1199,7 @@ namespace Terminal.Gui.TextTests {
 		[InlineData ("A sentence has words.\r\nLine 2.", 29, -1, TextAlignment.Left, false, 1, false, 1)]
 		[InlineData ("A sentence has words.\r\nLine 2.", 29, -1, TextAlignment.Left, false, 1, false, 1)]
 		[InlineData ("A sentence has words.\r\nLine 2.", 30, 0, TextAlignment.Left, false, 1, false)]
 		[InlineData ("A sentence has words.\r\nLine 2.", 30, 0, TextAlignment.Left, false, 1, false)]
 		[InlineData ("A sentence has words.\r\nLine 2.", 31, 1, TextAlignment.Left, false, 1, false)]
 		[InlineData ("A sentence has words.\r\nLine 2.", 31, 1, TextAlignment.Left, false, 1, false)]
-		public void Reformat_NoWordrap_NewLines (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, int linesCount, bool stringEmpty, int clipWidthOffset = 0)
+		public void Reformat_NoWordrap_NewLines_MultiLine_False (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, int linesCount, bool stringEmpty, int clipWidthOffset = 0)
 		{
 		{
 			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
 			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
 			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth) + clipWidthOffset;
 			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth) + clipWidthOffset;
@@ -1092,6 +1220,64 @@ namespace Terminal.Gui.TextTests {
 			}
 			}
 		}
 		}
 
 
+		[Theory]
+		[InlineData ("A sentence has words.\nLine 2.", 0, -29, TextAlignment.Left, false, 1, true, new string [] { "" })]
+		[InlineData ("A sentence has words.\nLine 2.", 1, -28, TextAlignment.Left, false, 2, false, new string [] { "A", "L" })]
+		[InlineData ("A sentence has words.\nLine 2.", 5, -24, TextAlignment.Left, false, 2, false, new string [] { "A sen", "Line " })]
+		[InlineData ("A sentence has words.\nLine 2.", 28, -1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
+		//// no clip
+		[InlineData ("A sentence has words.\nLine 2.", 29, 0, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
+		[InlineData ("A sentence has words.\nLine 2.", 30, 1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
+		[InlineData ("A sentence has words.\r\nLine 2.", 0, -30, TextAlignment.Left, false, 1, true, new string [] { "" })]
+		[InlineData ("A sentence has words.\r\nLine 2.", 1, -29, TextAlignment.Left, false, 2, false, new string [] { "A", "L" })]
+		[InlineData ("A sentence has words.\r\nLine 2.", 5, -25, TextAlignment.Left, false, 2, false, new string [] { "A sen", "Line " })]
+		[InlineData ("A sentence has words.\r\nLine 2.", 29, -1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
+		[InlineData ("A sentence has words.\r\nLine 2.", 30, 0, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
+		[InlineData ("A sentence has words.\r\nLine 2.", 31, 1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
+		public void Reformat_NoWordrap_NewLines_MultiLine_True (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, int linesCount, bool stringEmpty, IEnumerable<string> resultLines)
+		{
+			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+			var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, false, 0, TextDirection.LeftRight_TopBottom, true);
+			Assert.NotEmpty (list);
+			Assert.True (list.Count == linesCount);
+			if (stringEmpty) {
+				Assert.Equal (string.Empty, list [0]);
+			} else {
+				Assert.NotEqual (string.Empty, list [0]);
+			}
+
+			Assert.Equal (list, resultLines);
+		}
+
+		[Theory]
+		[InlineData ("A sentence has words.\nLine 2.", 0, -29, TextAlignment.Left, false, 1, true, new string [] { "" })]
+		[InlineData ("A sentence has words.\nLine 2.", 1, -28, TextAlignment.Left, false, 2, false, new string [] { "A", "L" })]
+		[InlineData ("A sentence has words.\nLine 2.", 5, -24, TextAlignment.Left, false, 2, false, new string [] { "A sen", "Line " })]
+		[InlineData ("A sentence has words.\nLine 2.", 28, -1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
+		//// no clip
+		[InlineData ("A sentence has words.\nLine 2.", 29, 0, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
+		[InlineData ("A sentence has words.\nLine 2.", 30, 1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
+		[InlineData ("A sentence has words.\r\nLine 2.", 0, -30, TextAlignment.Left, false, 1, true, new string [] { "" })]
+		[InlineData ("A sentence has words.\r\nLine 2.", 1, -29, TextAlignment.Left, false, 2, false, new string [] { "A", "L" })]
+		[InlineData ("A sentence has words.\r\nLine 2.", 5, -25, TextAlignment.Left, false, 2, false, new string [] { "A sen", "Line " })]
+		[InlineData ("A sentence has words.\r\nLine 2.", 29, -1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
+		[InlineData ("A sentence has words.\r\nLine 2.", 30, 0, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
+		[InlineData ("A sentence has words.\r\nLine 2.", 31, 1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
+		public void Reformat_NoWordrap_NewLines_MultiLine_True_Vertical (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, int linesCount, bool stringEmpty, IEnumerable<string> resultLines)
+		{
+			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+			var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, false, 0, TextDirection.TopBottom_LeftRight, true);
+			Assert.NotEmpty (list);
+			Assert.True (list.Count == linesCount);
+			if (stringEmpty) {
+				Assert.Equal (string.Empty, list [0]);
+			} else {
+				Assert.NotEqual (string.Empty, list [0]);
+			}
+
+			Assert.Equal (list, resultLines);
+		}
+
 		[Theory]
 		[Theory]
 		// Even # of spaces
 		// Even # of spaces
 		//            0123456789
 		//            0123456789
@@ -1418,5 +1604,149 @@ namespace Terminal.Gui.TextTests {
 				Assert.Equal (expected, text [index].ToString ());
 				Assert.Equal (expected, text [index].ToString ());
 			}
 			}
 		}
 		}
+
+		[Fact]
+		public void GetLengthThatFits_With_Combining_Runes ()
+		{
+			var text = "Les Mise\u0328\u0301rables";
+			Assert.Equal (16, TextFormatter.GetLengthThatFits (text, 14));
+		}
+
+		[Fact]
+		public void GetMaxColsForWidth_With_Combining_Runes ()
+		{
+			var text = new List<string> () { "Les Mis", "e\u0328\u0301", "rables" };
+			Assert.Equal (1, TextFormatter.GetMaxColsForWidth (text, 1));
+		}
+
+		[Fact]
+		public void GetSumMaxCharWidth_With_Combining_Runes ()
+		{
+			var text = "Les Mise\u0328\u0301rables";
+			Assert.Equal (1, TextFormatter.GetSumMaxCharWidth (text, 1, 1));
+		}
+
+		[Fact]
+		public void GetSumMaxCharWidth_List_With_Combining_Runes ()
+		{
+			var text = new List<string> () { "Les Mis", "e\u0328\u0301", "rables" };
+			Assert.Equal (1, TextFormatter.GetSumMaxCharWidth (text, 1, 1));
+		}
+
+		[Theory]
+		[InlineData (14, 1, TextDirection.LeftRight_TopBottom)]
+		[InlineData (1, 14, TextDirection.TopBottom_LeftRight)]
+		public void CalcRect_With_Combining_Runes (int width, int height, TextDirection textDirection)
+		{
+			var text = "Les Mise\u0328\u0301rables";
+			Assert.Equal (new Rect (0, 0, width, height), TextFormatter.CalcRect (0, 0, text, textDirection));
+		}
+
+		[Theory]
+		[InlineData (14, 1, TextDirection.LeftRight_TopBottom, "Les Misęrables")]
+		[InlineData (1, 14, TextDirection.TopBottom_LeftRight, "L\ne\ns\n \nM\ni\ns\nę\nr\na\nb\nl\ne\ns")]
+		[InlineData (4, 4, TextDirection.TopBottom_LeftRight, @"
+LMre
+eias
+ssb 
+ ęl ")]
+		public void Draw_With_Combining_Runes (int width, int height, TextDirection textDirection, string expected)
+		{
+			var driver = new FakeDriver ();
+			driver.Init ();
+
+			var text = "Les Mise\u0328\u0301rables";
+
+			var tf = new TextFormatter ();
+			tf.Direction = textDirection;
+			tf.Text = text;
+
+			Assert.True (tf.WordWrap);
+			if (textDirection == TextDirection.LeftRight_TopBottom) {
+				Assert.Equal (new Size (width, height), tf.Size);
+			} else {
+				Assert.Equal (new Size (1, text.GetColumns ()), tf.Size);
+				tf.Size = new Size (width, height);
+			}
+			tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver);
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver);
+
+			driver.End ();
+		}
+
+		[Theory]
+		[InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a     Tab")]
+		[InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")]
+		[InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")]
+		[InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")]
+		public void TabWith_PreserveTrailingSpaces_False (int width, int height, TextDirection textDirection, int tabWidth, string expected)
+		{
+			var driver = new FakeDriver ();
+			driver.Init ();
+
+			var text = "This is a \tTab";
+			var tf = new TextFormatter ();
+			tf.Direction = textDirection;
+			tf.TabWidth = tabWidth;
+			tf.Text = text;
+
+			Assert.True (tf.WordWrap);
+			Assert.False (tf.PreserveTrailingSpaces);
+			Assert.Equal (new Size (width, height), tf.Size);
+			tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver);
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver);
+
+			driver.End ();
+		}
+
+		[Theory]
+		[InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a     Tab")]
+		[InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")]
+		[InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")]
+		[InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")]
+		public void TabWith_PreserveTrailingSpaces_True (int width, int height, TextDirection textDirection, int tabWidth, string expected)
+		{
+			var driver = new FakeDriver ();
+			driver.Init ();
+
+			var text = "This is a \tTab";
+			var tf = new TextFormatter ();
+			tf.Direction = textDirection;
+			tf.TabWidth = tabWidth;
+			tf.PreserveTrailingSpaces = true;
+			tf.Text = text;
+
+			Assert.True (tf.WordWrap);
+			Assert.Equal (new Size (width, height), tf.Size);
+			tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver);
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver);
+
+			driver.End ();
+		}
+
+		[Theory]
+		[InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a     Tab")]
+		[InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")]
+		[InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")]
+		[InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")]
+		public void TabWith_WordWrap_True (int width, int height, TextDirection textDirection, int tabWidth, string expected)
+		{
+			var driver = new FakeDriver ();
+			driver.Init ();
+
+			var text = "This is a \tTab";
+			var tf = new TextFormatter ();
+			tf.Direction = textDirection;
+			tf.TabWidth = tabWidth;
+			tf.WordWrap = true;
+			tf.Text = text;
+
+			Assert.False (tf.PreserveTrailingSpaces);
+			Assert.Equal (new Size (width, height), tf.Size);
+			tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver);
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver);
+
+			driver.End ();
+		}
 	}
 	}
 }
 }

+ 7 - 6
UnitTests/View/Layout/LayoutTests.cs

@@ -357,9 +357,9 @@ namespace Terminal.Gui.ViewTests {
 			win.Add (label);
 			win.Add (label);
 			Application.Top.Add (win);
 			Application.Top.Add (win);
 
 
-			// Text is empty so height=0
+			// Text is empty but height=1 by default, see Label view
 			Assert.False (label.AutoSize);
 			Assert.False (label.AutoSize);
-			Assert.Equal ("(0,0,0,0)", label.Bounds.ToString ());
+			Assert.Equal ("(0,0,0,1)", label.Bounds.ToString ());
 
 
 			label.Text = "New text\nNew line";
 			label.Text = "New text\nNew line";
 			Application.Top.LayoutSubviews ();
 			Application.Top.LayoutSubviews ();
@@ -385,10 +385,11 @@ namespace Terminal.Gui.ViewTests {
 
 
 			Assert.True (label.IsAdded);
 			Assert.True (label.IsAdded);
 
 
-			// Text is empty so height=0
+			// Text is empty but height=1 by default, see Label view
 			Assert.True (label.AutoSize);
 			Assert.True (label.AutoSize);
 			// BUGBUG: LayoutSubviews has not been called, so this test is not really valid (pos/dim are indeterminate, not 0)
 			// BUGBUG: LayoutSubviews has not been called, so this test is not really valid (pos/dim are indeterminate, not 0)
-			Assert.Equal ("(0,0,0,0)", label.Bounds.ToString ());
+			// Not really a bug because View call OnResizeNeeded method on the SetInitialProperties method
+			Assert.Equal ("(0,0,0,1)", label.Bounds.ToString ());
 
 
 			label.Text = "First line\nSecond line";
 			label.Text = "First line\nSecond line";
 			Application.Top.LayoutSubviews ();
 			Application.Top.LayoutSubviews ();
@@ -420,9 +421,9 @@ namespace Terminal.Gui.ViewTests {
 			win.Add (label);
 			win.Add (label);
 			Application.Top.Add (win);
 			Application.Top.Add (win);
 
 
-			// Text is empty so height=0
+			// Text is empty but height=1 by default, see Label view
 			Assert.True (label.AutoSize);
 			Assert.True (label.AutoSize);
-			Assert.Equal ("(0,0,0,0)", label.Bounds.ToString ());
+			Assert.Equal ("(0,0,0,1)", label.Bounds.ToString ());
 
 
 			var rs = Application.Begin (Application.Top);
 			var rs = Application.Begin (Application.Top);
 
 

+ 71 - 25
UnitTests/Views/LabelTests.cs

@@ -446,8 +446,10 @@ Test
 			Assert.Equal (new Rect (0, 0, width + 2, height + 2), pos);
 			Assert.Equal (new Rect (0, 0, width + 2, height + 2), pos);
 		}
 		}
 
 
-		[Fact, AutoInitShutdown]
-		public void Label_WordWrap_PreserveTrailingSpaces_Horizontal_With_Wide_Runes ()
+		[Theory, AutoInitShutdown]
+		[InlineData (false)]
+		[InlineData (true)]
+		public void Label_WordWrap_PreserveTrailingSpaces_Horizontal_With_Wide_Runes (bool autoSize)
 		{
 		{
 			var text = "文に は言葉 があり ます。";
 			var text = "文に は言葉 があり ます。";
 			var width = 6;
 			var width = 6;
@@ -455,17 +457,29 @@ Test
 			var wrappedLines = TextFormatter.WordWrapText (text, width, true);
 			var wrappedLines = TextFormatter.WordWrapText (text, width, true);
 			var breakLines = "";
 			var breakLines = "";
 			foreach (var line in wrappedLines) breakLines += $"{line}{Environment.NewLine}";
 			foreach (var line in wrappedLines) breakLines += $"{line}{Environment.NewLine}";
-			var label = new Label (breakLines) { Width = Dim.Fill (), Height = Dim.Fill () };
+			var label = new Label (breakLines) { Width = Dim.Fill (), Height = Dim.Fill (), AutoSize = autoSize };
 			var frame = new FrameView () { Width = Dim.Fill (), Height = Dim.Fill () };
 			var frame = new FrameView () { Width = Dim.Fill (), Height = Dim.Fill () };
 
 
 			frame.Add (label);
 			frame.Add (label);
 			Application.Top.Add (frame);
 			Application.Top.Add (frame);
 			Application.Begin (Application.Top);
 			Application.Begin (Application.Top);
+
+			Assert.True (label.AutoSize == autoSize);
+			Assert.Equal (new Rect (0, 0, 78, 23), label.Frame);
+			if (autoSize) {
+				// The size of the wrappedLines [1]
+				Assert.Equal (new Size (width, height - 2), label.TextFormatter.Size);
+			} else {
+				Assert.Equal (new Size (78, 23), label.TextFormatter.Size);
+			}
 			((FakeDriver)Application.Driver).SetBufferSize (width + 2, height + 2);
 			((FakeDriver)Application.Driver).SetBufferSize (width + 2, height + 2);
 
 
-			Assert.True (label.AutoSize);
 			Assert.Equal (new Rect (0, 0, width, height), label.Frame);
 			Assert.Equal (new Rect (0, 0, width, height), label.Frame);
-			Assert.Equal (new Size (width, height), label.TextFormatter.Size);
+			if (autoSize) {
+				Assert.Equal (new Size (width, height - 2), label.TextFormatter.Size);
+			} else {
+				Assert.Equal (new Size (width, height), label.TextFormatter.Size);
+			}
 			Assert.Equal (new Rect (0, 0, width + 2, height + 2), frame.Frame);
 			Assert.Equal (new Rect (0, 0, width + 2, height + 2), frame.Frame);
 
 
 			var expected = @"
 			var expected = @"
@@ -670,15 +684,17 @@ e
 			Assert.Equal (new Rect (0, 0, 2, 7), pos);
 			Assert.Equal (new Rect (0, 0, 2, 7), pos);
 		}
 		}
 
 
-		[Fact, AutoInitShutdown]
-		public void Label_Draw_Horizontal_Simple_TextAlignments ()
+		[Theory, AutoInitShutdown]
+		[InlineData (true)]
+		[InlineData (false)]
+		public void Label_Draw_Horizontal_Simple_TextAlignments (bool autoSize)
 		{
 		{
 			var text = "Hello World";
 			var text = "Hello World";
 			var width = 20;
 			var width = 20;
-			var lblLeft = new Label (text) { Width = width };
-			var lblCenter = new Label (text) { Y = 1, Width = width, TextAlignment = TextAlignment.Centered };
-			var lblRight = new Label (text) { Y = 2, Width = width, TextAlignment = TextAlignment.Right };
-			var lblJust = new Label (text) { Y = 3, Width = width, TextAlignment = TextAlignment.Justified };
+			var lblLeft = new Label (text) { Width = width, AutoSize = autoSize };
+			var lblCenter = new Label (text) { Y = 1, Width = width, TextAlignment = TextAlignment.Centered, AutoSize = autoSize };
+			var lblRight = new Label (text) { Y = 2, Width = width, TextAlignment = TextAlignment.Right, AutoSize = autoSize };
+			var lblJust = new Label (text) { Y = 3, Width = width, TextAlignment = TextAlignment.Justified, AutoSize = autoSize };
 			var frame = new FrameView () { Width = Dim.Fill (), Height = Dim.Fill () };
 			var frame = new FrameView () { Width = Dim.Fill (), Height = Dim.Fill () };
 
 
 			frame.Add (lblLeft, lblCenter, lblRight, lblJust);
 			frame.Add (lblLeft, lblCenter, lblRight, lblJust);
@@ -686,14 +702,28 @@ e
 			Application.Begin (Application.Top);
 			Application.Begin (Application.Top);
 			((FakeDriver)Application.Driver).SetBufferSize (width + 2, 6);
 			((FakeDriver)Application.Driver).SetBufferSize (width + 2, 6);
 
 
-			Assert.True (lblLeft.AutoSize);
-			Assert.True (lblCenter.AutoSize);
-			Assert.True (lblRight.AutoSize);
-			Assert.True (lblJust.AutoSize);
+			Assert.True (lblLeft.AutoSize == autoSize);
+			Assert.True (lblCenter.AutoSize == autoSize);
+			Assert.True (lblRight.AutoSize == autoSize);
+			Assert.True (lblJust.AutoSize == autoSize);
+			Assert.True (lblLeft.TextFormatter.AutoSize == autoSize);
+			Assert.True (lblCenter.TextFormatter.AutoSize == autoSize);
+			Assert.True (lblRight.TextFormatter.AutoSize == autoSize);
+			Assert.True (lblJust.TextFormatter.AutoSize == autoSize);
 			Assert.Equal (new Rect (0, 0, width, 1), lblLeft.Frame);
 			Assert.Equal (new Rect (0, 0, width, 1), lblLeft.Frame);
 			Assert.Equal (new Rect (0, 1, width, 1), lblCenter.Frame);
 			Assert.Equal (new Rect (0, 1, width, 1), lblCenter.Frame);
 			Assert.Equal (new Rect (0, 2, width, 1), lblRight.Frame);
 			Assert.Equal (new Rect (0, 2, width, 1), lblRight.Frame);
 			Assert.Equal (new Rect (0, 3, width, 1), lblJust.Frame);
 			Assert.Equal (new Rect (0, 3, width, 1), lblJust.Frame);
+			if (autoSize) {
+				Assert.Equal (new Size (11, 1), lblLeft.TextFormatter.Size);
+				Assert.Equal (new Size (11, 1), lblCenter.TextFormatter.Size);
+				Assert.Equal (new Size (11, 1), lblRight.TextFormatter.Size);
+			} else {
+				Assert.Equal (new Size (width, 1), lblLeft.TextFormatter.Size);
+				Assert.Equal (new Size (width, 1), lblCenter.TextFormatter.Size);
+				Assert.Equal (new Size (width, 1), lblRight.TextFormatter.Size);
+			}
+			Assert.Equal (new Size (width, 1), lblJust.TextFormatter.Size);
 			Assert.Equal (new Rect (0, 0, width + 2, 6), frame.Frame);
 			Assert.Equal (new Rect (0, 0, width + 2, 6), frame.Frame);
 
 
 			var expected = @"
 			var expected = @"
@@ -709,15 +739,17 @@ e
 			Assert.Equal (new Rect (0, 0, width + 2, 6), pos);
 			Assert.Equal (new Rect (0, 0, width + 2, 6), pos);
 		}
 		}
 
 
-		[Fact, AutoInitShutdown]
-		public void Label_Draw_Vertical_Simple_TextAlignments ()
+		[Theory, AutoInitShutdown]
+		[InlineData (true)]
+		[InlineData (false)]
+		public void Label_Draw_Vertical_Simple_TextAlignments (bool autoSize)
 		{
 		{
 			var text = "Hello World";
 			var text = "Hello World";
 			var height = 20;
 			var height = 20;
-			var lblLeft = new Label (text, direction: TextDirection.TopBottom_LeftRight) { Height = height };
-			var lblCenter = new Label (text, direction: TextDirection.TopBottom_LeftRight) { X = 2, Height = height, VerticalTextAlignment = VerticalTextAlignment.Middle };
-			var lblRight = new Label (text, direction: TextDirection.TopBottom_LeftRight) { X = 4, Height = height, VerticalTextAlignment = VerticalTextAlignment.Bottom };
-			var lblJust = new Label (text, direction: TextDirection.TopBottom_LeftRight) { X = 6, Height = height, VerticalTextAlignment = VerticalTextAlignment.Justified };
+			var lblLeft = new Label (text, direction: TextDirection.TopBottom_LeftRight, autoSize) { Height = height };
+			var lblCenter = new Label (text, direction: TextDirection.TopBottom_LeftRight, autoSize) { X = 2, Height = height, VerticalTextAlignment = VerticalTextAlignment.Middle };
+			var lblRight = new Label (text, direction: TextDirection.TopBottom_LeftRight, autoSize) { X = 4, Height = height, VerticalTextAlignment = VerticalTextAlignment.Bottom };
+			var lblJust = new Label (text, direction: TextDirection.TopBottom_LeftRight, autoSize) { X = 6, Height = height, VerticalTextAlignment = VerticalTextAlignment.Justified };
 			var frame = new FrameView () { Width = Dim.Fill (), Height = Dim.Fill () };
 			var frame = new FrameView () { Width = Dim.Fill (), Height = Dim.Fill () };
 
 
 			frame.Add (lblLeft, lblCenter, lblRight, lblJust);
 			frame.Add (lblLeft, lblCenter, lblRight, lblJust);
@@ -725,14 +757,28 @@ e
 			Application.Begin (Application.Top);
 			Application.Begin (Application.Top);
 			((FakeDriver)Application.Driver).SetBufferSize (9, height + 2);
 			((FakeDriver)Application.Driver).SetBufferSize (9, height + 2);
 
 
-			Assert.True (lblLeft.AutoSize);
-			Assert.True (lblCenter.AutoSize);
-			Assert.True (lblRight.AutoSize);
-			Assert.True (lblJust.AutoSize);
+			Assert.True (lblLeft.AutoSize == autoSize);
+			Assert.True (lblCenter.AutoSize == autoSize);
+			Assert.True (lblRight.AutoSize == autoSize);
+			Assert.True (lblJust.AutoSize == autoSize);
+			Assert.True (lblLeft.TextFormatter.AutoSize == autoSize);
+			Assert.True (lblCenter.TextFormatter.AutoSize == autoSize);
+			Assert.True (lblRight.TextFormatter.AutoSize == autoSize);
+			Assert.True (lblJust.TextFormatter.AutoSize == autoSize);
 			Assert.Equal (new Rect (0, 0, 1, height), lblLeft.Frame);
 			Assert.Equal (new Rect (0, 0, 1, height), lblLeft.Frame);
 			Assert.Equal (new Rect (2, 0, 1, height), lblCenter.Frame);
 			Assert.Equal (new Rect (2, 0, 1, height), lblCenter.Frame);
 			Assert.Equal (new Rect (4, 0, 1, height), lblRight.Frame);
 			Assert.Equal (new Rect (4, 0, 1, height), lblRight.Frame);
 			Assert.Equal (new Rect (6, 0, 1, height), lblJust.Frame);
 			Assert.Equal (new Rect (6, 0, 1, height), lblJust.Frame);
+			if (autoSize) {
+				Assert.Equal (new Size (1, 11), lblLeft.TextFormatter.Size);
+				Assert.Equal (new Size (1, 11), lblCenter.TextFormatter.Size);
+				Assert.Equal (new Size (1, 11), lblRight.TextFormatter.Size);
+			} else {
+				Assert.Equal (new Size (1, height), lblLeft.TextFormatter.Size);
+				Assert.Equal (new Size (1, height), lblCenter.TextFormatter.Size);
+				Assert.Equal (new Size (1, height), lblRight.TextFormatter.Size);
+			}
+			Assert.Equal (new Size (1, height), lblJust.TextFormatter.Size);
 			Assert.Equal (new Rect (0, 0, 9, height + 2), frame.Frame);
 			Assert.Equal (new Rect (0, 0, 9, height + 2), frame.Frame);
 
 
 			var expected = @"
 			var expected = @"