Răsfoiți Sursa

MultiColumn: Added support for spacer decoration

Marcin Ziąbek 1 an în urmă
părinte
comite
4e4e14448a

+ 94 - 40
Source/QuestPDF.Examples/MultiColumnExamples.cs

@@ -23,19 +23,59 @@ public class MultiColumnExamples
                 container
                     .Padding(25)
                     .DefaultTextStyle(x => x.FontSize(8))
-                    .MultiColumn(4, 30)
-                    .Column(column =>
+                    .MultiColumn(multiColumn =>
                     {
-                        column.Spacing(10);
+                        multiColumn.Columns(3);
+                        
+                        multiColumn
+                            .Content()
+                            .Column(column =>
+                            {
+                                column.Spacing(10);
 
-                        foreach (var sectionId in Enumerable.Range(0, 10))
-                        {
-                            foreach (var textId in Enumerable.Range(0, Random.Shared.Next(5, 10)))
-                                column.Item().Text(Placeholders.Paragraph());
+                                foreach (var sectionId in Enumerable.Range(0, 10))
+                                {
+                                    foreach (var textId in Enumerable.Range(0, Random.Shared.Next(5, 10)))
+                                        column.Item().Text(Placeholders.Paragraph());
 
-                            foreach (var blockId in Enumerable.Range(0, Random.Shared.Next(5, 10)))
-                                column.Item().Width(50 + blockId * 5).Height(50).Background(Placeholders.BackgroundColor());
-                        }
+                                    foreach (var blockId in Enumerable.Range(0, Random.Shared.Next(5, 10)))
+                                        column.Item().Width(50 + blockId * 5).Height(50).Background(Placeholders.BackgroundColor());
+                                }
+                            });
+                    });
+            });
+    }
+    
+    [Test]
+    public void Decoration()
+    {
+        RenderingTest
+            .Create()
+            .PageSize(PageSizes.A4)
+            .ProducePdf()
+            .ShowResults()
+            .Render(container =>
+            {
+                container
+                    .Padding(25)
+                    .DefaultTextStyle(x => x.FontSize(8))
+                    .MultiColumn(multiColumn =>
+                    {
+                        multiColumn.Columns(3);
+                        multiColumn.Spacing(25);
+                        multiColumn.BalanceHeight();
+                        
+                        multiColumn.Decoration().AlignCenter().LineVertical(2).LineColor(Colors.Grey.Medium);
+                        
+                        multiColumn
+                            .Content()
+                            .Column(column =>
+                            {
+                                column.Spacing(10);
+
+                                foreach (var blockId in Enumerable.Range(0, 100))
+                                    column.Item().Height(50).Background(Placeholders.BackgroundColor());
+                            });
                     });
             });
     }
@@ -55,34 +95,40 @@ public class MultiColumnExamples
                 container
                     .Padding(25)
                     .DefaultTextStyle(x => x.FontSize(8))
-                    .MultiColumn(2, 25)
-                    .Border(1)
-                    .Table(table =>
+                    .MultiColumn(multiColumn =>
                     {
-                        table.ColumnsDefinition(columns =>
-                        {
-                            columns.RelativeColumn(1);
-                            columns.RelativeColumn(2);
-                            columns.RelativeColumn(3);
-                        });
+                        multiColumn.Spacing(10);
+                        
+                        multiColumn
+                            .Content()
+                            .Border(1)
+                            .Table(table =>
+                            {
+                                table.ColumnsDefinition(columns =>
+                                {
+                                    columns.RelativeColumn(1);
+                                    columns.RelativeColumn(2);
+                                    columns.RelativeColumn(3);
+                                });
 
-                        table.Header(header =>
-                        {
-                            header.Cell().Element(Style).Text("#").Bold();
-                            header.Cell().Element(Style).Text("Label").Bold();
-                            header.Cell().Element(Style).Text("Description").Bold();
+                                table.Header(header =>
+                                {
+                                    header.Cell().Element(Style).Text("#").Bold();
+                                    header.Cell().Element(Style).Text("Label").Bold();
+                                    header.Cell().Element(Style).Text("Description").Bold();
                             
-                            IContainer Style(IContainer container) => container.Border(1).BorderColor(Colors.Grey.Medium).Background(Colors.Grey.Lighten2).Padding(2);
-                        });
+                                    IContainer Style(IContainer container) => container.Border(1).BorderColor(Colors.Grey.Medium).Background(Colors.Grey.Lighten2).Padding(2);
+                                });
                         
-                        foreach (var i in Enumerable.Range(1, 10_000))
-                        {
-                            table.Cell().Element(Style).ShowEntire().Text(i.ToString());
-                            table.Cell().Element(Style).ShowEntire().Text(Placeholders.Label());
-                            table.Cell().Element(Style).ShowEntire().Text(Placeholders.Sentence());
+                                foreach (var i in Enumerable.Range(1, 10_000))
+                                {
+                                    table.Cell().Element(Style).ShowEntire().Text(i.ToString());
+                                    table.Cell().Element(Style).ShowEntire().Text(Placeholders.Label());
+                                    table.Cell().Element(Style).ShowEntire().Text(Placeholders.Sentence());
                             
-                            IContainer Style(IContainer container) => container.Border(1).BorderColor(Colors.Grey.Medium).Background(i % 2 == 0 ? Colors.White : Colors.Grey.Lighten4).Padding(2);
-                        }
+                                    IContainer Style(IContainer container) => container.Border(1).BorderColor(Colors.Grey.Medium).Background(i % 2 == 0 ? Colors.White : Colors.Grey.Lighten4).Padding(2);
+                                }
+                            });
                     });
             });
     }
