Browse Source

Checked out Justifier from v2_1785-PosJustify

Tig 1 year ago
parent
commit
b6b29fec1a
2 changed files with 760 additions and 0 deletions
  1. 332 0
      Terminal.Gui/Drawing/Justification.cs
  2. 428 0
      UnitTests/Drawing/JustifierTests.cs

+ 332 - 0
Terminal.Gui/Drawing/Justification.cs

@@ -0,0 +1,332 @@
+namespace Terminal.Gui;
+
+/// <summary>
+///     Controls how the <see cref="Justifier"/> justifies items within a container. 
+/// </summary>
+public enum Justification
+{
+    /// <summary>
+    ///     The items will be aligned to the left.
+    ///     Set <see cref="Justifier.PutSpaceBetweenItems"/> to <see langword="true"/> to ensure at least one space between
+    ///     each item.
+    /// </summary>
+    /// <example>
+    ///     <c>
+    ///         111 2222 33333
+    ///     </c>
+    /// </example>
+    Left,
+
+    /// <summary>
+    ///     The items will be aligned to the right.
+    ///     Set <see cref="Justifier.PutSpaceBetweenItems"/> to <see langword="true"/> to ensure at least one space between
+    ///     each item.
+    /// </summary>
+    /// <example>
+    ///     <c>
+    ///         111 2222 33333
+    ///     </c>
+    /// </example>
+    Right,
+
+    /// <summary>
+    ///     The group will be centered in the container.
+    ///     If centering is not possible, the group will be left-justified.
+    ///     Set <see cref="Justifier.PutSpaceBetweenItems"/> to <see langword="true"/> to ensure at least one space between
+    ///     each item.
+    /// </summary>
+    /// <example>
+    ///     <c>
+    ///         111 2222 33333
+    ///     </c>
+    /// </example>
+    Centered,
+
+    /// <summary>
+    ///     The items will be justified. Space will be added between the items such that the first item
+    ///     is at the start and the right side of the last item against the end.
+    ///     Set <see cref="Justifier.PutSpaceBetweenItems"/> to <see langword="true"/> to ensure at least one space between
+    ///     each item.
+    /// </summary>
+    /// <example>
+    ///     <c>
+    ///         111    2222     33333
+    ///     </c>
+    /// </example>
+    Justified,
+
+    /// <summary>
+    ///     The first item will be aligned to the left and the remaining will aligned to the right.
+    ///     Set <see cref="Justifier.PutSpaceBetweenItems"/> to <see langword="true"/> to ensure at least one space between
+    ///     each item.
+    /// </summary>
+    /// <example>
+    ///     <c>
+    ///         111        2222 33333
+    ///     </c>
+    /// </example>
+    FirstLeftRestRight,
+
+    /// <summary>
+    ///     The last item will be aligned to the right and the remaining will aligned to the left.
+    ///     Set <see cref="Justifier.PutSpaceBetweenItems"/> to <see langword="true"/> to ensure at least one space between
+    ///     each item.
+    /// </summary>
+    /// <example>
+    ///     <c>
+    ///         111 2222        33333
+    ///     </c>
+    /// </example>
+    LastRightRestLeft
+}
+
+/// <summary>
+///     Justifies items within a container based on the specified <see cref="Justification"/>.
+/// </summary>
+public class Justifier
+{
+    /// <summary>
+    /// Gets or sets how the <see cref="Justifier"/> justifies items within a container.
+    /// </summary>
+    public Justification Justification { get; set; }
+
+    /// <summary>
+    /// The size of the container.
+    /// </summary>
+    public int ContainerSize { get; set; }
+
+    /// <summary>
+    ///     Gets or sets whether <see cref="Justify"/> puts a space is placed between items. Default is <see langword="false"/>. If <see langword="true"/>, a space will be
+    ///     placed between each item, which is useful for justifying text.
+    /// </summary>
+    public bool PutSpaceBetweenItems { get; set; }
+
+    /// <summary>
+    ///     Takes a list of items and returns their positions when justified within a container <see name="ContainerSize"/> wide based on the specified
+    ///     <see cref="Justification"/>.
+    /// </summary>
+    /// <param name="sizes">The sizes of the items to justify.</param>
+    /// <returns>The locations of the items, from left to right.</returns>
+    public int [] Justify (int [] sizes)
+    {
+        return Justify (Justification, PutSpaceBetweenItems, ContainerSize, sizes);
+    }
+
+    /// <summary>
+    ///     Takes a list of items and returns their positions when justified within a container <paramref name="containerSize"/> wide based on the specified
+    ///     <see cref="Justification"/>.
+    /// </summary>
+    /// <param name="sizes">The sizes of the items to justify.</param>
+    /// <param name="justification">The justification style.</param>
+    /// <param name="containerSize">The size of the container.</param>
+    /// <returns>The locations of the items, from left to right.</returns>
+    public static int [] Justify (Justification justification, bool putSpaceBetweenItems, int containerSize, int [] sizes)
+    {
+        if (sizes.Length == 0)
+        {
+            return new int [] { };
+        }
+
+        int maxSpaceBetweenItems = putSpaceBetweenItems ? 1 : 0;
+
+        var positions = new int [sizes.Length]; // positions of the items. the return value.
+        int totalItemsSize = sizes.Sum ();
+        int totalGaps = sizes.Length - 1; // total gaps between items
+        int totalItemsAndSpaces = totalItemsSize + totalGaps * maxSpaceBetweenItems; // total size of items and spaces if we had enough room
+
+        int spaces = totalGaps * maxSpaceBetweenItems; // We'll decrement this below to place one space between each item until we run out
+        if (totalItemsSize >= containerSize)
+        {
+            spaces = 0;
+        }
+        else if (totalItemsAndSpaces > containerSize)
+        {
+            spaces = containerSize - totalItemsSize;
+        }
+
+        switch (justification)
+        {
+            case Justification.Left:
+                var currentPosition = 0;
+
+                for (var i = 0; i < sizes.Length; i++)
+                {
+                    if (sizes [i] < 0)
+                    {
+                        throw new ArgumentException ("The size of an item cannot be negative.");
+                    }
+
+                    if (i == 0)
+                    {
+                        positions [0] = 0; // first item position
+
+                        continue;
+                    }
+
+                    int spaceBefore = spaces-- > 0 ? maxSpaceBetweenItems : 0;
+
+                    // subsequent items are placed one space after the previous item
+                    positions [i] = positions [i - 1] + sizes [i - 1] + spaceBefore;
+                }
+
+                break;
+            case Justification.Right:
+                currentPosition = Math.Max (0, containerSize - totalItemsSize - spaces);
+
+                for (var i = 0; i < sizes.Length; i++)
+                {
+                    if (sizes [i] < 0)
+                    {
+                        throw new ArgumentException ("The size of an item cannot be negative.");
+                    }
+
+                    int spaceBefore = spaces-- > 0 ? maxSpaceBetweenItems : 0;
+
+                    positions [i] = currentPosition;
+                    currentPosition += sizes [i] + spaceBefore;
+                }
+
+                break;
+
+            case Justification.Centered:
+                if (sizes.Length > 1)
+                {
+                    // remaining space to be distributed before first and after the items
+                    int remainingSpace = Math.Max (0, containerSize - totalItemsSize - spaces);
+
+                    for (var i = 0; i < sizes.Length; i++)
+                    {
+                        if (sizes [i] < 0)
+                        {
+                            throw new ArgumentException ("The size of an item cannot be negative.");
+                        }
+
+                        if (i == 0)
+                        {
+                            positions [i] = remainingSpace / 2; // first item position
+
+                            continue;
+                        }
+
+                        int spaceBefore = spaces-- > 0 ? maxSpaceBetweenItems : 0;
+
+                        // subsequent items are placed one space after the previous item
+                        positions [i] = positions [i - 1] + sizes [i - 1] + spaceBefore;
+                    }
+                }
+                else if (sizes.Length == 1)
+                {
+                    if (sizes [0] < 0)
+                    {
+                        throw new ArgumentException ("The size of an item cannot be negative.");
+                    }
+
+                    positions [0] = (containerSize - sizes [0]) / 2; // single item is centered
+                }
+
+                break;
+
+            case Justification.Justified:
+                int spaceBetween = sizes.Length > 1 ? (containerSize - totalItemsSize) / (sizes.Length - 1) : 0;
+                int remainder = sizes.Length > 1 ? (containerSize - totalItemsSize) % (sizes.Length - 1) : 0;
+                currentPosition = 0;
+
+                for (var i = 0; i < sizes.Length; i++)
+                {
+                    if (sizes [i] < 0)
+                    {
+                        throw new ArgumentException ("The size of an item cannot be negative.");
+                    }
+
+                    positions [i] = currentPosition;
+                    int extraSpace = i < remainder ? 1 : 0;
+                    currentPosition += sizes [i] + spaceBetween + extraSpace;
+                }
+
+                break;
+
+            // 111 2222        33333
+            case Justification.LastRightRestLeft:
+                if (sizes.Length > 1)
+                {
+                    currentPosition = 0;
+
+                    for (var i = 0; i < sizes.Length; i++)
+                    {
+                        if (sizes [i] < 0)
+                        {
+                            throw new ArgumentException ("The size of an item cannot be negative.");
+                        }
+
+                        if (i < sizes.Length - 1)
+                        {
+                            int spaceBefore = spaces-- > 0 ? maxSpaceBetweenItems : 0;
+
+                            positions [i] = currentPosition;
+                            currentPosition += sizes [i] + spaceBefore;
+                        }
+                    }
+
+                    positions [sizes.Length - 1] = containerSize - sizes [sizes.Length - 1];
+                }
+                else if (sizes.Length == 1)
+                {
+                    if (sizes [0] < 0)
+                    {
+                        throw new ArgumentException ("The size of an item cannot be negative.");
+                    }
+
+                    positions [0] = containerSize - sizes [0]; // single item is flush right
+                }
+
+                break;
+
+            // 111        2222 33333
+            case Justification.FirstLeftRestRight:
+                if (sizes.Length > 1)
+                {
+                    currentPosition = 0;
+                    positions [0] = currentPosition; // first item is flush left
+
+                    for (int i = sizes.Length - 1; i >= 0; i--)
+                    {
+                        if (sizes [i] < 0)
+                        {
+                            throw new ArgumentException ("The size of an item cannot be negative.");
+                        }
+
+                        if (i == sizes.Length - 1)
+                        {
+                            // start at right
+                            currentPosition = containerSize - sizes [i];
+                            positions [i] = currentPosition;
+                        }
+
+                        if (i < sizes.Length - 1 && i > 0)
+                        {
+                            int spaceBefore = spaces-- > 0 ? maxSpaceBetweenItems : 0;
+
+                            positions [i] = currentPosition - sizes [i] - spaceBefore;
+                            currentPosition = positions [i];
+                        }
+                    }
+                }
+                else if (sizes.Length == 1)
+                {
+                    if (sizes [0] < 0)
+                    {
+                        throw new ArgumentException ("The size of an item cannot be negative.");
+                    }
+
+                    positions [0] = 0; // single item is flush left
+                }
+
+                break;
+
+            default:
+                throw new ArgumentOutOfRangeException (nameof (justification), justification, null);
+        }
+
+        return positions;
+    }
+}

