فهرست منبع

Merge pull request #32 from BDisp/tig_v2_2432-DimAuto

Improving TextFormatter.
Tig 1 سال پیش
والد
کامیت
4d49ae8a99

+ 45 - 71
Terminal.Gui/Text/TextFormatter.cs

@@ -269,17 +269,6 @@ public class TextFormatter
 
         List<string> linesFormatted = GetLines ();
 
-        switch (Direction)
-        {
-            case TextDirection.TopBottom_RightLeft:
-            case TextDirection.LeftRight_BottomTop:
-            case TextDirection.RightLeft_BottomTop:
-            case TextDirection.BottomTop_RightLeft:
-                linesFormatted.Reverse ();
-
-                break;
-        }
-
         bool isVertical = IsVerticalDirection (Direction);
         Rectangle maxScreen = screen;
 
@@ -327,15 +316,6 @@ public class TextFormatter
 
             Rune [] runes = linesFormatted [line].ToRunes ();
 
-            runes = Direction switch
-            {
-                TextDirection.RightLeft_BottomTop => runes.Reverse ().ToArray (),
-                TextDirection.RightLeft_TopBottom => runes.Reverse ().ToArray (),
-                TextDirection.BottomTop_LeftRight => runes.Reverse ().ToArray (),
-                TextDirection.BottomTop_RightLeft => runes.Reverse ().ToArray (),
-                _ => runes
-            };
-
             // When text is justified, we lost left or right, so we use the direction to align. 
 
             int x, y;
@@ -345,7 +325,7 @@ public class TextFormatter
             {
                 if (isVertical)
                 {
-                    int runesWidth = GetWidestLineLength (linesFormatted, 0, linesFormatted.Count - line, TabWidth);
+                    int runesWidth = GetColumnsRequiredForVerticalText (linesFormatted, line, linesFormatted.Count - line, TabWidth);
                     x = screen.Right - runesWidth;
                     CursorPosition = screen.Width - runesWidth + (_hotKeyPos > -1 ? _hotKeyPos : 0);
                 }
@@ -361,7 +341,7 @@ public class TextFormatter
                 if (isVertical)
                 {
                     int runesWidth = line > 0
-                                         ? GetWidestLineLength (linesFormatted, 0, line, TabWidth)
+                                         ? GetColumnsRequiredForVerticalText (linesFormatted, 0, line, TabWidth)
                                          : 0;
                     x = screen.Left + runesWidth;
                 }
@@ -376,8 +356,9 @@ public class TextFormatter
             {
                 if (isVertical)
                 {
-                    int runesWidth = GetWidestLineLength (linesFormatted, line, 1, TabWidth);
-                    x = screen.Left + line + (screen.Width - runesWidth) / 2;
+                    int runesWidth = GetColumnsRequiredForVerticalText (linesFormatted, 0, linesFormatted.Count, TabWidth);
+                    int linesWidth = GetColumnsRequiredForVerticalText (linesFormatted, 0, line, TabWidth);
+                    x = screen.Left + linesWidth + (screen.Width - runesWidth) / 2;
 
                     CursorPosition = (screen.Width - runesWidth) / 2 + (_hotKeyPos > -1 ? _hotKeyPos : 0);
                 }
@@ -466,8 +447,8 @@ public class TextFormatter
                         break;
                     }
 
-                    if ((!isVertical && current - start > maxScreen.Left + maxScreen.Width - screen.X + colOffset)
-                        || (isVertical && idx > maxScreen.Top + maxScreen.Height - screen.Y))
+                    if ((!isVertical && (current - start > maxScreen.Left + maxScreen.Width - screen.X + colOffset || (idx < runes.Length && runes [idx].GetColumns () > screen.Width)))
+                        || (isVertical && ((current > start + size + zeroLengthCount && idx > maxScreen.Top + maxScreen.Height - screen.Y) || (idx < runes.Length && runes [idx].GetColumns () > screen.Width))))
                     {
                         break;
                     }
@@ -1367,7 +1348,7 @@ public class TextFormatter
     {
         return StringExtensions.ToString (
                                           runes.GetRange (
-                                                          index,
+                                                          Math.Max (index, 0),
                                                           GetLengthThatFits (text, width, tabWidth, textDirection)
                                                          )
                                          );
@@ -1580,16 +1561,17 @@ public class TextFormatter
 
                 foreach (string line in lines)
                 {
-                    lineResult.Add (ClipAndJustify (line, width, justify, textDirection, tabWidth, textFormatter));
+
+                    lineResult.Add (ClipAndJustify (PerformCorrectFormatDirection (textDirection, line), width, justify, textDirection, tabWidth, textFormatter));
                 }
 
-                return lineResult;
+                return PerformCorrectFormatDirection (textDirection, lineResult);
             }
 
             text = ReplaceCRLFWithSpace (text);
-            lineResult.Add (ClipAndJustify (text, width, justify, textDirection, tabWidth, textFormatter));
+            lineResult.Add (ClipAndJustify (PerformCorrectFormatDirection (textDirection, text), width, justify, textDirection, tabWidth, textFormatter));
 
-            return lineResult;
+            return PerformCorrectFormatDirection (textDirection, lineResult);
         }
 
         List<Rune> runes = StripCRLF (text, true).ToRuneList ();
@@ -1604,7 +1586,7 @@ public class TextFormatter
             {
                 List<string> wrappedLines =
                     WordWrapText (
-                                  StringExtensions.ToString (runes.GetRange (lp, i - lp)),
+                                  StringExtensions.ToString (PerformCorrectFormatDirection (textDirection, runes.GetRange (lp, i - lp))),
                                   width,
                                   preserveTrailingSpaces,
                                   tabWidth,
@@ -1627,7 +1609,7 @@ public class TextFormatter
         }
 
         foreach (string line in WordWrapText (
-                                              StringExtensions.ToString (runes.GetRange (lp, runeCount - lp)),
+                                              StringExtensions.ToString (PerformCorrectFormatDirection (textDirection, runes.GetRange (lp, runeCount - lp))),
                                               width,
                                               preserveTrailingSpaces,
                                               tabWidth,
@@ -1638,7 +1620,36 @@ public class TextFormatter
             lineResult.Add (ClipAndJustify (line, width, justify, textDirection, tabWidth));
         }
 
-        return lineResult;
+        return PerformCorrectFormatDirection (textDirection, lineResult);
+    }
+
+    private static string PerformCorrectFormatDirection (TextDirection textDirection, string line)
+    {
+        return textDirection switch
+               {
+                   TextDirection.RightLeft_BottomTop
+                       or TextDirection.RightLeft_TopBottom
+                       or TextDirection.BottomTop_LeftRight
+                       or TextDirection.BottomTop_RightLeft => StringExtensions.ToString (line.EnumerateRunes ().Reverse ()),
+                   _ => line
+               };
+    }
+
+    private static List<Rune> PerformCorrectFormatDirection (TextDirection textDirection, List<Rune> runes)
+    {
+        return PerformCorrectFormatDirection (textDirection, StringExtensions.ToString (runes)).ToRuneList ();
+    }
+
+    private static List<string> PerformCorrectFormatDirection (TextDirection textDirection, List<string> lines)
+    {
+        return textDirection switch
+               {
+                   TextDirection.TopBottom_RightLeft
+                       or TextDirection.LeftRight_BottomTop
+                       or TextDirection.RightLeft_BottomTop
+                       or TextDirection.BottomTop_RightLeft => lines.ToArray ().Reverse ().ToList (),
+                   _ => lines
+               };
     }
 
     /// <summary>Returns the number of lines needed to render the specified text given the width.</summary>
@@ -1705,43 +1716,6 @@ public class TextFormatter
         return result.Max (x => GetRuneWidth (x, tabWidth));
     }
 
