Browse Source

Implemented the Inlined element

Marcin Ziąbek 4 years ago
parent
commit
479915b68f

+ 49 - 0
QuestPDF.Examples/InlinedExamples.cs

@@ -0,0 +1,49 @@
+using System;
+using System.Linq;
+using NUnit.Framework;
+using QuestPDF.Examples.Engine;
+using QuestPDF.Fluent;
+using QuestPDF.Helpers;
+
+namespace QuestPDF.Examples
+{
+    public class InlinedExamples
+    {
+        [Test]
+        public void Inlined()
+        {
+            RenderingTest
+                .Create()
+                .PageSize(800, 575)
+                .FileName()
+                .ProduceImages()
+                .ShowResults()
+                .Render(container =>
+                {
+                    container
+                        .Padding(25)
+                        .Border(1)
+                        .Background(Colors.Grey.Lighten2)
+                        .Inlined(inlined =>
+                        {
+                            inlined.Spacing(25);
+                            
+                            inlined.AlignCenter();
+                            inlined.BaselineMiddle();
+
+                            var random = new Random();
+                            
+                            foreach (var _ in Enumerable.Range(0, 50))
+                            {
+                                inlined
+                                    .Item()
+                                    .Border(1)
+                                    .Width(random.Next(1, 5) * 25)
+                                    .Height(random.Next(1, 5) * 25)
+                                    .Background(Placeholders.BackgroundColor());
+                            }
+                        });
+                });
+        }
+    }
+}

+ 4 - 5
QuestPDF/Elements/Grid.cs

@@ -2,7 +2,6 @@
 using System.Linq;
 using System.Linq;
 using QuestPDF.Fluent;
 using QuestPDF.Fluent;
 using QuestPDF.Infrastructure;
 using QuestPDF.Infrastructure;
-using static QuestPDF.Infrastructure.HorizontalAlignment;
 
 
 namespace QuestPDF.Elements
 namespace QuestPDF.Elements
 {
 {
@@ -20,7 +19,7 @@ namespace QuestPDF.Elements
         public Queue<GridElement> ChildrenQueue { get; set; } = new Queue<GridElement>();
         public Queue<GridElement> ChildrenQueue { get; set; } = new Queue<GridElement>();
         public int ColumnsCount { get; set; } = DefaultColumnsCount;
         public int ColumnsCount { get; set; } = DefaultColumnsCount;
 
 
-        public HorizontalAlignment Alignment { get; set; } = Left;
+        public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
         public float VerticalSpacing { get; set; } = 0;
         public float VerticalSpacing { get; set; } = 0;
         public float HorizontalSpacing { get; set; } = 0;
         public float HorizontalSpacing { get; set; } = 0;
         
         
@@ -62,15 +61,15 @@ namespace QuestPDF.Elements
             var emptySpace = ColumnsCount - columnsWidth;
             var emptySpace = ColumnsCount - columnsWidth;
             var hasEmptySpace = emptySpace >= Size.Epsilon;
             var hasEmptySpace = emptySpace >= Size.Epsilon;
 
 
-            if (Alignment == Center)
+            if (Alignment == HorizontalAlignment.Center)
                 emptySpace /= 2;
                 emptySpace /= 2;
             
             
-            if (hasEmptySpace && Alignment != Left)
+            if (hasEmptySpace && Alignment != HorizontalAlignment.Left)
                 row.RelativeColumn(emptySpace);
                 row.RelativeColumn(emptySpace);
                 
                 
             elements.ForEach(x => row.RelativeColumn(x.Columns).Element(x.Child));
             elements.ForEach(x => row.RelativeColumn(x.Columns).Element(x.Child));
 
 
-            if (hasEmptySpace && Alignment != Right)
+            if (hasEmptySpace && Alignment != HorizontalAlignment.Right)
                 row.RelativeColumn(emptySpace);
                 row.RelativeColumn(emptySpace);
         }
         }
     }
     }

+ 195 - 0
QuestPDF/Elements/Inlined.cs