+ 428 - 0
UnitTests/Drawing/JustifierTests.cs

@@ -0,0 +1,428 @@
+using System.Text;
+using Xunit.Abstractions;
+
+namespace Terminal.Gui.DrawingTests;
+
+public class JustifierTests (ITestOutputHelper output)
+{
+    private readonly ITestOutputHelper _output = output;
+
+    public static IEnumerable<object []> JustificationEnumValues ()
+    {
+        foreach (object number in Enum.GetValues (typeof (Justification)))
+        {
+            yield return new [] { number };
+        }
+    }
+
+    [Theory]
+    [MemberData (nameof (JustificationEnumValues))]
+    public void NoItems_Works (Justification justification)
+    {
+        int [] sizes = [];
+        int [] positions = Justifier.Justify (justification, false, 100, sizes);
+        Assert.Equal (new int [] { }, positions);
+    }
+
+    [Theory]
+    [MemberData (nameof (JustificationEnumValues))]
+    public void Negative_Widths_Not_Allowed (Justification justification)
+    {
+        Assert.Throws<ArgumentException> (() => new Justifier ()
+        {
+            Justification = justification,
+            ContainerSize = 100
+        }.Justify (new [] { -10, 20, 30 }));
+        Assert.Throws<ArgumentException> (() => new Justifier ()
+        {
+            Justification = justification,
+            ContainerSize = 100
+        }.Justify (new [] { 10, -20, 30 }));
+        Assert.Throws<ArgumentException> (() => new Justifier ()
+        {
+            Justification = justification,
+            ContainerSize = 100
+        }.Justify (new [] { 10, 20, -30 }));
+    }
+
+    [Theory]
+    [InlineData (Justification.Left, new [] { 0 }, 1, new [] { 0 })]
+    [InlineData (Justification.Left, new [] { 0, 0 }, 1, new [] { 0, 1 })]
+    [InlineData (Justification.Left, new [] { 0, 0, 0 }, 1, new [] { 0, 1, 1 })]
+    [InlineData (Justification.Left, new [] { 1 }, 1, new [] { 0 })]
+    [InlineData (Justification.Left, new [] { 1 }, 2, new [] { 0 })]
+    [InlineData (Justification.Left, new [] { 1 }, 3, new [] { 0 })]
+    [InlineData (Justification.Left, new [] { 1, 1 }, 2, new [] { 0, 1 })]
+    [InlineData (Justification.Left, new [] { 1, 1 }, 3, new [] { 0, 2 })]
+    [InlineData (Justification.Left, new [] { 1, 1 }, 4, new [] { 0, 2 })]
+    [InlineData (Justification.Left, new [] { 1, 1, 1 }, 3, new [] { 0, 1, 2 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3 }, 6, new [] { 0, 1, 3 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3 }, 7, new [] { 0, 2, 4 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3 }, 10, new [] { 0, 2, 5 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3 }, 11, new [] { 0, 2, 5 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3 }, 12, new [] { 0, 2, 5 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3 }, 13, new [] { 0, 2, 5 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3, 4 }, 10, new [] { 0, 1, 3, 6 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3, 4 }, 11, new [] { 0, 2, 4, 7 })]
+    [InlineData (Justification.Left, new [] { 33, 33, 33 }, 100, new [] { 0, 34, 67 })]
+    [InlineData (Justification.Left, new [] { 10 }, 101, new [] { 0 })]
+    [InlineData (Justification.Left, new [] { 10, 20 }, 101, new [] { 0, 11 })]
+    [InlineData (Justification.Left, new [] { 10, 20, 30 }, 100, new [] { 0, 11, 32 })]
+    [InlineData (Justification.Left, new [] { 10, 20, 30 }, 101, new [] { 0, 11, 32 })]
+    [InlineData (Justification.Left, new [] { 10, 20, 30, 40 }, 101, new [] { 0, 11, 31, 61 })]
+    [InlineData (Justification.Left, new [] { 10, 20, 30, 40, 50 }, 151, new [] { 0, 11, 31, 61, 101 })]
+    [InlineData (Justification.Right, new [] { 0 }, 1, new [] { 1 })]
+    [InlineData (Justification.Right, new [] { 0, 0 }, 1, new [] { 0, 1 })]
+    [InlineData (Justification.Right, new [] { 0, 0, 0 }, 1, new [] { 0, 1, 1 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3 }, 6, new [] { 0, 1, 3 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3 }, 7, new [] { 0, 2, 4 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3 }, 10, new [] { 2, 4, 7 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3 }, 11, new [] { 3, 5, 8 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3 }, 12, new [] { 4, 6, 9 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3 }, 13, new [] { 5, 7, 10 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3, 4 }, 10, new [] { 0, 1, 3, 6 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3, 4 }, 11, new [] { 0, 2, 4, 7 })]
+    [InlineData (Justification.Right, new [] { 10, 20, 30 }, 100, new [] { 38, 49, 70 })]
+    [InlineData (Justification.Right, new [] { 33, 33, 33 }, 100, new [] { 0, 34, 67 })]
+    [InlineData (Justification.Right, new [] { 10 }, 101, new [] { 91 })]
+    [InlineData (Justification.Right, new [] { 10, 20 }, 101, new [] { 70, 81 })]
+    [InlineData (Justification.Right, new [] { 10, 20, 30 }, 101, new [] { 39, 50, 71 })]
+    [InlineData (Justification.Right, new [] { 10, 20, 30, 40 }, 101, new [] { 0, 11, 31, 61 })]
+    [InlineData (Justification.Right, new [] { 10, 20, 30, 40, 50 }, 151, new [] { 0, 11, 31, 61, 101 })]
+    [InlineData (Justification.Centered, new [] { 0 }, 1, new [] { 0 })]
+    [InlineData (Justification.Centered, new [] { 0, 0 }, 1, new [] { 0, 1 })]
+    [InlineData (Justification.Centered, new [] { 0, 0, 0 }, 1, new [] { 0, 1, 1 })]
+    [InlineData (Justification.Centered, new [] { 1 }, 1, new [] { 0 })]
+    [InlineData (Justification.Centered, new [] { 1 }, 2, new [] { 0 })]
+    [InlineData (Justification.Centered, new [] { 1 }, 3, new [] { 1 })]
+    [InlineData (Justification.Centered, new [] { 1, 1 }, 2, new [] { 0, 1 })]
+    [InlineData (Justification.Centered, new [] { 1, 1 }, 3, new [] { 0, 2 })]
+    [InlineData (Justification.Centered, new [] { 1, 1 }, 4, new [] { 0, 2 })]
+    [InlineData (Justification.Centered, new [] { 1, 1, 1 }, 3, new [] { 0, 1, 2 })]
+    [InlineData (Justification.Centered, new [] { 1, 2, 3 }, 6, new [] { 0, 1, 3 })]
+    [InlineData (Justification.Centered, new [] { 1, 2, 3 }, 7, new [] { 0, 2, 4 })]
+    [InlineData (Justification.Centered, new [] { 1, 2, 3 }, 10, new [] { 1, 3, 6 })]
+    [InlineData (Justification.Centered, new [] { 1, 2, 3 }, 11, new [] { 1, 3, 6 })]
+    [InlineData (Justification.Centered, new [] { 1, 2, 3, 4 }, 10, new [] { 0, 1, 3, 6 })]
+    [InlineData (Justification.Centered, new [] { 1, 2, 3, 4 }, 11, new [] { 0, 2, 4, 7 })]
+    [InlineData (Justification.Centered, new [] { 3, 3, 3 }, 9, new [] { 0, 3, 6 })]
+    [InlineData (Justification.Centered, new [] { 3, 3, 3 }, 10, new [] { 0, 4, 7 })]
+    [InlineData (Justification.Centered, new [] { 3, 3, 3 }, 11, new [] { 0, 4, 8 })]
+    [InlineData (Justification.Centered, new [] { 3, 3, 3 }, 12, new [] { 0, 4, 8 })]
+    [InlineData (Justification.Centered, new [] { 3, 3, 3 }, 13, new [] { 1, 5, 9 })]
+    [InlineData (Justification.Centered, new [] { 33, 33, 33 }, 100, new [] { 0, 34, 67 })]
+    [InlineData (Justification.Centered, new [] { 33, 33, 33 }, 101, new [] { 0, 34, 68 })]
+    [InlineData (Justification.Centered, new [] { 33, 33, 33 }, 102, new [] { 0, 34, 68 })]
+    [InlineData (Justification.Centered, new [] { 33, 33, 33 }, 103, new [] { 1, 35, 69 })]
+    [InlineData (Justification.Centered, new [] { 33, 33, 33 }, 104, new [] { 1, 35, 69 })]
+    [InlineData (Justification.Centered, new [] { 10 }, 101, new [] { 45 })]
+    [InlineData (Justification.Centered, new [] { 10, 20 }, 101, new [] { 35, 46 })]
+    [InlineData (Justification.Centered, new [] { 10, 20, 30 }, 100, new [] { 19, 30, 51 })]
+    [InlineData (Justification.Centered, new [] { 10, 20, 30 }, 101, new [] { 19, 30, 51 })]
+    [InlineData (Justification.Centered, new [] { 10, 20, 30, 40 }, 100, new [] { 0, 10, 30, 60 })]
+    [InlineData (Justification.Centered, new [] { 10, 20, 30, 40 }, 101, new [] { 0, 11, 31, 61 })]
+    [InlineData (Justification.Centered, new [] { 10, 20, 30, 40, 50 }, 151, new [] { 0, 11, 31, 61, 101 })]
+    [InlineData (Justification.Centered, new [] { 3, 4, 5, 6 }, 25, new [] { 2, 6, 11, 17 })]
+    [InlineData (Justification.Justified, new [] { 10, 20, 30, 40, 50 }, 151, new [] { 0, 11, 31, 61, 101 })]
+    [InlineData (Justification.Justified, new [] { 10, 20, 30, 40 }, 101, new [] { 0, 11, 31, 61 })]
+    [InlineData (Justification.Justified, new [] { 10, 20, 30 }, 100, new [] { 0, 30, 70 })]
+    [InlineData (Justification.Justified, new [] { 10, 20, 30 }, 101, new [] { 0, 31, 71 })]
+    [InlineData (Justification.Justified, new [] { 33, 33, 33 }, 100, new [] { 0, 34, 67 })]
+    [InlineData (Justification.Justified, new [] { 11, 17, 23 }, 100, new [] { 0, 36, 77 })]
+    [InlineData (Justification.Justified, new [] { 1, 2, 3 }, 11, new [] { 0, 4, 8 })]
+    [InlineData (Justification.Justified, new [] { 10, 20 }, 101, new [] { 0, 81 })]
+    [InlineData (Justification.Justified, new [] { 10 }, 101, new [] { 0 })]
+    [InlineData (Justification.Justified, new [] { 3, 3, 3 }, 21, new [] { 0, 9, 18 })]
+    [InlineData (Justification.Justified, new [] { 3, 4, 5 }, 21, new [] { 0, 8, 16 })]
+    [InlineData (Justification.Justified, new [] { 3, 4, 5, 6 }, 18, new [] { 0, 3, 7, 12 })]
+    [InlineData (Justification.Justified, new [] { 3, 4, 5, 6 }, 19, new [] { 0, 4, 8, 13 })]
+    [InlineData (Justification.Justified, new [] { 3, 4, 5, 6 }, 20, new [] { 0, 4, 9, 14 })]
+    [InlineData (Justification.Justified, new [] { 3, 4, 5, 6 }, 21, new [] { 0, 4, 9, 15 })]
+    [InlineData (Justification.Justified, new [] { 6, 5, 4, 3 }, 22, new [] { 0, 8, 14, 19 })]
+    [InlineData (Justification.Justified, new [] { 6, 5, 4, 3 }, 23, new [] { 0, 8, 15, 20 })]
+    [InlineData (Justification.Justified, new [] { 6, 5, 4, 3 }, 24, new [] { 0, 8, 15, 21 })]
+    [InlineData (Justification.Justified, new [] { 6, 5, 4, 3 }, 25, new [] { 0, 9, 16, 22 })]
+    [InlineData (Justification.Justified, new [] { 6, 5, 4, 3 }, 26, new [] { 0, 9, 17, 23 })]
+    [InlineData (Justification.Justified, new [] { 6, 5, 4, 3 }, 31, new [] { 0, 11, 20, 28 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 0 }, 1, new [] { 1 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 0, 0 }, 1, new [] { 0, 1 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 0, 0, 0 }, 1, new [] { 0, 1, 1 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1 }, 1, new [] { 0 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1 }, 2, new [] { 1 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1 }, 3, new [] { 2 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 1 }, 2, new [] { 0, 1 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 1 }, 3, new [] { 0, 2 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 1 }, 4, new [] { 0, 3 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 1, 1 }, 3, new [] { 0, 1, 2 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3 }, 6, new [] { 0, 1, 3 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3 }, 7, new [] { 0, 2, 4 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3 }, 8, new [] { 0, 2, 5 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3 }, 9, new [] { 0, 2, 6 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3 }, 10, new [] { 0, 2, 7 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3 }, 11, new [] { 0, 2, 8 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3, 4 }, 10, new [] { 0, 1, 3, 6 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3, 4 }, 11, new [] { 0, 2, 4, 7 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 3, 3, 3 }, 21, new [] { 0, 4, 18 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 3, 4, 5 }, 21, new [] { 0, 4, 16 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 33, 33, 33 }, 100, new [] { 0, 34, 67 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 10 }, 101, new [] { 91 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 10, 20 }, 101, new [] { 0, 81 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 10, 20, 30 }, 100, new [] { 0, 11, 70 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 10, 20, 30 }, 101, new [] { 0, 11, 71 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 10, 20, 30, 40 }, 101, new [] { 0, 11, 31, 61 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 10, 20, 30, 40, 50 }, 151, new [] { 0, 11, 31, 61, 101 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 0 }, 1, new [] { 0 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 0, 0 }, 1, new [] { 0, 1 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 0, 0, 0 }, 1, new [] { 0, 0, 1 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1 }, 1, new [] { 0 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1 }, 2, new [] { 0 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1 }, 3, new [] { 0 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 1 }, 2, new [] { 0, 1 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 1 }, 3, new [] { 0, 2 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 1 }, 4, new [] { 0, 3 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 1, 1 }, 3, new [] { 0, 1, 2 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3 }, 6, new [] { 0, 1, 3 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3 }, 7, new [] { 0, 1, 4 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3 }, 8, new [] { 0, 2, 5 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3 }, 9, new [] { 0, 3, 6 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3 }, 10, new [] { 0, 4, 7 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3 }, 11, new [] { 0, 5, 8 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3, 4 }, 10, new [] { 0, 1, 3, 6 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3, 4 }, 11, new [] { 0, 1, 3, 7 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3, 4 }, 12, new [] { 0, 1, 4, 8 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 3, 3, 3 }, 21, new [] { 0, 14, 18 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 3, 4, 5 }, 21, new [] { 0, 11, 16 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 33, 33, 33 }, 100, new [] { 0, 33, 67 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 10 }, 101, new [] { 0 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 10, 20 }, 101, new [] { 0, 81 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 10, 20, 30 }, 100, new [] { 0, 49, 70 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 10, 20, 30 }, 101, new [] { 0, 50, 71 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 10, 20, 30, 40 }, 101, new [] { 0, 10, 30, 61 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 10, 20, 30, 40, 50 }, 151, new [] { 0, 10, 30, 60, 101 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 3, 3, 3 }, 21, new [] { 0, 14, 18 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 3, 4, 5 }, 21, new [] { 0, 11, 16 })]
+    public void TestJustifications_PutSpaceBetweenItems (Justification justification, int [] sizes, int containerSize, int [] expected)
+    {
+        int [] positions = new Justifier
+        {
+            PutSpaceBetweenItems = true,
+            Justification = justification,
+            ContainerSize = containerSize
+        }.Justify (sizes);
+        AssertJustification (justification, sizes, containerSize, positions, expected);
+    }
+
+    [Theory]
+    [InlineData (Justification.Left, new [] { 0 }, 1, new [] { 0 })]
+    [InlineData (Justification.Left, new [] { 0, 0 }, 1, new [] { 0, 0 })]
+    [InlineData (Justification.Left, new [] { 0, 0, 0 }, 1, new [] { 0, 0, 0 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3 }, 6, new [] { 0, 1, 3 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3 }, 7, new [] { 0, 1, 3 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3 }, 10, new [] { 0, 1, 3 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3 }, 11, new [] { 0, 1, 3 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3 }, 12, new [] { 0, 1, 3 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3 }, 13, new [] { 0, 1, 3 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3, 4 }, 10, new [] { 0, 1, 3, 6 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3, 4 }, 11, new [] { 0, 1, 3, 6 })]
+    [InlineData (Justification.Left, new [] { 10, 20, 30 }, 100, new [] { 0, 10, 30 })]
+    [InlineData (Justification.Left, new [] { 33, 33, 33 }, 100, new [] { 0, 33, 66 })]
+    [InlineData (Justification.Left, new [] { 10 }, 101, new [] { 0 })]
+    [InlineData (Justification.Left, new [] { 10, 20 }, 101, new [] { 0, 10 })]
+    [InlineData (Justification.Left, new [] { 10, 20, 30 }, 101, new [] { 0, 10, 30 })]
+    [InlineData (Justification.Left, new [] { 10, 20, 30, 40 }, 101, new [] { 0, 10, 30, 60 })]
+    [InlineData (Justification.Left, new [] { 10, 20, 30, 40, 50 }, 151, new [] { 0, 10, 30, 60, 100 })]
+    [InlineData (Justification.Right, new [] { 0 }, 1, new [] { 1 })]
+    [InlineData (Justification.Right, new [] { 0, 0 }, 1, new [] { 1, 1 })]
+    [InlineData (Justification.Right, new [] { 0, 0, 0 }, 1, new [] { 1, 1, 1 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3 }, 6, new [] { 0, 1, 3 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3 }, 7, new [] { 1, 2, 4 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3 }, 10, new [] { 4, 5, 7 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3 }, 11, new [] { 5, 6, 8 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3 }, 12, new [] { 6, 7, 9 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3 }, 13, new [] { 7, 8, 10 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3, 4 }, 10, new [] { 0, 1, 3, 6 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3, 4 }, 11, new [] { 1, 2, 4, 7 })]
+    [InlineData (Justification.Right, new [] { 10, 20, 30 }, 100, new [] { 40, 50, 70 })]
+    [InlineData (Justification.Right, new [] { 33, 33, 33 }, 100, new [] { 1, 34, 67 })]
+    [InlineData (Justification.Right, new [] { 10 }, 101, new [] { 91 })]
+    [InlineData (Justification.Right, new [] { 10, 20 }, 101, new [] { 71, 81 })]
+    [InlineData (Justification.Right, new [] { 10, 20, 30 }, 101, new [] { 41, 51, 71 })]
+    [InlineData (Justification.Right, new [] { 10, 20, 30, 40 }, 101, new [] { 1, 11, 31, 61 })]
+    [InlineData (Justification.Right, new [] { 10, 20, 30, 40, 50 }, 151, new [] { 1, 11, 31, 61, 101 })]
+    [InlineData (Justification.Centered, new [] { 1 }, 1, new [] { 0 })]
+    [InlineData (Justification.Centered, new [] { 1 }, 2, new [] { 0 })]
+    [InlineData (Justification.Centered, new [] { 1 }, 3, new [] { 1 })]
+    [InlineData (Justification.Centered, new [] { 1, 1 }, 2, new [] { 0, 1 })]
+    [InlineData (Justification.Centered, new [] { 1, 1 }, 3, new [] { 0, 1 })]
+    [InlineData (Justification.Centered, new [] { 1, 1 }, 4, new [] { 1, 2 })]
+    [InlineData (Justification.Centered, new [] { 1, 1, 1 }, 3, new [] { 0, 1, 2 })]
+    [InlineData (Justification.Centered, new [] { 1, 2, 3 }, 6, new [] { 0, 1, 3 })]
+    [InlineData (Justification.Centered, new [] { 1, 2, 3 }, 7, new [] { 0, 1, 3 })]
+    [InlineData (Justification.Centered, new [] { 1, 2, 3 }, 10, new [] { 2, 3, 5 })]
+    [InlineData (Justification.Centered, new [] { 1, 2, 3 }, 11, new [] { 2, 3, 5 })]
+    [InlineData (Justification.Centered, new [] { 1, 2, 3, 4 }, 10, new [] { 0, 1, 3, 6 })]
+    [InlineData (Justification.Centered, new [] { 1, 2, 3, 4 }, 11, new [] { 0, 1, 3, 6 })]
+    [InlineData (Justification.Centered, new [] { 3, 3, 3 }, 9, new [] { 0, 3, 6 })]
+    [InlineData (Justification.Centered, new [] { 3, 3, 3 }, 10, new [] { 0, 3, 6 })]
+    [InlineData (Justification.Centered, new [] { 3, 3, 3 }, 11, new [] { 1, 4, 7 })]
+    [InlineData (Justification.Centered, new [] { 3, 3, 3 }, 12, new [] { 1, 4, 7 })]
+    [InlineData (Justification.Centered, new [] { 3, 3, 3 }, 13, new [] { 2, 5, 8 })]
+    [InlineData (Justification.Centered, new [] { 33, 33, 33 }, 100, new [] { 0, 33, 66 })]
+    [InlineData (Justification.Centered, new [] { 33, 33, 33 }, 101, new [] { 1, 34, 67 })]
+    [InlineData (Justification.Centered, new [] { 33, 33, 33 }, 102, new [] { 1, 34, 67 })]
+    [InlineData (Justification.Centered, new [] { 33, 33, 33 }, 103, new [] { 2, 35, 68 })]
+    [InlineData (Justification.Centered, new [] { 33, 33, 33 }, 104, new [] { 2, 35, 68 })]
+    [InlineData (Justification.Centered, new [] { 3, 4, 5, 6 }, 25, new [] { 3, 6, 10, 15 })]
+    [InlineData (Justification.Justified, new [] { 10, 20, 30, 40, 50 }, 151, new [] { 0, 11, 31, 61, 101 })]
+    [InlineData (Justification.Justified, new [] { 10, 20, 30, 40 }, 101, new [] { 0, 11, 31, 61 })]
+    [InlineData (Justification.Justified, new [] { 10, 20, 30 }, 100, new [] { 0, 30, 70 })]
+    [InlineData (Justification.Justified, new [] { 10, 20, 30 }, 101, new [] { 0, 31, 71 })]
+    [InlineData (Justification.Justified, new [] { 33, 33, 33 }, 100, new [] { 0, 34, 67 })]
+    [InlineData (Justification.Justified, new [] { 11, 17, 23 }, 100, new [] { 0, 36, 77 })]
+    [InlineData (Justification.Justified, new [] { 1, 2, 3 }, 11, new [] { 0, 4, 8 })]
+    [InlineData (Justification.Justified, new [] { 10, 20 }, 101, new [] { 0, 81 })]
+    [InlineData (Justification.Justified, new [] { 10 }, 101, new [] { 0 })]
+    [InlineData (Justification.Justified, new [] { 3, 3, 3 }, 21, new [] { 0, 9, 18 })]
+    [InlineData (Justification.Justified, new [] { 3, 4, 5 }, 21, new [] { 0, 8, 16 })]
+    [InlineData (Justification.Justified, new [] { 3, 4, 5, 6 }, 18, new [] { 0, 3, 7, 12 })]
+    [InlineData (Justification.Justified, new [] { 3, 4, 5, 6 }, 19, new [] { 0, 4, 8, 13 })]
+    [InlineData (Justification.Justified, new [] { 3, 4, 5, 6 }, 20, new [] { 0, 4, 9, 14 })]
+    [InlineData (Justification.Justified, new [] { 3, 4, 5, 6 }, 21, new [] { 0, 4, 9, 15 })]
+    [InlineData (Justification.Justified, new [] { 6, 5, 4, 3 }, 22, new [] { 0, 8, 14, 19 })]
+    [InlineData (Justification.Justified, new [] { 6, 5, 4, 3 }, 23, new [] { 0, 8, 15, 20 })]
+    [InlineData (Justification.Justified, new [] { 6, 5, 4, 3 }, 24, new [] { 0, 8, 15, 21 })]
+    [InlineData (Justification.Justified, new [] { 6, 5, 4, 3 }, 25, new [] { 0, 9, 16, 22 })]
+    [InlineData (Justification.Justified, new [] { 6, 5, 4, 3 }, 26, new [] { 0, 9, 17, 23 })]
+    [InlineData (Justification.Justified, new [] { 6, 5, 4, 3 }, 31, new [] { 0, 11, 20, 28 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 0 }, 1, new [] { 1 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 0, 0 }, 1, new [] { 0, 1 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 0, 0, 0 }, 1, new [] { 0, 0, 1 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1 }, 1, new [] { 0 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1 }, 2, new [] { 1 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1 }, 3, new [] { 2 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 1 }, 2, new [] { 0, 1 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 1 }, 3, new [] { 0, 2 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 1 }, 4, new [] { 0, 3 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 1, 1 }, 3, new [] { 0, 1, 2 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3 }, 6, new [] { 0, 1, 3 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3 }, 7, new [] { 0, 1, 4 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3 }, 8, new [] { 0, 1, 5 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3 }, 9, new [] { 0, 1, 6 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3 }, 10, new [] { 0, 1, 7 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3 }, 11, new [] { 0, 1, 8 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3, 4 }, 10, new [] { 0, 1, 3, 6 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3, 4 }, 11, new [] { 0, 1, 3, 7 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3, 4 }, 12, new [] { 0, 1, 3, 8 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 3, 3, 3 }, 21, new [] { 0, 3, 18 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 3, 4, 5 }, 21, new [] { 0, 3, 16 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 33, 33, 33 }, 100, new [] { 0, 33, 67 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 10 }, 101, new [] { 91 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 10, 20 }, 101, new [] { 0, 81 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 10, 20, 30 }, 100, new [] { 0, 10, 70 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 10, 20, 30 }, 101, new [] { 0, 10, 71 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 10, 20, 30, 40 }, 101, new [] { 0, 10, 30, 61 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 10, 20, 30, 40, 50 }, 151, new [] { 0, 10, 30, 60, 101 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 0 }, 1, new [] { 0 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 0, 0 }, 1, new [] { 0, 1 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 0, 0, 0 }, 1, new [] { 0, 1, 1 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1 }, 1, new [] { 0 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1 }, 2, new [] { 0 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1 }, 3, new [] { 0 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 1 }, 2, new [] { 0, 1 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 1 }, 3, new [] { 0, 2 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 1 }, 4, new [] { 0, 3 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 1, 1 }, 3, new [] { 0, 1, 2 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3 }, 6, new [] { 0, 1, 3 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3 }, 7, new [] { 0, 2, 4 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3 }, 8, new [] { 0, 3, 5 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3 }, 9, new [] { 0, 4, 6 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3 }, 10, new [] { 0, 5, 7 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3 }, 11, new [] { 0, 6, 8 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3, 4 }, 10, new [] { 0, 1, 3, 6 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3, 4 }, 11, new [] { 0, 2, 4, 7 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3, 4 }, 12, new [] { 0, 3, 5, 8 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 3, 3, 3 }, 21, new [] { 0, 15, 18 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 3, 4, 5 }, 21, new [] { 0, 12, 16 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 33, 33, 33 }, 100, new [] { 0, 34, 67 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 10 }, 101, new [] { 0 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 10, 20 }, 101, new [] { 0, 81 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 10, 20, 30 }, 100, new [] { 0, 50, 70 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 10, 20, 30 }, 101, new [] { 0, 51, 71 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 10, 20, 30, 40 }, 101, new [] { 0, 11, 31, 61 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 10, 20, 30, 40, 50 }, 151, new [] { 0, 11, 31, 61, 101 })]
+    public void TestJustifications_NoSpaceBetweenItems (Justification justification, int [] sizes, int containerSize, int [] expected)
+    {
+        int [] positions = new Justifier
+        {
+            PutSpaceBetweenItems = false,
+            Justification = justification,
+            ContainerSize = containerSize
+        }.Justify (sizes);
+        AssertJustification (justification, sizes, containerSize, positions, expected);
+    }
+
+    public void AssertJustification (Justification justification, int [] sizes, int totalSize, int [] positions, int [] expected)
+    {
+        try
+        {
+            _output.WriteLine ($"Testing: {RenderJustification (justification, sizes, totalSize, expected)}");
+        }
+        catch (Exception e)
+        {
+            _output.WriteLine ($"Exception rendering expected: {e.Message}");
+            _output.WriteLine ($"Actual: {RenderJustification (justification, sizes, totalSize, positions)}");
+        }
+
+        if (!expected.SequenceEqual (positions))
+        {
+            _output.WriteLine ($"Expected: {RenderJustification (justification, sizes, totalSize, expected)}");
+            _output.WriteLine ($"Actual: {RenderJustification (justification, sizes, totalSize, positions)}");
+            Assert.Fail (" Expected and actual do not match");
+        }
+    }
+
+    public string RenderJustification (Justification justification, int [] sizes, int totalSize, int [] positions)
+    {
+        var output = new StringBuilder ();
+        output.AppendLine ($"Justification: {justification}, Positions: {string.Join (", ", positions)}, TotalSize: {totalSize}");
+
+        for (var i = 0; i <= totalSize / 10; i++)
+        {
+            output.Append (i.ToString ().PadRight (9) + " ");
+        }
+
+        output.AppendLine ();
+
+        for (var i = 0; i < totalSize; i++)
+        {
+            output.Append (i % 10);
+        }
+
+        output.AppendLine ();
+
+        var items = new char [totalSize];
+
+        for (var position = 0; position < positions.Length; position++)
+        {
+            // try
+            {
+                for (var j = 0; j < sizes [position] && positions [position] + j < totalSize; j++)
+                {
+                    items [positions [position] + j] = (position + 1).ToString () [0];
+                }
+            }
+
+            //catch (Exception e)
+            //{
+            //    output.AppendLine ($"{e.Message} - position = {position}, positions[{position}]: {positions [position]}, sizes[{position}]: {sizes [position]}, totalSize: {totalSize}");
+            //    output.Append (new string (items).Replace ('\0', ' '));
+
+            //    Assert.Fail (e.Message + output.ToString ());
+            //}
+        }
+
+        output.Append (new string (items).Replace ('\0', ' '));
+
+        return output.ToString ();
+    }
+}