@@ -101,15 +147,23 @@ public class MultiColumnExamples
                     .Padding(25)
                     .DefaultTextStyle(x => x.FontSize(8))
                     .ShrinkVertical()
-                    .MultiColumn(4, 30)
-                    .Column(column =>
+                    .MultiColumn(multiColumn =>
                     {
-                        column.Spacing(10);
+                        multiColumn.Columns(4);
+                        multiColumn.BalanceHeight(true);
+                        multiColumn.Spacing(10);
+                        
+                        multiColumn
+                            .Content()
+                            .Column(column =>
+                            {
+                                column.Spacing(10);
 
-                        foreach (var sectionId in Enumerable.Range(0, 20))
-                        {
-                            column.Item().EnsureSpace(20).Text(Placeholders.Paragraph());
-                        }
+                                foreach (var sectionId in Enumerable.Range(0, 20))
+                                {
+                                    column.Item().EnsureSpace(20).Text(Placeholders.Paragraph());
+                                }
+                            });
                     });
             });
     }

+ 40 - 14
Source/QuestPDF/Elements/MultiColumn.cs

@@ -1,3 +1,4 @@
+using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Linq;
@@ -31,14 +32,30 @@ internal class MultiColumnChildDrawingObserver : ContainerElement
     }
 }
 