@@ -0,0 +1,195 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using QuestPDF.Drawing.SpacePlan;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Elements
+{
+    internal class InlinedElement : Container
+    {
+        public ISpacePlan? Size { get; set; }
+    }
+    
+    internal class Inlined : Element, IStateResettable
+    {
+        public List<InlinedElement> Elements { get; internal set; } = new List<InlinedElement>();
+        private Queue<InlinedElement> ChildrenQueue { get; set; }
+
+        internal HorizontalAlignment HorizontalAlignment { get; set; }
+        internal VerticalAlignment BaselineAlignment { get; set; }
+        
+        public void ResetState()
+        {
+            Elements.ForEach(x => x.Size ??= x.Measure(Size.Max));
+            ChildrenQueue = new Queue<InlinedElement>(Elements);
+        }
+        
+        internal override void HandleVisitor(Action<Element?> visit)
+        {
+            Elements.ForEach(x => x.HandleVisitor(visit));
+            base.HandleVisitor(visit);
+        }
+
+        internal override ISpacePlan Measure(Size availableSpace)
+        {
+            if (!ChildrenQueue.Any())
+                return new FullRender(Size.Zero);
+            
+            var lines = Compose(availableSpace);
+
+            if (!lines.Any())
+                return new Wrap();
+
+            var lineSizes = lines.Select(GetLineSize).ToList();
+            
+            var width = lineSizes.Max(x => x.Width);
+            var height = lineSizes.Sum(x => x.Height);
+            var targetSize = new Size(width, height);
+
+            var isPartiallyRendered = lines.Sum(x => x.Count) != ChildrenQueue.Count;
+
+            if (isPartiallyRendered)
+                return new PartialRender(targetSize);
+            
+            return new FullRender(targetSize);
+        }
+
+        internal override void Draw(Size availableSpace)
+        {
+            var lines = Compose(availableSpace);
+            var topOffset = 0f;
+            
+            foreach (var line in lines)
+            {
+                var height = line.Select(x => x.Size as Size).Where(x => x != null).Max(x => x.Height);
+                DrawLine(line);
+
+                topOffset += height;
+                Canvas.Translate(new Position(0, height));
+            }
+            
+            Canvas.Translate(new Position(0, -topOffset));
+            lines.SelectMany(x => x).ToList().ForEach(x => ChildrenQueue.Dequeue());
+
+            void DrawLine(ICollection<InlinedElement> elements)
+            {
+                var lineSize = GetLineSize(elements);
+                
+                var leftOffset = AlignOffset();
+                Canvas.Translate(new Position(leftOffset, 0));
+                
+                foreach (var element in elements)
+                {
+                    var size = element.Size as Size;
+                    var baselineOffset = BaselineOffset(size, lineSize.Height);
+                    
+                    Canvas.Translate(new Position(0, baselineOffset));
+                    element.Draw(size);
+                    Canvas.Translate(new Position(0, -baselineOffset));
+
+                    leftOffset += size.Width;
+                    Canvas.Translate(new Position(size.Width, 0));
+                }
+                
+                Canvas.Translate(new Position(-leftOffset, 0));
+
+                float AlignOffset()
+                {
+                    if (HorizontalAlignment == HorizontalAlignment.Left)
+                        return 0;
+
+                    var difference = availableSpace.Width - lineSize.Width;
+                    
+                    if (HorizontalAlignment == HorizontalAlignment.Center)
+                        return difference / 2;
+
+                    return difference;
+                }
+                
+                float BaselineOffset(Size elementSize, float lineHeight)
+                {
+                    if (BaselineAlignment == VerticalAlignment.Top)
+                        return 0;
+
+                    var difference = lineHeight - elementSize.Height;
+                    
+                    if (BaselineAlignment == VerticalAlignment.Middle)
+                        return difference / 2;
+
+                    return difference;
+                }
+            }
+        }
+
+        Size GetLineSize(ICollection<InlinedElement> elements)
+        {
+            var sizes = elements
+                .Select(x => x.Size as Size)
+                .Where(x => x != null)
+                .ToList();
+            
+            var width = sizes.Sum(x => x.Width);
+            var height = sizes.Max(x => x.Height);
+
+            return new Size(width, height);
+        }
+        
+        // list of lines, each line is a list of elements
+        private ICollection<ICollection<InlinedElement>> Compose(Size availableSize)
+        {
+            var queue = new Queue<InlinedElement>(ChildrenQueue);
+            var result = new List<ICollection<InlinedElement>>();
+
+            var topOffset = 0f;
+            
+            while (true)
+            {
+                var line = GetNextLine();
+                
+                if (!line.Any())
+                    break;
+
+                var height = line
+                    .Select(x => x.Size as Size)
+                    .Where(x => x != null)
+                    .Max(x => x.Height);
+                
+                if (topOffset + height > availableSize.Height)
+                    break;
+
+                topOffset += height;
+                result.Add(line);
+            }
+
+            return result;
+
+            ICollection<InlinedElement> GetNextLine()
+            {
+                var result = new List<InlinedElement>();
+                var leftOffset = 0f;
+                
+                while (true)
+                {
+                    if (!queue.Any())
+                        break;
+                    
+                    var element = queue.Peek();
+                    var size = element.Size as Size;
+                    
+                    if (size == null)
+                        break;
+                    
+                    if (leftOffset + size.Width > availableSize.Width)
+                        break;
+
+                    queue.Dequeue();
+                    leftOffset += size.Width;
+                    result.Add(element);    
+                }
+
+                return result;
+            }
+        }
+    }
+}

