diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index 4acc14554..ba2182f77 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -269,17 +269,6 @@ public class TextFormatter List 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; @@ -467,8 +447,8 @@ public class TextFormatter break; } - if ((!isVertical && current - start > maxScreen.Left + maxScreen.Width - screen.X + colOffset) - || (isVertical && current > start + size + zeroLengthCount && 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; } @@ -1368,7 +1348,7 @@ public class TextFormatter { return StringExtensions.ToString ( runes.GetRange ( - index, + Math.Max (index, 0), GetLengthThatFits (text, width, tabWidth, textDirection) ) ); @@ -1581,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 runes = StripCRLF (text, true).ToRuneList (); @@ -1605,7 +1586,7 @@ public class TextFormatter { List wrappedLines = WordWrapText ( - StringExtensions.ToString (runes.GetRange (lp, i - lp)), + StringExtensions.ToString (PerformCorrectFormatDirection (textDirection, runes.GetRange (lp, i - lp))), width, preserveTrailingSpaces, tabWidth, @@ -1628,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, @@ -1639,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 PerformCorrectFormatDirection (TextDirection textDirection, List runes) + { + return PerformCorrectFormatDirection (textDirection, StringExtensions.ToString (runes)).ToRuneList (); + } + + private static List PerformCorrectFormatDirection (TextDirection textDirection, List lines) + { + return textDirection switch + { + TextDirection.TopBottom_RightLeft + or TextDirection.LeftRight_BottomTop + or TextDirection.RightLeft_BottomTop + or TextDirection.BottomTop_RightLeft => lines.ToArray ().Reverse ().ToList (), + _ => lines + }; } /// Returns the number of lines needed to render the specified text given the width. diff --git a/UnitTests/Text/TextFormatterTests.cs b/UnitTests/Text/TextFormatterTests.cs index ef54b187e..799e33fe1 100644 --- a/UnitTests/Text/TextFormatterTests.cs +++ b/UnitTests/Text/TextFormatterTests.cs @@ -3872,4 +3872,176 @@ B")] }; 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); + } }