-    /// <summary>
-    ///     Returns the number of columns in the widest line in the list based on the <paramref name="startIndex"/> and
-    ///     the <paramref name="length"/>.
-    /// </summary>
-    /// <remarks>
-    ///     This API will return incorrect results if the text includes glyphs who's width is dependent on surrounding
-    ///     glyphs (e.g. Arabic).
-    /// </remarks>
-    /// <param name="lines">The lines.</param>
-    /// <param name="startIndex">The start index.</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>
-    public static int GetWidestLineLength (
-        List<string> lines,
-        int startIndex = -1,
-        int length = -1,
-        int tabWidth = 0
-    )
-    {
-        var max = 0;
-
-        for (int i = startIndex == -1 ? 0 : startIndex;
-             i < (length == -1 ? lines.Count : startIndex + length);
-             i++)
-        {
-            string runes = lines [i];
-
-            if (runes.Length > 0)
-            {
-                max += runes.EnumerateRunes ().Max (r => GetRuneWidth (r, tabWidth));
-            }
-        }
-
-        return max;
-    }
-
     /// <summary>
     ///     Gets the maximum number of columns from the text based on the <paramref name="startIndex"/> and the
     ///     <paramref name="length"/>.

+ 15 - 17
UICatalog/Scenarios/DimAutoDemo.cs

@@ -34,33 +34,31 @@ public class DimAutoDemo : Scenario
         };
         view.Add (textEdit);
 
-        var hlabel = new Label
-        {
-            Text = textEdit.Text,
-            X = Pos.Left (textEdit) + 1,
-            Y = Pos.Bottom (textEdit),
-            AutoSize = false,
-            Width = Auto (DimAutoStyle.Text, 20),
-            Height = 1,
-            ColorScheme = Colors.ColorSchemes ["Error"]
-        };
-        view.Add (hlabel);
-
         var vlabel = new Label
         {
             Text = textEdit.Text,
             X = Pos.Left (textEdit),
             Y = Pos.Bottom (textEdit) + 1,
-            AutoSize = false,
-            Width = 1,
+            Width = Auto (DimAutoStyle.Text, 1),
             Height = Auto (DimAutoStyle.Text, 8),
-            ColorScheme = Colors.ColorSchemes ["Error"]
-
-            //TextDirection = TextDirection.TopBottom_LeftRight
+            ColorScheme = Colors.ColorSchemes ["Error"],
+            TextDirection = TextDirection.TopBottom_LeftRight
         };
         vlabel.Id = "vlabel";
         view.Add (vlabel);
 
