Browse Source

Merge branch 'main' into epic-custom-skia

# Conflicts:
#	Source/QuestPDF.Examples/QuestPDF.Examples.csproj
#	Source/QuestPDF.Previewer/QuestPDF.Previewer.csproj
#	Source/QuestPDF/Elements/Canvas.cs
#	Source/QuestPDF/Elements/Text/Items/TextBlockSectionLink.cs
#	Source/QuestPDF/Elements/Text/TextBlock.cs
#	Source/QuestPDF/QuestPDF.csproj
#	Source/QuestPDF/Resources/ReleaseNotes.txt
Marcin Ziąbek 1 year ago
parent
commit
e17b342f77

+ 1 - 1
README.md

@@ -56,7 +56,7 @@ Choosing a project dependency could be difficult. We need to ensure stability an
 
 ⭐ Please give this repository a star. It takes seconds and help thousands of developers! ⭐
 
-<img src="https://github.com/QuestPDF/QuestPDF/assets/9263853/0217a87a-1fb5-4096-a172-c627f240e4c0" width="700" />
+<img src="https://github.com/QuestPDF/QuestPDF/assets/9263853/0e0ec850-9a29-4976-85d7-1b2553a1d5d4" width="700" />
 
 
 ## Please share with the community

+ 33 - 1
Source/QuestPDF.Examples/ChartExamples.cs

@@ -1,11 +1,14 @@
+using System;
 using System.Linq;
 using NUnit.Framework;
 using QuestPDF.Examples.Engine;
 using QuestPDF.Fluent;
-using QuestPDF.Helpers;
 using QuestPDF.Infrastructure;
 using Microcharts;
+using ScottPlot;
 using SkiaSharp;
+using Colors = QuestPDF.Helpers.Colors;
+using Orientation = Microcharts.Orientation;
 
 namespace QuestPDF.Examples
 {    
@@ -84,5 +87,34 @@ namespace QuestPDF.Examples
                         });
                 });
         }
+        
+        [Test]
+        public void ScottPlotChart()
+        {
+            RenderingTest
+                .Create()
+                .PageSize(400, 300)
+                .ProduceImages()
+                .ShowResults()
+                .Render(container =>
+                {
+                    container
+                        .Background(Colors.White)
+                        .Padding(25)
+                        .Canvas((canvas, availableSpace) =>
+                        {
+                            var points = Enumerable
+                                .Range(0, 100)
+                                .Select(x => new Coordinates(x, Math.Sin(x / 10f)))
+                                .ToArray();
+                            
+                            using var plot = new Plot();
+                            plot.Add.Scatter(points, Color.FromHex(Colors.Teal.Medium));
+                            
+                            canvas.ClipRect(new SKRect(0, 0, availableSpace.Width, availableSpace.Height));
+                            plot.Render(canvas, (int)availableSpace.Width, (int)availableSpace.Height);
+                        });
+                });
+        }
     }
 }

+ 52 - 0
Source/QuestPDF.Examples/ColumnExamples.cs

@@ -37,6 +37,58 @@ namespace QuestPDF.Examples
                 });
         }
         
+        [Test]
+        public void ColumnDoesNotPutDoubleSpacingWhenChildIsEmpty()
+        {
+            RenderingTest
+                .Create()
+                .PageSize(PageSizes.A4)
+                .ShowResults()
+                .ProducePdf()
+                .Render(container =>
+                {
+                    container.Padding(50).Shrink().Border(1).Background(Colors.Grey.Lighten3).Column(column =>
+                    {
+                        column.Spacing(30);
+                        
+                        column.Item();
+                        column.Item();
+                        column.Item();
+                        
+                        foreach (var i in Enumerable.Range(0, 5))
+                        {
+                            column.Item().Element(Block);
+                            column.Item().Element(Block);
+                            column.Item().Element(Block);
+                        
+                            column.Item();
+                        
+                            column.Item().Element(Block);
+                            column.Item().Element(Block);
+                            column.Item().Element(Block);
+                            column.Item().Element(Block);
+                        
+                            column.Item();
+                            column.Item();
+                        
+                            column.Item().Element(Block);
+                            column.Item().Element(Block);
+                        
+                            column.Item();
+                            column.Item();
+                        }
+                        
+                        static void Block(IContainer container)
+                        {
+                            container
+                                .Width(100)
+                                .Height(30)
+                                .Background(Placeholders.Color());
+                        }
+                    });
+                });
+        }
+        
         [Test]
         public void Stability_NoItems()
         {

+ 3 - 2
Source/QuestPDF.Examples/DynamicOptimizedExample.cs

@@ -136,8 +136,9 @@ namespace QuestPDF.Examples
                 });
 
                 var elementHeight = element.Size.Height;