+ 81 - 0
QuestPDF/Fluent/InlinedExtensions.cs

@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using QuestPDF.Elements;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Fluent
+{
+    public class InlinedDescriptor
+    {
+        private ICollection<Element> Children = new List<Element>();
+        private float VerticalSpacingValue { get; set; }
+        private VerticalAlignment BaselineAlignmentValue { get; set; }
+        private float HorizontalSpacingValue { get; set; }
+        private HorizontalAlignment HorizontalAlignmentValue { get; set; }
+        
+        public void Spacing(float value)
+        {
+            VerticalSpacing(value);
+            HorizontalSpacing(value);
+        }
+        
+        public void VerticalSpacing(float value) => VerticalSpacingValue = value;
+        public void HorizontalSpacing(float value) => HorizontalSpacingValue = value;
+
+        public void BaselineTop() => BaselineAlignmentValue = VerticalAlignment.Top;
+        public void BaselineMiddle() => BaselineAlignmentValue = VerticalAlignment.Middle;
+        public void BaselineBottom() => BaselineAlignmentValue = VerticalAlignment.Bottom;
+
+        public void AlignLeft() => HorizontalAlignmentValue = HorizontalAlignment.Left;
+        public void AlignCenter() => HorizontalAlignmentValue = HorizontalAlignment.Center;
+        public void AlignRight() => HorizontalAlignmentValue = HorizontalAlignment.Right;
+        
+        public IContainer Item()
+        {
+            var container = new Container();
+            Children.Add(container);
+            return container;
+        }
+
+        internal Element Compose()
+        {
+            var elements = Children
+                .Select(x => new InlinedElement
+                {
+                    Child = new Padding
+                    {
+                        Left = HorizontalSpacingValue,
+                        Top = VerticalSpacingValue,
+                        Child = x
+                    }
+                })
+                .ToList();
+            
+            return new Padding
+            {
+                Left = -HorizontalSpacingValue,
+                Top = -VerticalSpacingValue,
+                
+                Child = new Inlined
+                {
+                    Elements = elements,
+                    
+                    HorizontalAlignment = HorizontalAlignmentValue,
+                    BaselineAlignment = BaselineAlignmentValue
+                }
+            };
+        }
+    }
+    
+    public static class InlinedExtensions
+    {
+        public static void Inlined(this IContainer element, Action<InlinedDescriptor> handler)
+        {
+            var descriptor = new InlinedDescriptor();
+            handler(descriptor);
+            
+            element.Element(descriptor.Compose());
+        }
+    }
+}