+        var hlabel = new Label
+        {
+            Text = textEdit.Text,
+            X = Pos.Right (vlabel) + 1,
+            Y = Pos.Bottom (textEdit),
+            Width = Auto (DimAutoStyle.Text, 20),
+            Height = Auto (DimAutoStyle.Text, 1),
+            ColorScheme = Colors.ColorSchemes ["Error"]
+        };
+        hlabel.Id = "hlabel";
+        view.Add (hlabel);
+
         var heightAuto = new View
         {
             X = Pos.Right (vlabel) + 1,

+ 124 - 60
UICatalog/Scenarios/TextAlignmentsAndDirection.cs

@@ -33,7 +33,6 @@ public class TextAlignmentsAndDirections : Scenario
         {
             X = 1,
             Y = 1,
-            AutoSize = false,
             Width = 9,
             Height = 1,
             TextAlignment = TextAlignment.Right,
@@ -45,7 +44,6 @@ public class TextAlignmentsAndDirections : Scenario
         {
             X = 1,
             Y = 2,
-            AutoSize = false,
             Width = 9,
             Height = 1,
             TextAlignment = TextAlignment.Right,
@@ -57,7 +55,6 @@ public class TextAlignmentsAndDirections : Scenario
         {
             X = 1,
             Y = 3,
-            AutoSize = false,
             Width = 9,
             Height = 1,
             TextAlignment = TextAlignment.Right,
@@ -69,7 +66,6 @@ public class TextAlignmentsAndDirections : Scenario
         {
             X = 1,
             Y = 4,
-            AutoSize = false,
             Width = 9,
             Height = 1,
             TextAlignment = TextAlignment.Right,
@@ -81,7 +77,6 @@ public class TextAlignmentsAndDirections : Scenario
         {
             X = Pos.Right (labelHL) + 1,
             Y = Pos.Y (labelHL),
-            AutoSize = false,
             Width = Dim.Fill (1) - 9,
             Height = 1,
             ColorScheme = color1,
@@ -93,7 +88,6 @@ public class TextAlignmentsAndDirections : Scenario
         {
             X = Pos.Right (labelHC) + 1,
             Y = Pos.Y (labelHC),
-            AutoSize = false,
             Width = Dim.Fill (1) - 9,
             Height = 1,
             ColorScheme = color2,
@@ -105,7 +99,6 @@ public class TextAlignmentsAndDirections : Scenario
         {
             X = Pos.Right (labelHR) + 1,
             Y = Pos.Y (labelHR),
-            AutoSize = false,
             Width = Dim.Fill (1) - 9,
             Height = 1,
             ColorScheme = color1,
@@ -117,7 +110,6 @@ public class TextAlignmentsAndDirections : Scenario
         {
             X = Pos.Right (labelHJ) + 1,
             Y = Pos.Y (labelHJ),
-            AutoSize = false,
             Width = Dim.Fill (1) - 9,
             Height = 1,
             ColorScheme = color2,
@@ -145,7 +137,6 @@ public class TextAlignmentsAndDirections : Scenario
         {
             X = Pos.AnchorEnd (8),
             Y = 1,
-            AutoSize = false,
             Width = 2,
             Height = 9,
             ColorScheme = color1,
@@ -153,12 +144,12 @@ public class TextAlignmentsAndDirections : Scenario
             VerticalTextAlignment = VerticalTextAlignment.Bottom,
             Text = "Top"
         };
+        labelVT.TextFormatter.WordWrap = false;
 
         var labelVM = new Label
         {
             X = Pos.AnchorEnd (6),
             Y = 1,
-            AutoSize = false,
             Width = 2,
             Height = 9,
             ColorScheme = color1,
@@ -166,12 +157,12 @@ public class TextAlignmentsAndDirections : Scenario
             VerticalTextAlignment = VerticalTextAlignment.Bottom,
             Text = "Middle"
         };
+        labelVM.TextFormatter.WordWrap = false;
 
         var labelVB = new Label
         {
             X = Pos.AnchorEnd (4),
             Y = 1,
-            AutoSize = false,
             Width = 2,
             Height = 9,
             ColorScheme = color1,
@@ -179,71 +170,72 @@ public class TextAlignmentsAndDirections : Scenario
             VerticalTextAlignment = VerticalTextAlignment.Bottom,
             Text = "Bottom"
         };
+        labelVB.TextFormatter.WordWrap = false;
 
         var labelVJ = new Label
         {
             X = Pos.AnchorEnd (2),
             Y = 1,
-            AutoSize = false,
-            Width = 1,
+            Width = 2,
             Height = 9,
             ColorScheme = color1,
             TextDirection = TextDirection.TopBottom_LeftRight,
             VerticalTextAlignment = VerticalTextAlignment.Bottom,
             Text = "Justified"
         };
+        labelVJ.TextFormatter.WordWrap = false;
 
         var txtLabelVT = new Label
         {
             X = Pos.X (labelVT),
             Y = Pos.Bottom (labelVT) + 1,
-            AutoSize = false,
-            Width = 1,
+            Width = 2,
             Height = Dim.Fill (1),
             ColorScheme = color1,
             TextDirection = TextDirection.TopBottom_LeftRight,
             VerticalTextAlignment = VerticalTextAlignment.Top,
             Text = txt
         };
+        txtLabelVT.TextFormatter.WordWrap = false;
 
         var txtLabelVM = new Label
         {
             X = Pos.X (labelVM),
             Y = Pos.Bottom (labelVM) + 1,
-            AutoSize = false,
-            Width = 1,
+            Width = 2,
             Height = Dim.Fill (1),
             ColorScheme = color2,
             TextDirection = TextDirection.TopBottom_LeftRight,
             VerticalTextAlignment = VerticalTextAlignment.Middle,
             Text = txt
         };
+        txtLabelVM.TextFormatter.WordWrap = false;
 
         var txtLabelVB = new Label
         {
             X = Pos.X (labelVB),
             Y = Pos.Bottom (labelVB) + 1,
-            AutoSize = false,
-            Width = 1,
+            Width = 2,
             Height = Dim.Fill (1),
             ColorScheme = color1,
             TextDirection = TextDirection.TopBottom_LeftRight,
             VerticalTextAlignment = VerticalTextAlignment.Bottom,
             Text = txt
         };
+        txtLabelVB.TextFormatter.WordWrap = false;
 
         var txtLabelVJ = new Label
         {
             X = Pos.X (labelVJ),
             Y = Pos.Bottom (labelVJ) + 1,
-            AutoSize = false,
-            Width = 1,
+            Width = 2,
             Height = Dim.Fill (1),
             ColorScheme = color2,
             TextDirection = TextDirection.TopBottom_LeftRight,
             VerticalTextAlignment = VerticalTextAlignment.Justified,
             Text = txt
         };
+        txtLabelVJ.TextFormatter.WordWrap = false;
 
         txts.Add (txtLabelVT);
         txts.Add (txtLabelVM);
@@ -266,7 +258,7 @@ public class TextAlignmentsAndDirections : Scenario
             X = 0,
             Y = Pos.Bottom (txtLabelHJ),
             Width = Dim.Fill (31),
-            Height = Dim.Fill (6),
+            Height = Dim.Fill (4),
             ColorScheme = color2
         };
 
@@ -274,7 +266,6 @@ public class TextAlignmentsAndDirections : Scenario
         {
             X = 1 /*                    */,
             Y = 1,
-            AutoSize = false,
             Width = Dim.Percent (100f / 3f),
             Height = Dim.Percent (100f / 3f),
             TextAlignment = TextAlignment.Left,
@@ -282,12 +273,12 @@ public class TextAlignmentsAndDirections : Scenario
             ColorScheme = color1,
             Text = txt
         };
+        txtLabelTL.TextFormatter.MultiLine = true;
 
         var txtLabelTC = new Label
         {
             X = Pos.Right (txtLabelTL) + 2,
             Y = 1,
-            AutoSize = false,
             Width = Dim.Percent (100f / 3f),
             Height = Dim.Percent (100f / 3f),
             TextAlignment = TextAlignment.Centered,
@@ -295,12 +286,12 @@ public class TextAlignmentsAndDirections : Scenario
             ColorScheme = color1,
             Text = txt
         };
+        txtLabelTC.TextFormatter.MultiLine = true;
 
         var txtLabelTR = new Label
         {
             X = Pos.Right (txtLabelTC) + 2,
             Y = 1,
-            AutoSize = false,
             Width = Dim.Percent (100f, true),
             Height = Dim.Percent (100f / 3f),
             TextAlignment = TextAlignment.Right,
@@ -308,12 +299,12 @@ public class TextAlignmentsAndDirections : Scenario
             ColorScheme = color1,
             Text = txt
         };
+        txtLabelTR.TextFormatter.MultiLine = true;
 
         var txtLabelML = new Label
         {
             X = Pos.X (txtLabelTL),
             Y = Pos.Bottom (txtLabelTL) + 1,
-            AutoSize = false,
             Width = Dim.Width (txtLabelTL),
             Height = Dim.Percent (100f / 3f),
             TextAlignment = TextAlignment.Left,
@@ -321,12 +312,12 @@ public class TextAlignmentsAndDirections : Scenario
             ColorScheme = color1,
             Text = txt
         };
+        txtLabelML.TextFormatter.MultiLine = true;
 
         var txtLabelMC = new Label
         {
             X = Pos.X (txtLabelTC),
             Y = Pos.Bottom (txtLabelTC) + 1,
-            AutoSize = false,
             Width = Dim.Width (txtLabelTC),
             Height = Dim.Percent (100f / 3f),
             TextAlignment = TextAlignment.Centered,
@@ -334,12 +325,12 @@ public class TextAlignmentsAndDirections : Scenario
             ColorScheme = color1,
             Text = txt
         };
+        txtLabelMC.TextFormatter.MultiLine = true;
 
         var txtLabelMR = new Label
         {
             X = Pos.X (txtLabelTR),
             Y = Pos.Bottom (txtLabelTR) + 1,
-            AutoSize = false,
             Width = Dim.Percent (100f, true),
             Height = Dim.Percent (100f / 3f),
             TextAlignment = TextAlignment.Right,
@@ -347,12 +338,12 @@ public class TextAlignmentsAndDirections : Scenario
             ColorScheme = color1,
             Text = txt
         };
+        txtLabelMR.TextFormatter.MultiLine = true;
 
         var txtLabelBL = new Label
         {
             X = Pos.X (txtLabelML),
             Y = Pos.Bottom (txtLabelML) + 1,
-            AutoSize = false,
             Width = Dim.Width (txtLabelML),
             Height = Dim.Percent (100f, true),
             TextAlignment = TextAlignment.Left,
@@ -360,12 +351,12 @@ public class TextAlignmentsAndDirections : Scenario
             ColorScheme = color1,
             Text = txt
         };
+        txtLabelBL.TextFormatter.MultiLine = true;
 
         var txtLabelBC = new Label
         {
             X = Pos.X (txtLabelMC),
             Y = Pos.Bottom (txtLabelMC) + 1,
-            AutoSize = false,
             Width = Dim.Width (txtLabelMC),
             Height = Dim.Percent (100f, true),
             TextAlignment = TextAlignment.Centered,
@@ -373,12 +364,12 @@ public class TextAlignmentsAndDirections : Scenario
             ColorScheme = color1,
             Text = txt
         };
+        txtLabelBC.TextFormatter.MultiLine = true;
 
         var txtLabelBR = new Label
         {
             X = Pos.X (txtLabelMR),
             Y = Pos.Bottom (txtLabelMR) + 1,
-            AutoSize = false,
             Width = Dim.Percent (100f, true),
             Height = Dim.Percent (100f, true),
             TextAlignment = TextAlignment.Right,
@@ -386,6 +377,7 @@ public class TextAlignmentsAndDirections : Scenario
             ColorScheme = color1,
             Text = txt
         };
+        txtLabelBR.TextFormatter.MultiLine = true;
 
         mtxts.Add (txtLabelTL);
         mtxts.Add (txtLabelTC);
@@ -465,42 +457,77 @@ public class TextAlignmentsAndDirections : Scenario
         {
             X = Pos.Right (container) + 1,
             Y = Pos.Y (container) + 1,
-            AutoSize = false,
             Width = Dim.Fill (10),
             Height = 1,
             Text = "Justify"
         };
 
-        justifyCheckbox.Toggled += (s, e) =>
-                                   {
-                                       if (e.OldValue == true)
-                                       {
-                                           foreach (Label t in mtxts)
-                                           {
-                                               t.TextAlignment = (TextAlignment)((dynamic)t.Data).h;
-                                               t.VerticalTextAlignment = (VerticalTextAlignment)((dynamic)t.Data).v;
-                                           }
-                                       }
-                                       else
-                                       {
-                                           foreach (Label t in mtxts)
-                                           {
-                                               if (TextFormatter.IsVerticalDirection (t.TextDirection))
-                                               {
-                                                   t.VerticalTextAlignment = VerticalTextAlignment.Justified;
-                                                   t.TextAlignment = ((dynamic)t.Data).h;
-                                               }
-                                               else
-                                               {
-                                                   t.TextAlignment = TextAlignment.Justified;
-                                                   t.VerticalTextAlignment = ((dynamic)t.Data).v;
-                                               }
-                                           }
-                                       }
-                                   };
+        justifyCheckbox.Toggled += (s, e) => ToggleJustify (e.OldValue is { } && (bool)e.OldValue);
 
         app.Add (justifyCheckbox);
 
+        // WRAP CHECKBOX
+
+        var wrapCheckbox = new CheckBox
+        {
+            X = Pos.Right (container) + 1,
+            Y = Pos.Y (justifyCheckbox) + 1,
+            Width = Dim.Fill (10),
+            Height = 1,
+            Text = "Word Wrap",
+        };
+        wrapCheckbox.Checked = wrapCheckbox.TextFormatter.WordWrap;
+        wrapCheckbox.Toggled += (s, e) =>
+                                {
+                                    if (e.OldValue == true)
+                                    {
+                                        foreach (Label t in mtxts)
+                                        {
+                                            t.TextFormatter.WordWrap = false;
+                                        }
+                                    }
+                                    else
+                                    {
+                                        foreach (Label t in mtxts)
+                                        {
+                                            t.TextFormatter.WordWrap = true;
+                                        }
+                                    }
+                                };
+
+        app.Add (wrapCheckbox);
+
+        // AUTOSIZE CHECKBOX
+
+        var autoSizeCheckbox = new CheckBox
+        {
+            X = Pos.Right (container) + 1,
+            Y = Pos.Y (wrapCheckbox) + 1,
+            Width = Dim.Fill (10),
+            Height = 1,
+            Text = "AutoSize",
+        };
+        autoSizeCheckbox.Checked = autoSizeCheckbox.TextFormatter.AutoSize;
+        autoSizeCheckbox.Toggled += (s, e) =>
+                                    {
+                                        if (e.OldValue == true)
+                                        {
+                                            foreach (Label t in mtxts)
+                                            {
+                                                t.TextFormatter.AutoSize = false;
+                                            }
+                                        }
+                                        else
+                                        {
+                                            foreach (Label t in mtxts)
+                                            {
+                                                t.TextFormatter.AutoSize = true;
+                                            }
+                                        }
+                                    };
+
+        app.Add (autoSizeCheckbox);
+
         // Direction Options
 
         List<TextDirection> directionsEnum = Enum.GetValues (typeof (TextDirection)).Cast<TextDirection> ().ToList ();
@@ -508,7 +535,7 @@ public class TextAlignmentsAndDirections : Scenario
         var directionOptions = new RadioGroup
         {
             X = Pos.Right (container) + 1,
-            Y = Pos.Bottom (justifyCheckbox) + 1,
+            Y = Pos.Bottom (autoSizeCheckbox) + 1,
             Width = Dim.Fill (10),
             Height = Dim.Fill (1),
             HotKeySpecifier = (Rune)'\xffff',
@@ -517,15 +544,52 @@ public class TextAlignmentsAndDirections : Scenario
 
         directionOptions.SelectedItemChanged += (s, ev) =>
                                                 {
+                                                    var justChecked = justifyCheckbox.Checked is { } && (bool)justifyCheckbox.Checked;
+                                                    if (justChecked)
+                                                    {
+                                                        ToggleJustify (true);
+                                                    }
                                                     foreach (Label v in mtxts)
                                                     {
                                                         v.TextDirection = (TextDirection)ev.SelectedItem;
                                                     }
+                                                    if (justChecked)
+                                                    {
+                                                        ToggleJustify (false);
+                                                    }
                                                 };
 
         app.Add (directionOptions);
 
         Application.Run (app);
         app.Dispose ();
+
+        void ToggleJustify (bool oldValue)
+        {
+            if (oldValue == true)
+            {
+                foreach (Label t in mtxts)
+                {
+                    t.TextAlignment = (TextAlignment)((dynamic)t.Data).h;
+                    t.VerticalTextAlignment = (VerticalTextAlignment)((dynamic)t.Data).v;
+                }
+            }
+            else
+            {
+                foreach (Label t in mtxts)
+                {
+                    if (TextFormatter.IsVerticalDirection (t.TextDirection))
+                    {
+                        t.VerticalTextAlignment = VerticalTextAlignment.Justified;
+                        t.TextAlignment = ((dynamic)t.Data).h;
+                    }
+                    else
+                    {
+                        t.TextAlignment = TextAlignment.Justified;
+                        t.VerticalTextAlignment = ((dynamic)t.Data).v;
+                    }
+                }
+            }
+        }
     }
 }

+ 458 - 13
UnitTests/Text/TextFormatterTests.cs

@@ -3639,6 +3639,214 @@ ek")]
         TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output);
     }
 
+    [SetupFakeDriver]
+    [Theory]
+    [InlineData ("A", 0, 1, false, "", 0)]
+    [InlineData ("A", 1, 1, false, "A", 0)]
+    [InlineData ("A", 2, 2, false, " A", 1)]
+    [InlineData ("AB", 1, 1, false, "B", 0)]
+    [InlineData ("AB", 2, 2, false, " A\n B", 0)]
+    [InlineData ("ABC", 3, 2, false, "  B\n  C", 0)]
+    [InlineData ("ABC", 4, 2, false, "   B\n   C", 0)]
+    [InlineData ("ABC", 6, 2, false, "     B\n     C", 0)]
+    [InlineData ("こんにちは", 0, 1, false, "", 0)]
+    [InlineData ("こんにちは", 1, 0, false, "", 0)]
+    [InlineData ("こんにちは", 1, 1, false, "", 0)]
+    [InlineData ("こんにちは", 2, 1, false, "は", 0)]
+    [InlineData ("こんにちは", 2, 2, false, "ち\nは", 0)]
+    [InlineData ("こんにちは", 2, 3, false, "に\nち\nは", 0)]
+    [InlineData ("こんにちは", 2, 4, false, "ん\nに\nち\nは", 0)]
+    [InlineData ("こんにちは", 2, 5, false, "こ\nん\nに\nち\nは", 0)]
+    [InlineData ("こんにちは", 2, 6, false, "こ\nん\nに\nち\nは", 1)]
+    [InlineData ("ABCD\nこんにちは", 4, 7, false, "  こ\n Aん\n Bに\n Cち\n Dは", 2)]
+    [InlineData ("こんにちは\nABCD", 3, 7, false, "こ \nんA\nにB\nちC\nはD", 2)]
+
+    [InlineData ("A", 0, 1, true, "", 0)]
+    [InlineData ("A", 1, 1, true, "A", 0)]
+    [InlineData ("A", 2, 2, true, " A", 1)]
+    [InlineData ("AB", 1, 1, true, "B", 0)]
+    [InlineData ("AB", 2, 2, true, " A\n B", 0)]
+    [InlineData ("ABC", 3, 2, true, "  B\n  C", 0)]
+    [InlineData ("ABC", 4, 2, true, "   B\n   C", 0)]
+    [InlineData ("ABC", 6, 2, true, "     B\n     C", 0)]
+    [InlineData ("こんにちは", 0, 1, true, "", 0)]
+    [InlineData ("こんにちは", 1, 0, true, "", 0)]
+    [InlineData ("こんにちは", 1, 1, true, "", 0)]
+    [InlineData ("こんにちは", 2, 1, true, "は", 0)]
+    [InlineData ("こんにちは", 2, 2, true, "ち\nは", 0)]
+    [InlineData ("こんにちは", 2, 3, true, "に\nち\nは", 0)]
+    [InlineData ("こんにちは", 2, 4, true, "ん\nに\nち\nは", 0)]
+    [InlineData ("こんにちは", 2, 5, true, "こ\nん\nに\nち\nは", 0)]
+    [InlineData ("こんにちは", 2, 6, true, "こ\nん\nに\nち\nは", 1)]
+    [InlineData ("ABCD\nこんにちは", 4, 7, true, "  こ\n Aん\n Bに\n Cち\n Dは", 2)]
+    [InlineData ("こんにちは\nABCD", 3, 7, true, "こ \nんA\nにB\nちC\nはD", 2)]
+    public void Draw_Vertical_Bottom_Horizontal_Right (string text, int width, int height, bool autoSize, string expectedText, int expectedY)
+    {
+        TextFormatter tf = new ()
+        {
+            Text = text,
+            Alignment = TextAlignment.Right,
+            Direction = TextDirection.TopBottom_LeftRight,
+            VerticalAlignment = VerticalTextAlignment.Bottom,
+            AutoSize = autoSize,
+        };
+
+        if (!autoSize)
+        {
+            tf.Size = new Size (width, height);
+        }
+
+        tf.Draw (new Rectangle (Point.Empty, new (width, height)), Attribute.Default, Attribute.Default);
+        Rectangle rect = TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output);
+        Assert.Equal (expectedY, rect.Y);
+    }
+
+    [SetupFakeDriver]
+    [Theory]
+    [InlineData ("A", 5, false, "A")]
+    [InlineData ("AB12", 5, false, @"
+A
+B
+1
+2")]
+    [InlineData ("AB\n12", 5, false, @"
+A1
+B2")]
+    [InlineData ("", 1, false, "")]
+    [InlineData ("AB1 2", 2, false, @"
+A12
+B  ")]
+    [InlineData ("こんにちは", 1, false, @"
+こん")]
+    [InlineData ("こんにちは", 2, false, @"
+こに
+んち")]
+    [InlineData ("こんにちは", 5, false, @"
+こ
+ん
+に
+ち
+は")]
+
+    [InlineData ("A", 5, true, "A")]
+    [InlineData ("AB12", 5, true, @"
+A
+B
+1
+2")]
+    [InlineData ("AB\n12", 5, true, @"
+A1
+B2")]
+    [InlineData ("", 1, true, "")]
+    [InlineData ("AB1 2", 2, true, @"
+A
+B")]
+    [InlineData ("こんにちは", 1, true, @"
+こ")]
+    [InlineData ("こんにちは", 2, true, @"
+こ
+ん")]
+    [InlineData ("こんにちは", 5, true, @"
+こ
+ん
+に
+ち
+は")]
+    public void Draw_Vertical_TopBottom_LeftRight_Top (string text, int height, bool autoSize, string expectedText)
+    {
+        TextFormatter tf = new ()
+        {
+            Text = text,
+            AutoSize = autoSize,
+            Direction = TextDirection.TopBottom_LeftRight,
+        };
+
+        if (!autoSize)
+        {
+            tf.Size = new Size (5, height);
+        }
+        tf.Draw (new Rectangle (0, 0, 5, height), Attribute.Default, Attribute.Default);
+
+        TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output);
+    }
+
+    [SetupFakeDriver]
+    [Theory]
+
+    // The expectedY param is to probe that the expectedText param start at that Y coordinate
+
+    [InlineData ("A", 0, false, "", 0)]
+    [InlineData ("A", 1, false, "A", 0)]
+    [InlineData ("A", 2, false, "A", 0)]
+    [InlineData ("A", 3, false, "A", 1)]
+    [InlineData ("AB", 1, false, "A", 0)]
+    [InlineData ("AB", 2, false, "A\nB", 0)]
+    [InlineData ("ABC", 2, false, "A\nB", 0)]
+    [InlineData ("ABC", 3, false, "A\nB\nC", 0)]
+    [InlineData ("ABC", 4, false, "A\nB\nC", 0)]
+    [InlineData ("ABC", 5, false, "A\nB\nC", 1)]
+    [InlineData ("ABC", 6, false, "A\nB\nC", 1)]
+    [InlineData ("ABC", 9, false, "A\nB\nC", 3)]
+    [InlineData ("ABCD", 2, false, "B\nC", 0)]
+    [InlineData ("こんにちは", 0, false, "", 0)]
+    [InlineData ("こんにちは", 1, false, "に", 0)]
+    [InlineData ("こんにちは", 2, false, "ん\nに", 0)]
+    [InlineData ("こんにちは", 3, false, "ん\nに\nち", 0)]
+    [InlineData ("こんにちは", 4, false, "こ\nん\nに\nち", 0)]
+    [InlineData ("こんにちは", 5, false, "こ\nん\nに\nち\nは", 0)]
+    [InlineData ("こんにちは", 6, false, "こ\nん\nに\nち\nは", 0)]
+    [InlineData ("ABCD\nこんにちは", 7, false, "Aこ\nBん\nCに\nDち\n は", 1)]
+    [InlineData ("こんにちは\nABCD", 7, false, "こA\nんB\nにC\nちD\nは ", 1)]
+
+    [InlineData ("A", 0, true, "", 0)]
+    [InlineData ("A", 1, true, "A", 0)]
+    [InlineData ("A", 2, true, "A", 0)]
+    [InlineData ("A", 3, true, "A", 1)]
+    [InlineData ("AB", 1, true, "A", 0)]
+    [InlineData ("AB", 2, true, "A\nB", 0)]
+    [InlineData ("ABC", 2, true, "A\nB", 0)]
+    [InlineData ("ABC", 3, true, "A\nB\nC", 0)]
+    [InlineData ("ABC", 4, true, "A\nB\nC", 0)]
+    [InlineData ("ABC", 5, true, "A\nB\nC", 1)]
+    [InlineData ("ABC", 6, true, "A\nB\nC", 1)]
+    [InlineData ("ABC", 9, true, "A\nB\nC", 3)]
+    [InlineData ("ABCD", 2, true, "B\nC", 0)]
+    [InlineData ("こんにちは", 0, true, "", 0)]
+    [InlineData ("こんにちは", 1, true, "に", 0)]
+    [InlineData ("こんにちは", 2, true, "ん\nに", 0)]
+    [InlineData ("こんにちは", 3, true, "ん\nに\nち", 0)]
+    [InlineData ("こんにちは", 4, true, "こ\nん\nに\nち", 0)]
+    [InlineData ("こんにちは", 5, true, "こ\nん\nに\nち\nは", 0)]
+    [InlineData ("こんにちは", 6, true, "こ\nん\nに\nち\nは", 0)]
+    [InlineData ("こんにちは", 7, true, "こ\nん\nに\nち\nは", 1)]
+    [InlineData ("ABCD\nこんにちは", 7, true, "Aこ\nBん\nCに\nDち\n は", 1)]
+    [InlineData ("こんにちは\nABCD", 7, true, "こA\nんB\nにC\nちD\nは ", 1)]
+    public void Draw_Vertical_TopBottom_LeftRight_Middle (string text, int height, bool autoSize, string expectedText, int expectedY)
+    {
+        TextFormatter tf = new ()
+        {
+            Text = text,
+            Direction = TextDirection.TopBottom_LeftRight,
+            VerticalAlignment = VerticalTextAlignment.Middle,
+            AutoSize = autoSize,
+        };
+
+        if (!autoSize)
+        {
+            int width = text.ToRunes ().Max (r => r.GetColumns ());
+
+            if (text.Contains ("\n"))
+            {
+                width++;
+            }
+
+            tf.Size = new Size (width, height);
+        }
+        tf.Draw (new Rectangle (0, 0, 5, height), Attribute.Default, Attribute.Default);
+
+        Rectangle rect = TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output);
+        Assert.Equal (expectedY, rect.Y);
+    }
 
     [Theory]
     [InlineData ("1234", 4)]
@@ -3665,6 +3873,178 @@ ek")]
         Assert.Equal (new (1, expected), tf.Size);
     }
 
+    [SetupFakeDriver]
+    [Theory]
+    [InlineData ("A", 1, 0, false, "")]
+    [InlineData ("A", 0, 1, false, "")]
+    [InlineData ("AB1 2", 2, 1, false, "2")]
+    [InlineData ("AB12", 5, 1, false, "21BA")]
+    [InlineData ("AB\n12", 5, 2, false, "BA\n21")]
+    [InlineData ("ABC 123 456", 7, 2, false, "654 321\nCBA    ")]
+    [InlineData ("こんにちは", 1, 1, false, "")]
+    [InlineData ("こんにちは", 2, 1, false, "は")]
+    [InlineData ("こんにちは", 5, 1, false, "はち")]
+    [InlineData ("こんにちは", 10, 1, false, "はちにんこ")]
+    [InlineData ("こんにちは\nAB\n12", 10, 3, false, "はちにんこ\nBA        \n21        ")]
+
+    [InlineData ("A", 1, 0, true, "")]
+    [InlineData ("A", 0, 1, true, "")]
+    [InlineData ("AB1 2", 2, 1, true, "2")]
+    [InlineData ("AB12", 5, 1, true, "21BA")]
+    [InlineData ("AB\n12", 5, 2, true, "BA\n21")]
+    [InlineData ("ABC 123 456", 7, 2, true, "654 321")]
+    [InlineData ("こんにちは", 1, 1, true, "")]
+    [InlineData ("こんにちは", 2, 1, true, "は")]
+    [InlineData ("こんにちは", 5, 1, true, "はち")]
+    [InlineData ("こんにちは", 10, 1, true, "はちにんこ")]
+    [InlineData ("こんにちは\nAB\n12", 10, 3, true, "はちにんこ\nBA        \n21        ")]
+    public void Draw_Horizontal_RightLeft_TopBottom (string text, int width, int height, bool autoSize, string expectedText)
+    {
+        TextFormatter tf = new ()
+        {
+            Text = text,
+            Direction = TextDirection.RightLeft_TopBottom,
+            AutoSize = autoSize,
+        };
+
+        if (!autoSize)
+        {
+            tf.Size = new Size (width, height);
+        }
+        tf.Draw (new Rectangle (0, 0, width, height), Attribute.Default, Attribute.Default);
+
+        TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output);
+    }
+
+    [SetupFakeDriver]
+    [Theory]
+    [InlineData ("A", 1, 0, false, "")]
+    [InlineData ("A", 0, 1, false, "")]
+    [InlineData ("AB1 2", 2, 1, false, "2")]
+    [InlineData ("AB12", 5, 1, false, "21BA")]
+    [InlineData ("AB\n12", 5, 2, false, "21\nBA")]
+    [InlineData ("ABC 123 456", 7, 2, false, "CBA    \n654 321")]
+    [InlineData ("こんにちは", 1, 1, false, "")]
+    [InlineData ("こんにちは", 2, 1, false, "は")]
+    [InlineData ("こんにちは", 5, 1, false, "はち")]
+    [InlineData ("こんにちは", 10, 1, false, "はちにんこ")]
+    [InlineData ("こんにちは\nAB\n12", 10, 3, false, "21        \nBA        \nはちにんこ")]
+
+    [InlineData ("A", 1, 0, true, "")]
+    [InlineData ("A", 0, 1, true, "")]
+    [InlineData ("AB1 2", 2, 1, true, "2")]
+    [InlineData ("AB12", 5, 1, true, "21BA")]
+    [InlineData ("AB\n12", 5, 2, true, "21\nBA")]
+    [InlineData ("ABC 123 456", 7, 2, true, "654 321")]
+    [InlineData ("こんにちは", 1, 1, true, "")]
+    [InlineData ("こんにちは", 2, 1, true, "は")]
+    [InlineData ("こんにちは", 5, 1, true, "はち")]
+    [InlineData ("こんにちは", 10, 1, true, "はちにんこ")]
+    [InlineData ("こんにちは\nAB\n12", 10, 3, true, "21        \nBA        \nはちにんこ")]
+    public void Draw_Horizontal_RightLeft_BottomTop (string text, int width, int height, bool autoSize, string expectedText)
+    {
+        TextFormatter tf = new ()
+        {
+            Text = text,
+            Direction = TextDirection.RightLeft_BottomTop,
+            AutoSize = autoSize,
+        };
+
+        if (!autoSize)
+        {
+            tf.Size = new Size (width, height);
+        }
+        tf.Draw (new Rectangle (0, 0, width, height), Attribute.Default, Attribute.Default);
+
+        TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output);
+    }
+
+    [SetupFakeDriver]
+    [Theory]
+    [InlineData ("A", 1, 0, false, "")]
+    [InlineData ("A", 0, 1, false, "")]
+    [InlineData ("AB1 2", 1, 2, false, "2")]
+    [InlineData ("AB12", 1, 5, false, "2\n1\nB\nA")]
+    [InlineData ("AB\n12", 2, 5, false, "B2\nA1")]
+    [InlineData ("ABC 123 456", 2, 7, false, "6C\n5B\n4A\n  \n3 \n2 \n1 ")]
+    [InlineData ("こんにちは", 1, 1, false, "")]
+    [InlineData ("こんにちは", 2, 1, false, "は")]
+    [InlineData ("こんにちは", 2, 5, false, "は\nち\nに\nん\nこ")]
+    [InlineData ("こんにちは", 2, 10, false, "は\nち\nに\nん\nこ")]
+    [InlineData ("こんにちは\nAB\n12", 4, 10, false, "はB2\nちA1\nに  \nん  \nこ  ")]
+
+    [InlineData ("A", 1, 0, true, "")]
+    [InlineData ("A", 0, 1, true, "")]
+    [InlineData ("AB1 2", 1, 2, true, "2")]
+    [InlineData ("AB12", 1, 5, true, "2\n1\nB\nA")]
+    [InlineData ("AB\n12", 2, 5, true, "B2\nA1")]
+    [InlineData ("ABC 123 456", 2, 7, true, "6\n5\n4\n \n3\n2\n1")]
+    [InlineData ("こんにちは", 1, 1, true, "")]
+    [InlineData ("こんにちは", 2, 1, true, "は")]
+    [InlineData ("こんにちは", 2, 5, true, "は\nち\nに\nん\nこ")]
+    [InlineData ("こんにちは", 2, 10, true, "は\nち\nに\nん\nこ")]
+    [InlineData ("こんにちは\nAB\n12", 4, 10, true, "はB2\nちA1\nに  \nん  \nこ  ")]
+    public void Draw_Vertical_BottomTop_LeftRight (string text, int width, int height, bool autoSize, string expectedText)
+    {
+        TextFormatter tf = new ()
+        {
+            Text = text,
+            Direction = TextDirection.BottomTop_LeftRight,
+            AutoSize = autoSize,
+        };
+
+        if (!autoSize)
+        {
+            tf.Size = new Size (width, height);
+        }
+        tf.Draw (new Rectangle (0, 0, width, height), Attribute.Default, Attribute.Default);
+
+        TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output);
+    }
+
+    [SetupFakeDriver]
+    [Theory]
+    [InlineData ("A", 1, 0, false, "")]
+    [InlineData ("A", 0, 1, false, "")]
+    [InlineData ("AB1 2", 1, 2, false, "2")]
+    [InlineData ("AB12", 1, 5, false, "2\n1\nB\nA")]
+    [InlineData ("AB\n12", 2, 5, false, "2B\n1A")]
+    [InlineData ("ABC 123 456", 2, 7, false, "C6\nB5\nA4\n  \n 3\n 2\n 1")]
+    [InlineData ("こんにちは", 1, 1, false, "")]
+    [InlineData ("こんにちは", 2, 1, false, "は")]
+    [InlineData ("こんにちは", 2, 5, false, "は\nち\nに\nん\nこ")]
+    [InlineData ("こんにちは", 2, 10, false, "は\nち\nに\nん\nこ")]
+    [InlineData ("こんにちは\nAB\n12", 4, 10, false, "2Bは\n1Aち\n  に\n  ん\n  こ")]
+
+    [InlineData ("A", 1, 0, true, "")]
+    [InlineData ("A", 0, 1, true, "")]
+    [InlineData ("AB1 2", 1, 2, true, "2")]
+    [InlineData ("AB12", 1, 5, true, "2\n1\nB\nA")]
+    [InlineData ("AB\n12", 2, 5, true, "2B\n1A")]
+    [InlineData ("ABC 123 456", 2, 7, true, "6\n5\n4\n \n3\n2\n1")]
+    [InlineData ("こんにちは", 1, 1, true, "")]
+    [InlineData ("こんにちは", 2, 1, true, "は")]
+    [InlineData ("こんにちは", 2, 5, true, "は\nち\nに\nん\nこ")]
+    [InlineData ("こんにちは", 2, 10, true, "は\nち\nに\nん\nこ")]
+    [InlineData ("こんにちは\nAB\n12", 4, 10, true, "2Bは\n1Aち\n  に\n  ん\n  こ")]
+    public void Draw_Vertical_BottomTop_RightLeft (string text, int width, int height, bool autoSize, string expectedText)
+    {
+        TextFormatter tf = new ()
+        {
+            Text = text,
+            Direction = TextDirection.BottomTop_RightLeft,
+            AutoSize = autoSize,
+        };
+
+        if (!autoSize)
+        {
+            tf.Size = new Size (width, height);
+        }
+        tf.Draw (new Rectangle (0, 0, width, height), Attribute.Default, Attribute.Default);
+
+        TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output);
+    }
+
     // Draw tests - Note that these depend on View
 
     [Fact]
@@ -3700,10 +4080,10 @@ ek")]
         Application.Shutdown ();
     }
 
-    //TODO: Expand this test to cover Vertical Alignment as well
+    //FIXED: Expand this test to cover Vertical Alignment as well
     [SetupFakeDriver]
     [Theory]
-    [InlineData ("0 2 4", TextAlignment.Left, TextDirection.LeftRight_BottomTop, @"
+    [InlineData ("0 2 4", TextAlignment.Left, VerticalTextAlignment.Top, TextDirection.LeftRight_BottomTop, @"
 0 2 4**
 *******
 *******
@@ -3711,7 +4091,7 @@ ek")]
 *******
 *******
 *******")]
-    [InlineData ("0 2 4", TextAlignment.Right, TextDirection.LeftRight_BottomTop, @"
+    [InlineData ("0 2 4", TextAlignment.Right, VerticalTextAlignment.Top, TextDirection.LeftRight_BottomTop, @"
 **0 2 4
 *******
 *******
@@ -3719,7 +4099,7 @@ ek")]
 *******
 *******
 *******")]
-    [InlineData ("0 2 4", TextAlignment.Centered, TextDirection.LeftRight_BottomTop, @"
+    [InlineData ("0 2 4", TextAlignment.Centered, VerticalTextAlignment.Top, TextDirection.LeftRight_BottomTop, @"
 *0 2 4*
 *******
 *******
@@ -3727,8 +4107,7 @@ ek")]
 *******
 *******
 *******")]
-
-    [InlineData ("0 2 4", TextAlignment.Justified, TextDirection.LeftRight_BottomTop, @"
+    [InlineData ("0 2 4", TextAlignment.Justified, VerticalTextAlignment.Top, TextDirection.LeftRight_BottomTop, @"
 0  2  4
 *******
 *******
@@ -3737,15 +4116,15 @@ ek")]
 *******
 *******")]
 
-    [InlineData ("0 2 4", TextAlignment.Left, TextDirection.LeftRight_BottomTop, @"
-0 2 4**
+    [InlineData ("0 你 4", TextAlignment.Left, VerticalTextAlignment.Top, TextDirection.LeftRight_BottomTop, @"
+0 你 4*
 *******
 *******
 *******
 *******
 *******
 *******")]
-    [InlineData ("0 你 4", TextAlignment.Right, TextDirection.LeftRight_BottomTop, @"
+    [InlineData ("0 你 4", TextAlignment.Right, VerticalTextAlignment.Top, TextDirection.LeftRight_BottomTop, @"
 *0 你 4
 *******
 *******
@@ -3753,7 +4132,7 @@ ek")]
 *******
 *******
 *******")]
-    [InlineData ("0 你 4", TextAlignment.Centered, TextDirection.LeftRight_BottomTop, @"
+    [InlineData ("0 你 4", TextAlignment.Centered, VerticalTextAlignment.Top, TextDirection.LeftRight_BottomTop, @"
 0 你 4*
 *******
 *******
@@ -3761,8 +4140,7 @@ ek")]
 *******
 *******
 *******")]
-
-    [InlineData ("0 你 4", TextAlignment.Justified, TextDirection.LeftRight_BottomTop, @"
+    [InlineData ("0 你 4", TextAlignment.Justified, VerticalTextAlignment.Top, TextDirection.LeftRight_BottomTop, @"
 0  你 4
 *******
 *******
@@ -3770,11 +4148,78 @@ ek")]
 *******
 *******
 *******")]
-    public void Draw_Text_Alignment (string text, TextAlignment horizontalTextAlignment, TextDirection textDirection, string expectedText)
+
+    [InlineData ("0 2 4", TextAlignment.Left, VerticalTextAlignment.Top, TextDirection.TopBottom_RightLeft, @"
+0******
+ ******
+2******
+ ******
+4******
+*******
+*******")]
+    [InlineData ("0 2 4", TextAlignment.Left, VerticalTextAlignment.Bottom, TextDirection.TopBottom_RightLeft, @"
+*******
+*******
+0******
+ ******
+2******
+ ******
+4******")]
+    [InlineData ("0 2 4", TextAlignment.Left, VerticalTextAlignment.Middle, TextDirection.TopBottom_RightLeft, @"
+*******
+0******
+ ******
+2******
+ ******
+4******
+*******")]
+    [InlineData ("0 2 4", TextAlignment.Left, VerticalTextAlignment.Justified, TextDirection.TopBottom_RightLeft, @"
+0******
+ ******
+ ******
+2******
+ ******
+ ******
+4******")]
+
+    [InlineData ("0 你 4", TextAlignment.Left, VerticalTextAlignment.Top, TextDirection.TopBottom_RightLeft, @"
+0******
+ ******
+你*****
+ ******
+4******
+*******
+*******")]
+    [InlineData ("0 你 4", TextAlignment.Left, VerticalTextAlignment.Bottom, TextDirection.TopBottom_RightLeft, @"
+*******
+*******
+0******
+ ******
+你*****
+ ******
+4******")]
+    [InlineData ("0 你 4", TextAlignment.Left, VerticalTextAlignment.Middle, TextDirection.TopBottom_RightLeft, @"
+*******
+0******
+ ******
+你*****
+ ******
+4******
+*******")]
+    [InlineData ("0 你 4", TextAlignment.Left, VerticalTextAlignment.Justified, TextDirection.TopBottom_RightLeft, @"
+0******
+ ******
+ ******
+你*****
+ ******
+ ******
+4******")]
+    public void Draw_Text_Alignment (string text, TextAlignment horizontalTextAlignment, VerticalTextAlignment verticalTextAlignment, TextDirection textDirection, string expectedText)
     {
         TextFormatter tf = new ()
         {
             Alignment = horizontalTextAlignment,
+            VerticalAlignment = verticalTextAlignment,
             Direction = textDirection,
             Size = new (7, 7),
             Text = text