-internal class MultiColumn : ContainerElement
+// TODO: RTL support
+internal class MultiColumn : Element
 {
+    internal Element Content { get; set; } = Empty.Instance;
+    internal Element Decoration { get; set; } = Empty.Instance;
+    
     public int ColumnCount { get; set; } = 2;
-    public bool BalanceHeight { get; set; } = true;
+    public bool BalanceHeight { get; set; } = false;
     public float Spacing { get; set; }
 
     private ProxyCanvas ChildrenCanvas { get; } = new();
     private TreeNode<MultiColumnChildDrawingObserver>[] State { get; set; }
+
+    internal override void CreateProxy(Func<Element?, Element?> create)
+    {
+        Content = create(Content);
+        Decoration = create(Decoration);
+    }
+    
+    internal override IEnumerable<Element?> GetChildren()
+    {
+        yield return Content;
+        yield return Decoration;
+    }
     
     private void BuildState()
     {
@@ -57,8 +74,8 @@ internal class MultiColumn : ContainerElement
     {
         BuildState();
         
-        if (Child.Canvas != ChildrenCanvas)
-            Child.InjectDependencies(PageContext, ChildrenCanvas);
+        if (Content.Canvas != ChildrenCanvas)
+            Content.InjectDependencies(PageContext, ChildrenCanvas);
         
         ChildrenCanvas.Target = new FreeCanvas();
         
@@ -70,8 +87,8 @@ internal class MultiColumn : ContainerElement
             
             foreach (var _ in Enumerable.Range(0, ColumnCount))
             {
-                yield return Child.Measure(columnAvailableSpace);
-                Child.Draw(columnAvailableSpace);
+                yield return Content.Measure(columnAvailableSpace);
+                Content.Draw(columnAvailableSpace);
             }
             
             ResetObserverState(restoreChildState: true);
@@ -93,7 +110,7 @@ internal class MultiColumn : ContainerElement
             var minHeight = 0f;
             var maxHeight = availableSpace.Height;
             
-            foreach (var _ in Enumerable.Range(0, 4))
+            foreach (var _ in Enumerable.Range(0, 8))
             {
                 var middleHeight = (minHeight + maxHeight) / 2;
                 var middleMeasurement = MeasureColumns(new Size(availableSpace.Width, middleHeight));
@@ -117,21 +134,30 @@ internal class MultiColumn : ContainerElement
     
     internal override void Draw(Size availableSpace)
     {
-        var columnAvailableSpace = GetAvailableSpaceForColumn(availableSpace);
+        var contentAvailableSpace = GetAvailableSpaceForColumn(availableSpace);
+        var decorationAvailableSpace = new Size(Spacing, availableSpace.Height);
+        
         ChildrenCanvas.Target = Canvas;
         
         Canvas.Save();
         
-        foreach (var i in Enumerable.Range(0, ColumnCount))
+        foreach (var i in Enumerable.Range(1, ColumnCount))
         {
-            var columnMeasurement = Child.Measure(columnAvailableSpace);
-            var targetColumnSize = new Size(columnAvailableSpace.Width, columnMeasurement.Height);
+            var contentMeasurement = Content.Measure(contentAvailableSpace);
+            var targetColumnSize = new Size(contentAvailableSpace.Width, contentMeasurement.Height);
             
-            Child.Draw(targetColumnSize);
-            Canvas.Translate(new Position(columnAvailableSpace.Width + Spacing, 0));
+            Content.Draw(targetColumnSize);
+            Canvas.Translate(new Position(contentAvailableSpace.Width, 0));
             
-            if (columnMeasurement.Type is SpacePlanType.Empty or SpacePlanType.FullRender)
+            if (contentMeasurement.Type is SpacePlanType.Empty or SpacePlanType.FullRender)
                 break;
+            
+            var decorationMeasurement = Decoration.Measure(decorationAvailableSpace);
+            
+            if (i != ColumnCount && decorationMeasurement.Type is not SpacePlanType.Wrap)
+                Decoration.Draw(decorationAvailableSpace);
+            
+            Canvas.Translate(new Position(Spacing, 0));
         }
         
         Canvas.Restore();

+ 47 - 6
Source/QuestPDF/Fluent/MultiColumnExtensions.cs

@@ -1,19 +1,60 @@
+using System;
+using QuestPDF.Drawing.Exceptions;
 using QuestPDF.Elements;
 using QuestPDF.Infrastructure;
 
 namespace QuestPDF.Fluent;
 
+public class MultiColumnDescriptor
+{
+    internal MultiColumn MultiColumn { get; } = new MultiColumn();
+        
+    public void Spacing(float value, Unit unit = Unit.Point)
+    {
+        MultiColumn.Spacing = value.ToPoints(unit);
+    }
+    
+    public void Columns(int value = 2)
+    {
+        MultiColumn.ColumnCount = value;
+    }
+        
+    public void BalanceHeight(bool enable = true)
+    {
+        MultiColumn.BalanceHeight = enable;
+    }
+
+    public IContainer Content()
+    {
+        if (MultiColumn.Content is not Empty)
+            throw new DocumentComposeException("The 'MultiColumn.Content' layer has already been defined. Please call this method only once.");
+        
+        var container = new Container();
+        MultiColumn.Content = container;
+        return container;
+    }
+    
+    public IContainer Decoration()
+    {
+        if (MultiColumn.Decoration is not Empty)
+            throw new DocumentComposeException("The 'MultiColumn.Decoration' layer has already been defined. Please call this method only once.");
+        
+        var container = new RepeatContent();
+        MultiColumn.Decoration = container;
+        return container;
+    }
+}
+
 public static class MultiColumnExtensions
 {
     /// <summary>
     /// Creates a multi-column layout within the current container element.
     /// </summary>
-    public static IContainer MultiColumn(this IContainer element, int columnCount = 2, float spacing = 0)
+    public static void MultiColumn(this IContainer element, Action<MultiColumnDescriptor> handler)
     {
-        return element.Element(new MultiColumn()
-        {
-            ColumnCount = columnCount,
-            Spacing = spacing
-        });
+        var descriptor = new MultiColumnDescriptor();
+        
+        element.Element(descriptor.MultiColumn);
+        handler(descriptor);
     }
 }