Browse Source

Implemented experimental, tree-based simple stack and row

Marcin Ziąbek 4 years ago
parent
commit
0d86039c0c

+ 12 - 12
QuestPDF.UnitTests/GridTests.cs

@@ -45,20 +45,20 @@ namespace QuestPDF.UnitTests
                 {
                     row.RelativeColumn(6).Element(childA);
                     row.RelativeColumn(4).Element(childB);
-                    row.RelativeColumn(2).Element(new Empty());
+                    row.RelativeColumn(2).Element(Empty.Instance);
                 });
                 
                 stack.Item().Row(row =>
                 {
                     row.RelativeColumn(4).Element(childC);
                     row.RelativeColumn(2).Element(childD);
-                    row.RelativeColumn(6).Element(new Empty());
+                    row.RelativeColumn(6).Element(Empty.Instance);
                 });
                 
                 stack.Item().Row(row =>
                 {
                     row.RelativeColumn(8).Element(childE);
-                    row.RelativeColumn(4).Element(new Empty());
+                    row.RelativeColumn(4).Element(Empty.Instance);
                 });
             });
             
@@ -97,25 +97,25 @@ namespace QuestPDF.UnitTests
             {
                 stack.Item().Row(row =>
                 {
-                    row.RelativeColumn(1).Element(new Empty());
+                    row.RelativeColumn(1).Element(Empty.Instance);
                     row.RelativeColumn(6).Element(childA);
                     row.RelativeColumn(4).Element(childB);
-                    row.RelativeColumn(1).Element(new Empty());
+                    row.RelativeColumn(1).Element(Empty.Instance);
                 });
                 
                 stack.Item().Row(row =>
                 {
-                    row.RelativeColumn(3).Element(new Empty());
+                    row.RelativeColumn(3).Element(Empty.Instance);
                     row.RelativeColumn(4).Element(childC);
                     row.RelativeColumn(2).Element(childD);
-                    row.RelativeColumn(3).Element(new Empty());
+                    row.RelativeColumn(3).Element(Empty.Instance);
                 });
                 
                 stack.Item().Row(row =>
                 {
-                    row.RelativeColumn(2).Element(new Empty());
+                    row.RelativeColumn(2).Element(Empty.Instance);
                     row.RelativeColumn(8).Element(childE);
-                    row.RelativeColumn(2).Element(new Empty());
+                    row.RelativeColumn(2).Element(Empty.Instance);
                 });
             });
 
@@ -154,21 +154,21 @@ namespace QuestPDF.UnitTests
             {
                 stack.Item().Row(row =>
                 {
-                    row.RelativeColumn(2).Element(new Empty());
+                    row.RelativeColumn(2).Element(Empty.Instance);
                     row.RelativeColumn(6).Element(childA);
                     row.RelativeColumn(4).Element(childB);
                 });
                 
                 stack.Item().Row(row =>
                 {
-                    row.RelativeColumn(6).Element(new Empty());
+                    row.RelativeColumn(6).Element(Empty.Instance);
                     row.RelativeColumn(4).Element(childC);
                     row.RelativeColumn(2).Element(childD);
                 });
                 
                 stack.Item().Row(row =>
                 {
-                    row.RelativeColumn(4).Element(new Empty());
+                    row.RelativeColumn(4).Element(Empty.Instance);
                     row.RelativeColumn(8).Element(childE);
                 });
             });

+ 3 - 3
QuestPDF/Elements/Decoration.cs