-                    
-                if (totalHeight + elementHeight > context.AvailableSize.Height)
+
+                // it is important to use the Size.Epsilon constant to avoid floating point comparison issues
+                if (totalHeight + elementHeight > context.AvailableSize.Height + Size.Epsilon)
                     break;
                     
                 totalHeight += elementHeight;

+ 65 - 32
Source/QuestPDF/Drawing/Proxy/Helpers.cs

@@ -8,31 +8,32 @@ namespace QuestPDF.Drawing.Proxy;
 
 internal static class Helpers
 {
-    internal static Size? TryMeasureWithOverflow(this Element element, Size availableSpace)
+    internal static SpacePlan TryMeasureWithOverflow(this Element element, Size availableSpace)
     {
         return TryVerticalOverflow()
                ?? TryHorizontalOverflow() 
-               ?? TryUnconstrainedOverflow();
+               ?? TryUnconstrainedOverflow()
+               ?? SpacePlan.Wrap();
 
-        Size? TryOverflow(Size targetSpace)
+        SpacePlan? TryOverflow(Size targetSpace)
         {
             var contentSize = element.Measure(targetSpace);
             return contentSize.Type == SpacePlanType.Wrap ? null : contentSize;
         }
     
-        Size? TryVerticalOverflow()
+        SpacePlan? TryVerticalOverflow()
         {
             var overflowSpace = new Size(availableSpace.Width, Size.Infinity);
             return TryOverflow(overflowSpace);
         }
     
-        Size? TryHorizontalOverflow()
+        SpacePlan? TryHorizontalOverflow()
         {
             var overflowSpace = new Size(Size.Infinity, availableSpace.Height);
             return TryOverflow(overflowSpace);
         }
     
-        Size? TryUnconstrainedOverflow()
+        SpacePlan? TryUnconstrainedOverflow()
         {
             var overflowSpace = new Size(Size.Infinity, Size.Infinity);
             return TryOverflow(overflowSpace);
@@ -53,9 +54,23 @@ internal static class Helpers
         
         void Traverse(TreeNode<OverflowDebuggingProxy> element)
         {
-            if (element.Value.SpacePlanType is null or SpacePlanType.FullRender)
+            // before assessing the element,
+            // reset layout state by measuring the element with original space
+            // in case when parent has altered the layout state with different overflow test
+            element.Value.Measure(element.Value.MeasurementSize);
+            
+            // element was not part of the current layout measurement,
+            // it could not impact the process
+            if (element.Value.SpacePlanType is null)
+                return;
+            
+            // element renders fully,
+            // it could not impact the process
+            if (element.Value.SpacePlanType is SpacePlanType.FullRender)
                 return;
 
+            // when element is partially rendering, it likely has no issues,
+            // however, in certain cases, it may contain a child that is a root cause
             if (element.Value.SpacePlanType is SpacePlanType.PartialRender)
             {
                 foreach (var child in element.Children)
@@ -64,36 +79,54 @@ internal static class Helpers
                 return;
             }
             
-            // strategy: element wrap can be caused by any child that returned wrap
-            if (element.Children.Any(x => x.Value.SpacePlanType == SpacePlanType.Wrap))
-            {
-                if (TryFixChildrenOfType(SpacePlanType.Wrap))
-                    return;
-            }
- 
-            // strategy: there could be more complex inner/hidden layout constraint issue,
-            // if element cannot be successfully drawn on infinite canvas
-            if (element.Value.TryMeasureWithOverflow(element.Value.MeasurementSize) == null)
+            // all of the code below relates to element that is wrapping,
+            // it could be root cause, or contain a child (even deeply nested) that is the root cause
+            
+            // strategy
+            // element does not contain any wrapping elements, no obvious root causes,
+            // if it renders fully with extended space, it is a layout root cause
+            if (element.Children.All(x => x.Value.SpacePlanType is not SpacePlanType.Wrap) && MeasureElementWithExtendedSpace() is SpacePlanType.FullRender)
             {
-                if (TryFixChildrenOfType(SpacePlanType.PartialRender))
-                    return;
+                // so apply the layout overflow proxy
+                element.Value.CreateProxy(x => new LayoutOverflowVisualization { Child = x });
+                return;
             }
-  
-            // fixing children does not help, fix the element itself
-            element.Value.RemoveExistingProxies();
-            element.Value.CreateProxy(x => new LayoutOverflowVisualization { Child = x });
 
-            bool TryFixChildrenOfType(SpacePlanType spacePlanType)
-            {
-                var suspectedChildren = element.Children.Where(x => x.Value.SpacePlanType == spacePlanType);
-
-                if (!suspectedChildren.Any())
-                    return false;
+            // every time a measurement is made, the layout state is mutated
+            // the previous strategy could modify the layout state
+            // reset layout state by measuring the element with original space
+            element.Value.Measure(element.Value.MeasurementSize); 
+            
+            // strategy:
+            // element contains wrapping children, they are likely the root cause,
+            // traverse them and attempt to fix them
+            foreach (var child in element.Children.Where(x => x.Value.SpacePlanType is SpacePlanType.Wrap))
+                Traverse(child);
+                
+            // check if fixing wrapping children helped
+            if (MeasureElementWithExtendedSpace() is not SpacePlanType.Wrap)
+                return;
 
-                foreach (var child in suspectedChildren)
-                    Traverse(child);
+            // reset layout state by measuring the element with original space
+            element.Value.Measure(element.Value.MeasurementSize); // reset state
+            
+            // strategy:
+            // element has layout issues but no obvious/trivial root causes
+            // possibly the problem is in nested children of partial rendering children
+            foreach (var child in element.Children.Where(x => x.Value.SpacePlanType is SpacePlanType.PartialRender))
+                Traverse(child);
+                
+            // check if fixing partial children helped
+            if (MeasureElementWithExtendedSpace() is not SpacePlanType.Wrap)
+                return;
+            
+            // none of the attempts above have fixed the layout issue
+            // the element itself is the root cause
+            element.Value.CreateProxy(x => new LayoutOverflowVisualization { Child = x });
 
-                return element.Value.TryMeasureWithOverflow(element.Value.MeasurementSize).HasValue;
+            SpacePlanType MeasureElementWithExtendedSpace()
+            {
+                return element.Value.TryMeasureWithOverflow(element.Value.MeasurementSize).Type;
             }
         }
     }

+ 4 - 0
Source/QuestPDF/Elements/Column.cs

@@ -106,6 +106,10 @@ namespace QuestPDF.Elements
                 if (measurement.Type == SpacePlanType.Wrap)
                     break;
 
+                // when the item does not take any space, do not add spacing
+                if (measurement.Width < Size.Epsilon && measurement.Height < Size.Epsilon)
+                    topOffset -= Spacing;
+                
                 commands.Add(new ColumnItemRenderingCommand
                 {
                     ColumnItem = item,

+ 5 - 1
Source/QuestPDF/Elements/LayoutOverflowVisualization.cs

@@ -41,7 +41,11 @@ internal class LayoutOverflowVisualization : ContainerElement, IContentDirection
             skiaCanvasBase.MarkCurrentPageAsHavingLayoutIssues();
         
         // check overflow area
-        var contentSize = Child.TryMeasureWithOverflow(availableSpace) ?? Size.Max;
+        var contentArea = Child.TryMeasureWithOverflow(availableSpace);
+
+        var contentSize = contentArea.Type is SpacePlanType.Wrap
+            ? Size.Max
+            : contentArea;
         
         // draw content
         var translate = ContentDirection == ContentDirection.RightToLeft

+ 1 - 1
Source/QuestPDF/Elements/Padding.cs

@@ -18,7 +18,7 @@ namespace QuestPDF.Elements
             
             var internalSpace = InternalSpace(availableSpace);
 
-            if (internalSpace.Width < 0 || internalSpace.Height < 0)
+            if (internalSpace.Width < -Size.Epsilon || internalSpace.Height < -Size.Epsilon)
                 return SpacePlan.Wrap();
             
             var measure = base.Measure(internalSpace);

+ 1 - 1
Source/QuestPDF/Helpers/Helpers.cs

@@ -63,7 +63,7 @@ namespace QuestPDF.Helpers
 
         internal static bool IsNegative(this Size size)
         {
-            return size.Width < 0f || size.Height < 0f;
+            return size.Width < -Size.Epsilon || size.Height < -Size.Epsilon;
         }
         
         internal static int ToQualityValue(this ImageCompressionQuality quality)