@@ -5,9 +5,9 @@ namespace QuestPDF.Elements
 {
     internal class Decoration : IComponent
     {
-        public Element Header { get; set; } = new Empty();
-        public Element Content { get; set; } = new Empty();
-        public Element Footer { get; set; } = new Empty();
+        public Element Header { get; set; } = Empty.Instance;
+        public Element Content { get; set; } = Empty.Instance;
+        public Element Footer { get; set; } = Empty.Instance;
 
         public void Compose(IContainer container)
         {

+ 2 - 0
QuestPDF/Elements/Empty.cs

@@ -5,6 +5,8 @@ namespace QuestPDF.Elements
 {
     internal class Empty : Element
     {
+        internal static Empty Instance { get; } = new Empty();
+        
         internal override ISpacePlan Measure(Size availableSpace)
         {
             return new FullRender(Size.Zero);

+ 3 - 3
QuestPDF/Elements/Page.cs

@@ -5,9 +5,9 @@ namespace QuestPDF.Elements
 {
     internal class Page : IComponent
     {
-        public Element Header { get; set; } = new Empty();
-        public Element Content { get; set; } = new Empty();
-        public Element Footer { get; set; } = new Empty();
+        public Element Header { get; set; } = Empty.Instance;
+        public Element Content { get; set; } = Empty.Instance;
+        public Element Footer { get; set; } = Empty.Instance;
 
         public void Compose(IContainer container)
         {

+ 2 - 2
QuestPDF/Elements/SimpleDecoration.cs

@@ -12,8 +12,8 @@ namespace QuestPDF.Elements
     
     internal class SimpleDecoration : Element
     {
-        public Element DecorationElement { get; set; } = new Empty();
-        public Element ContentElement { get; set; } = new Empty();
+        public Element DecorationElement { get; set; } = Empty.Instance;
+        public Element ContentElement { get; set; } = Empty.Instance;
         public DecorationType Type { get; set; } 
 
         internal override ISpacePlan Measure(Size availableSpace)

+ 47 - 0
QuestPDF/Elements/SimpleRow.cs

@@ -0,0 +1,47 @@
+using System;
+using QuestPDF.Drawing.SpacePlan;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Elements
+{
+    internal class SimpleRow : Element
+    {
+        internal Element Left { get; set; }
+        internal Element Right { get; set; }
+
+        internal override ISpacePlan Measure(Size availableSpace)
+        {
+            var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height)) as Size;
+            
+            if (leftMeasurement == null)
+                return new Wrap();
+            
+            var rightMeasurement = Right.Measure(new Size(availableSpace.Width - leftMeasurement.Width, availableSpace.Height)) as Size;
+
+            if (rightMeasurement == null)
+                return new Wrap();
+            
+            var totalWidth = leftMeasurement.Width + rightMeasurement.Width;
+            var totalHeight = Math.Max(leftMeasurement.Height, rightMeasurement.Height);
+
+            var targetSize = new Size(totalWidth, totalHeight);
+
+            if (leftMeasurement is PartialRender || rightMeasurement is PartialRender)
+                return new PartialRender(targetSize);
+            
+            return new FullRender(targetSize);
+        }
+
+        internal override void Draw(ICanvas canvas, Size availableSpace)
+        {
+            var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height));
+            var leftWidth = (leftMeasurement as Size)?.Width ?? 0;
+            
+            Left.Draw(canvas, new Size(leftWidth, availableSpace.Height));
+            
+            canvas.Translate(new Position(leftWidth, 0));
+            Right.Draw(canvas, new Size(availableSpace.Width - leftWidth, availableSpace.Height));
+            canvas.Translate(new Position(-leftWidth, 0));
+        }
+    }
+}

+ 71 - 0
QuestPDF/Elements/SimpleStack.cs

@@ -0,0 +1,71 @@
+using System;
+using QuestPDF.Drawing.SpacePlan;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Elements
+{
+    internal class SimpleStack : Element
+    {
+        internal Element Current { get; set; } = Empty.Instance;
+        internal Element Next { get; set; } = Empty.Instance;
+
+        private bool IsFirstRendered { get; set; } = false;
+        
+        internal override ISpacePlan Measure(Size availableSpace)
+        {
+            var firstElement = IsFirstRendered ? Empty.Instance : Current;
+            var firstSize = firstElement.Measure(availableSpace) as Size;
+
+            if (firstSize == null)
+                return new Wrap();
+            
+            if (firstSize is PartialRender partialRender)
+                return partialRender;
+                
+            var spaceForSecond = new Size(availableSpace.Width, availableSpace.Height - firstSize.Height);
+                
+            var secondSize = Next.Measure(spaceForSecond) as Size;
+
+            if (secondSize == null)
+                return new PartialRender(firstSize);
+
+            var totalWidth = Math.Max(firstSize.Width, secondSize.Width);
+            var totalHeight = firstSize.Height + secondSize.Height;
+
+            var targetSize = new Size(totalWidth, totalHeight);
+
+            if (secondSize is PartialRender)
+                return new PartialRender(targetSize);
+                
+            return new FullRender(targetSize);
+        }
+
+        internal override void Draw(ICanvas canvas, Size availableSpace)
+        {
+            var firstElement = IsFirstRendered ? Empty.Instance : Current;
+
+            var firstMeasurement = firstElement.Measure(availableSpace);
+
+            if (firstMeasurement is FullRender)
+                IsFirstRendered = true;
+
+            var firstSize = firstMeasurement as Size;
+
+            if (firstSize != null)
+                firstElement.Draw(canvas, firstSize);
+
+            var firstHeight = firstSize?.Height ?? 0;
+
+            var spaceForSecond = new Size(availableSpace.Width, availableSpace.Height - firstHeight);
+
+            var secondMeasurement = Next?.Measure(spaceForSecond);
+
+            if (secondMeasurement is Wrap)
+                return;
+
+            canvas.Translate(new Position(0, firstHeight));
+            Next.Draw(canvas, secondMeasurement as Size);
+            canvas.Translate(new Position(0, -firstHeight));
+        }
+    }
+}

+ 29 - 0
QuestPDF/Elements/SimpleStackCache.cs

@@ -0,0 +1,29 @@
+using System;
+using QuestPDF.Drawing.SpacePlan;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Elements
+{
+    internal class SimpleStackWithCache : SimpleStack
+    {
+        private ISpacePlan? Cache { get; set; }
+        private Size? CacheForSize { get; set; }
+        
+        internal override ISpacePlan Measure(Size availableSpace)
+        {
+            if (Cache != null && CacheForSize != null && CacheForSize.Equals(availableSpace))
+                return Cache;
+
+            CacheForSize = availableSpace;
+            Cache = base.Measure(availableSpace);
+            return Cache;
+        }
+        
+        internal override void Draw(ICanvas canvas, Size availableSpace)
+        {
+            base.Draw(canvas, availableSpace);
+            Cache = null;
+            CacheForSize = null;
+        }
+    }
+}

+ 94 - 0
QuestPDF/Elements/TreeRow.cs

@@ -0,0 +1,94 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using QuestPDF.Drawing.SpacePlan;
+using QuestPDF.Fluent;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Elements
+{
+    internal class TreeRow : Element
+    {
+        public ICollection<RowElement> Children { get; internal set; } = new List<RowElement>();
+        public float Spacing { get; set; } = 0;
+        
+        public Element Compose(float availableWidth)
+        {
+            var elements = ReduceRows(AddSpacing(Children));
+            return BuildTree(elements.ToArray());
+
+            ICollection<Element> ReduceRows(ICollection<RowElement> elements)
+            {
+                var constantWidth = elements
+                    .Where(x => x is ConstantRowElement)
+                    .Cast<ConstantRowElement>()
+                    .Sum(x => x.Width);
+            
+                var relativeWidth = elements
+                    .Where(x => x is RelativeRowElement)
+                    .Cast<RelativeRowElement>()
+                    .Sum(x => x.Width);
+
+                var widthPerRelativeUnit = (availableWidth - constantWidth) / relativeWidth;
+                
+                return elements
+                    .Select(x =>
+                    {
+                        if (x is RelativeRowElement r)
+                            return new ConstantRowElement(r.Width * widthPerRelativeUnit)
+                            {
+                                Child = x.Child
+                            };
+                        
+                        return x;
+                    })
+                    .Select(x => new Constrained
+                    {
+                        MinWidth = x.Width,
+                        MaxWidth = x.Width,
+                        Child = x.Child
+                    })
+                    .Cast<Element>()
+                    .ToList();
+            }
+            
+            ICollection<RowElement> AddSpacing(ICollection<RowElement> elements)
+            {
+                if (Spacing < Size.Epsilon)
+                    return elements;
+                
+                return elements
+                    .SelectMany(x => new[] { new ConstantRowElement(Spacing), x })
+                    .Skip(1)
+                    .ToList();
+            }
+
+            Element BuildTree(Span<Element> elements)
+            {
+                if (elements.IsEmpty)
+                    return Empty.Instance;
+
+                if (elements.Length == 1)
+                    return elements[0];
+
+                var half = elements.Length / 2;
+                
+                return new SimpleRow
+                {
+                    Left = BuildTree(elements.Slice(0, half)),
+                    Right = BuildTree(elements.Slice(half))
+                };
+            }
+        }
+
+        internal override ISpacePlan Measure(Size availableSpace)
+        {
+            return Compose(availableSpace.Width).Measure(availableSpace);
+        }
+
+        internal override void Draw(ICanvas canvas, Size availableSpace)
+        {
+            Compose(availableSpace.Width).Draw(canvas, availableSpace);
+        }
+    }
+}

+ 58 - 0
QuestPDF/Elements/TreeStack.cs

@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using QuestPDF.Fluent;
+using QuestPDF.Infrastructure;
+using IComponent = QuestPDF.Infrastructure.IComponent;
+using IContainer = QuestPDF.Infrastructure.IContainer;
+
+namespace QuestPDF.Elements
+{
+    internal class TreeStack : IComponent
+    {
+        public ICollection<Element> Children { get; internal set; } = new List<Element>();
+        public float Spacing { get; set; } = 0;
+        
+        public void Compose(IContainer container)
+        {
+            var elements = AddSpacing(Children);
+
+            container
+                .PaddingBottom(-Spacing)    
+                .Element(BuildTree(elements.ToArray()));
+
+            ICollection<Element> AddSpacing(ICollection<Element> elements)
+            {
+                if (Spacing < Size.Epsilon)
+                    return elements;
+                
+                return elements
+                    .Select(x => new Padding
+                    {
+                        Bottom = Spacing,
+                        Child = x
+                    })
+                    .Cast<Element>()
+                    .ToList();
+            }
+
+            Element BuildTree(Span<Element> elements)
+            {
+                if (elements.IsEmpty)
+                    return Empty.Instance;
+
+                if (elements.Length == 1)
+                    return elements[0];
+
+                var half = elements.Length / 2;
+                
+                return new SimpleStack
+                {
+                    Current = BuildTree(elements.Slice(0, half)),
+                    Next = BuildTree(elements.Slice(half))
+                };
+            }
+        }
+    }
+}

+ 11 - 1
QuestPDF/Fluent/RowExtensions.cs

@@ -33,9 +33,18 @@ namespace QuestPDF.Fluent
         }
         
         internal Element CreateRow()
+        {
+            return new TreeRow
+            {
+                Children = Elements,
+                Spacing = RowSpacing
+            };
+        }
+        
+        internal Element CreateRow2()
         {
             if (Elements.Count == 0)
-                return new Empty();
+                return Empty.Instance;
             
             if (RowSpacing <= Size.Epsilon)
                 return new Row
@@ -67,6 +76,7 @@ namespace QuestPDF.Fluent
             var descriptor = new RowDescriptor();
             handler(descriptor);
             element.Element(descriptor.CreateRow());
+            //element.Element(descriptor.CreateRow2());
         }
     }
 }

+ 12 - 3
QuestPDF/Fluent/StackExtensions.cs

@@ -28,10 +28,19 @@ namespace QuestPDF.Fluent
             handler?.Invoke(Item());
         }
         
-        internal Element CreateStack()
+        internal IComponent CreateStack()
+        {
+            return new TreeStack
+            {
+                Children = Items,
+                Spacing = StackSpacing
+            };
+        }
+        
+        internal Element CreateStack2()
         {
             if (Items.Count == 0)
-                return new Empty();
+                return Empty.Instance;
             
             if (StackSpacing <= Size.Epsilon)
                 return new Stack
@@ -67,7 +76,7 @@ namespace QuestPDF.Fluent
         {
             var descriptor = new StackDescriptor();
             handler(descriptor);
-            element.Element(descriptor.CreateStack());
+            element.Component(descriptor.CreateStack());
         }
     }
 }

+ 1 - 1
QuestPDF/Infrastructure/ContainerElement.cs

@@ -5,7 +5,7 @@ namespace QuestPDF.Infrastructure
 {
     internal abstract class ContainerElement : Element, IContainer
     {
-        internal Element? Child { get; set; } = new Empty();
+        internal Element? Child { get; set; } = Empty.Instance;
 
         IElement? IContainer.Child
         {