Browse Source

Improved infinite layout debugging experience

Marcin Ziąbek 4 years ago
parent
commit
6a3bfd855e

+ 2 - 2
QuestPDF.Examples/DebuggingTesting.cs

@@ -23,8 +23,8 @@ namespace QuestPDF.Examples
                         {
                         {
                             grid.Spacing(15);
                             grid.Spacing(15);
                     
                     
-                            grid.Item().Background(Colors.Grey.Medium).Height(50);
-                            grid.Item().Background(Colors.Grey.Lighten1).Height(1000); // <-- problem
+                            grid.Item().Background(Colors.Grey.Medium).Text(Placeholders.LoremIpsum());
+                            grid.Item().DebugPointer("Test").Background(Colors.Grey.Lighten1).Height(1000); // <-- problem
                             grid.Item().Background(Colors.Grey.Lighten2).Height(150);
                             grid.Item().Background(Colors.Grey.Lighten2).Height(150);
                         });
                         });
                 });
                 });

+ 1 - 1
QuestPDF.Examples/ElementExamples.cs

@@ -155,7 +155,7 @@ namespace QuestPDF.Examples
                 {
                 {
                     container
                     container
                         .Padding(25)
                         .Padding(25)
-                        .Debug("Grid example", Colors.Blue.Medium)
+                        .DebugArea("Grid example", Colors.Blue.Medium)
                         .Grid(grid =>
                         .Grid(grid =>
                         {
                         {
                             grid.Columns(3);
                             grid.Columns(3);

+ 2 - 2
QuestPDF.ReportSample/Layouts/SectionTemplate.cs

@@ -66,13 +66,13 @@ namespace QuestPDF.ReportSample.Layouts
         
         
         void PhotosElement(IContainer container, ReportSectionPhotos model)
         void PhotosElement(IContainer container, ReportSectionPhotos model)
         {
         {
-            if (model.Photos.Count == 0)
+            if (model.Photos.Count == 0) 
             {
             {
                 container.Text("No photos", Typography.Normal);
                 container.Text("No photos", Typography.Normal);
                 return;
                 return;
             }
             }
 
 
-            container.Debug("Photos").Grid(grid =>
+            container.DebugArea("Photos").Grid(grid =>
             {
             {
                 grid.Spacing(5);
                 grid.Spacing(5);
                 grid.Columns(3);
                 grid.Columns(3);

+ 7 - 7
QuestPDF.UnitTests/DecorationTests.cs

@@ -15,7 +15,7 @@ namespace QuestPDF.UnitTests
         public void Measure_ReturnsWrap_WhenDecorationReturnsWrap()
         public void Measure_ReturnsWrap_WhenDecorationReturnsWrap()
         {
         {
             TestPlan
             TestPlan
-                .For(x => new SimpleDecoration
+                .For(x => new BinaryDecoration
                 {
                 {
                     Type = DecorationType.Append,
                     Type = DecorationType.Append,
                     DecorationElement = x.CreateChild("decoration"),
                     DecorationElement = x.CreateChild("decoration"),
@@ -30,7 +30,7 @@ namespace QuestPDF.UnitTests
         public void Measure_ReturnsWrap_WhenDecorationReturnsPartialRender()
         public void Measure_ReturnsWrap_WhenDecorationReturnsPartialRender()
         {
         {
             TestPlan
             TestPlan
-                .For(x => new SimpleDecoration
+                .For(x => new BinaryDecoration
                 {
                 {
                     Type = DecorationType.Append,
                     Type = DecorationType.Append,
                     DecorationElement = x.CreateChild("decoration"),
                     DecorationElement = x.CreateChild("decoration"),
@@ -45,7 +45,7 @@ namespace QuestPDF.UnitTests
         public void Measure_ReturnsWrap_WhenContentReturnsWrap()
         public void Measure_ReturnsWrap_WhenContentReturnsWrap()
         {
         {
             TestPlan
             TestPlan
-                .For(x => new SimpleDecoration
+                .For(x => new BinaryDecoration
                 {
                 {
                     Type = DecorationType.Append,
                     Type = DecorationType.Append,
                     DecorationElement = x.CreateChild("decoration"),
                     DecorationElement = x.CreateChild("decoration"),
@@ -61,7 +61,7 @@ namespace QuestPDF.UnitTests
         public void Measure_ReturnsPartialRender_WhenContentReturnsPartialRender()
         public void Measure_ReturnsPartialRender_WhenContentReturnsPartialRender()
         {
         {
             TestPlan
             TestPlan
-                .For(x => new SimpleDecoration
+                .For(x => new BinaryDecoration
                 {
                 {
                     Type = DecorationType.Append,
                     Type = DecorationType.Append,
                     DecorationElement = x.CreateChild("decoration"),
                     DecorationElement = x.CreateChild("decoration"),
@@ -77,7 +77,7 @@ namespace QuestPDF.UnitTests
         public void Measure_ReturnsFullRender_WhenContentReturnsFullRender()
         public void Measure_ReturnsFullRender_WhenContentReturnsFullRender()
         {
         {
             TestPlan
             TestPlan
-                .For(x => new SimpleDecoration
+                .For(x => new BinaryDecoration
                 {
                 {
                     Type = DecorationType.Append,
                     Type = DecorationType.Append,
                     DecorationElement = x.CreateChild("decoration"),
                     DecorationElement = x.CreateChild("decoration"),
@@ -97,7 +97,7 @@ namespace QuestPDF.UnitTests
         public void Draw_Prepend()
         public void Draw_Prepend()
         {
         {
             TestPlan
             TestPlan
-                .For(x => new SimpleDecoration
+                .For(x => new BinaryDecoration
                 {
                 {
                     Type = DecorationType.Prepend,
                     Type = DecorationType.Prepend,
                     DecorationElement = x.CreateChild("decoration"),
                     DecorationElement = x.CreateChild("decoration"),
@@ -116,7 +116,7 @@ namespace QuestPDF.UnitTests
         public void Draw_Append()
         public void Draw_Append()
         {
         {
             TestPlan
             TestPlan
-                .For(x => new SimpleDecoration
+                .For(x => new BinaryDecoration
                 {
                 {
                     Type = DecorationType.Append,
                     Type = DecorationType.Append,
                     DecorationElement = x.CreateChild("decoration"),
                     DecorationElement = x.CreateChild("decoration"),

+ 6 - 6
QuestPDF.UnitTests/RowTests.cs

@@ -15,7 +15,7 @@ namespace QuestPDF.UnitTests
         public void Measure_ReturnsWrap_WhenLeftChildReturnsWrap()
         public void Measure_ReturnsWrap_WhenLeftChildReturnsWrap()
         {
         {
             TestPlan
             TestPlan
-                .For(x => new SimpleRow
+                .For(x => new BinaryRow
                 {
                 {
                     Left = x.CreateChild("left"),
                     Left = x.CreateChild("left"),
                     Right = x.CreateChild("right")
                     Right = x.CreateChild("right")
@@ -29,7 +29,7 @@ namespace QuestPDF.UnitTests
         public void Measure_ReturnsWrap_WhenRightChildReturnsWrap()
         public void Measure_ReturnsWrap_WhenRightChildReturnsWrap()
         {
         {
             TestPlan
             TestPlan
-                .For(x => new SimpleRow
+                .For(x => new BinaryRow
                 {
                 {
                     Left = x.CreateChild("left"),
                     Left = x.CreateChild("left"),
                     Right = x.CreateChild("right")
                     Right = x.CreateChild("right")
@@ -44,7 +44,7 @@ namespace QuestPDF.UnitTests
         public void Measure_ReturnsPartialRender_WhenLeftChildReturnsPartialRender()
         public void Measure_ReturnsPartialRender_WhenLeftChildReturnsPartialRender()
         {
         {
             TestPlan
             TestPlan
-                .For(x => new SimpleRow
+                .For(x => new BinaryRow
                 {
                 {
                     Left = x.CreateChild("left"),
                     Left = x.CreateChild("left"),
                     Right = x.CreateChild("right")
                     Right = x.CreateChild("right")
@@ -59,7 +59,7 @@ namespace QuestPDF.UnitTests
         public void Measure_ReturnsPartialRender_WhenRightChildReturnsPartialRender()
         public void Measure_ReturnsPartialRender_WhenRightChildReturnsPartialRender()
         {
         {
             TestPlan
             TestPlan
-                .For(x => new SimpleRow
+                .For(x => new BinaryRow
                 {
                 {
                     Left = x.CreateChild("left"),
                     Left = x.CreateChild("left"),
                     Right = x.CreateChild("right")
                     Right = x.CreateChild("right")
@@ -74,7 +74,7 @@ namespace QuestPDF.UnitTests
         public void Measure_ReturnsFullRender_WhenBothChildrenReturnFullRender()
         public void Measure_ReturnsFullRender_WhenBothChildrenReturnFullRender()
         {
         {
             TestPlan
             TestPlan
-                .For(x => new SimpleRow
+                .For(x => new BinaryRow
                 {
                 {
                     Left = x.CreateChild("left"),
                     Left = x.CreateChild("left"),
                     Right = x.CreateChild("right")
                     Right = x.CreateChild("right")
@@ -93,7 +93,7 @@ namespace QuestPDF.UnitTests
         public void Draw()
         public void Draw()
         {
         {
             TestPlan
             TestPlan
-                .For(x => new SimpleRow
+                .For(x => new BinaryRow
                 {
                 {
                     Left = x.CreateChild("left"),
                     Left = x.CreateChild("left"),
                     Right = x.CreateChild("right")
                     Right = x.CreateChild("right")

+ 15 - 15
QuestPDF.UnitTests/StackTests.cs

@@ -15,7 +15,7 @@ namespace QuestPDF.UnitTests
         public void Measure_ReturnsWrap_WhenFirstChildWraps()
         public void Measure_ReturnsWrap_WhenFirstChildWraps()
         {
         {
             TestPlan
             TestPlan
-                .For(x => new SimpleStack
+                .For(x => new BinaryStack
                 {
                 {
                     First = x.CreateChild("first"),
                     First = x.CreateChild("first"),
                     Second = x.CreateChild("second")
                     Second = x.CreateChild("second")
@@ -29,7 +29,7 @@ namespace QuestPDF.UnitTests
         public void Measure_ReturnsPartialRender_WhenFirstChildReturnsPartialRender()
         public void Measure_ReturnsPartialRender_WhenFirstChildReturnsPartialRender()
         {
         {
             TestPlan
             TestPlan
-                .For(x => new SimpleStack
+                .For(x => new BinaryStack
                 {
                 {
                     First = x.CreateChild("first"),
                     First = x.CreateChild("first"),
                     Second = x.CreateChild("second")
                     Second = x.CreateChild("second")
@@ -43,7 +43,7 @@ namespace QuestPDF.UnitTests
         public void Measure_ReturnsPartialRender_WhenSecondChildWraps()
         public void Measure_ReturnsPartialRender_WhenSecondChildWraps()
         {
         {
             TestPlan
             TestPlan
-                .For(x => new SimpleStack
+                .For(x => new BinaryStack
                 {
                 {
                     First = x.CreateChild("first"),
                     First = x.CreateChild("first"),
                     Second = x.CreateChild("second")
                     Second = x.CreateChild("second")
@@ -58,7 +58,7 @@ namespace QuestPDF.UnitTests
         public void Measure_ReturnsPartialRender_WhenSecondChildReturnsPartialRender()
         public void Measure_ReturnsPartialRender_WhenSecondChildReturnsPartialRender()
         {
         {
             TestPlan
             TestPlan
-                .For(x => new SimpleStack
+                .For(x => new BinaryStack
                 {
                 {
                     First = x.CreateChild("first"),
                     First = x.CreateChild("first"),
                     Second = x.CreateChild("second")
                     Second = x.CreateChild("second")
@@ -73,7 +73,7 @@ namespace QuestPDF.UnitTests
         public void Measure_ReturnsFullRender_WhenSecondChildReturnsFullRender()
         public void Measure_ReturnsFullRender_WhenSecondChildReturnsFullRender()
         {
         {
             TestPlan
             TestPlan
-                .For(x => new SimpleStack
+                .For(x => new BinaryStack
                 {
                 {
                     First = x.CreateChild("first"),
                     First = x.CreateChild("first"),
                     Second = x.CreateChild("second")
                     Second = x.CreateChild("second")
@@ -88,7 +88,7 @@ namespace QuestPDF.UnitTests
         public void Measure_UsesEmpty_WhenFirstChildIsRendered()
         public void Measure_UsesEmpty_WhenFirstChildIsRendered()
         {
         {
             TestPlan
             TestPlan
-                .For(x => new SimpleStack
+                .For(x => new BinaryStack
                 {
                 {
                     First = x.CreateChild("first"),
                     First = x.CreateChild("first"),
                     Second = x.CreateChild("second"),
                     Second = x.CreateChild("second"),
@@ -108,7 +108,7 @@ namespace QuestPDF.UnitTests
         public void Draw_WhenFirstChildWraps()
         public void Draw_WhenFirstChildWraps()
         {
         {
             TestPlan
             TestPlan
-                .For(x => new SimpleStack
+                .For(x => new BinaryStack
                 {
                 {
                     First = x.CreateChild("first"),
                     First = x.CreateChild("first"),
                     Second = x.CreateChild("second")
                     Second = x.CreateChild("second")
@@ -122,7 +122,7 @@ namespace QuestPDF.UnitTests
         public void Draw_WhenFirstChildPartiallyRenders()
         public void Draw_WhenFirstChildPartiallyRenders()
         {
         {
             TestPlan
             TestPlan
-                .For(x => new SimpleStack
+                .For(x => new BinaryStack
                 {
                 {
                     First = x.CreateChild("first"),
                     First = x.CreateChild("first"),
                     Second = x.CreateChild("second")
                     Second = x.CreateChild("second")
@@ -137,7 +137,7 @@ namespace QuestPDF.UnitTests
         public void Draw_WhenFirstChildFullyRenders_AndSecondChildWraps()
         public void Draw_WhenFirstChildFullyRenders_AndSecondChildWraps()
         {
         {
             TestPlan
             TestPlan
-                .For(x => new SimpleStack
+                .For(x => new BinaryStack
                 {
                 {
                     First = x.CreateChild("first"),
                     First = x.CreateChild("first"),
                     Second = x.CreateChild("second")
                     Second = x.CreateChild("second")
@@ -153,7 +153,7 @@ namespace QuestPDF.UnitTests
         public void Draw_WhenFirstChildFullyRenders_AndSecondChildPartiallyRenders()
         public void Draw_WhenFirstChildFullyRenders_AndSecondChildPartiallyRenders()
         {
         {
             TestPlan
             TestPlan
-                .For(x => new SimpleStack
+                .For(x => new BinaryStack
                 {
                 {
                     First = x.CreateChild("first"),
                     First = x.CreateChild("first"),
                     Second = x.CreateChild("second")
                     Second = x.CreateChild("second")
@@ -172,7 +172,7 @@ namespace QuestPDF.UnitTests
         public void Draw_WhenFirstChildFullyRenders_AndSecondChildFullyRenders()
         public void Draw_WhenFirstChildFullyRenders_AndSecondChildFullyRenders()
         {
         {
             TestPlan
             TestPlan
-                .For(x => new SimpleStack
+                .For(x => new BinaryStack
                 {
                 {
                     First = x.CreateChild("first"),
                     First = x.CreateChild("first"),
                     Second = x.CreateChild("second")
                     Second = x.CreateChild("second")
@@ -191,7 +191,7 @@ namespace QuestPDF.UnitTests
         public void Draw_UsesEmpty_WhenFirstChildIsRendered()
         public void Draw_UsesEmpty_WhenFirstChildIsRendered()
         {
         {
             TestPlan
             TestPlan
-                .For(x => new SimpleStack
+                .For(x => new BinaryStack
                 {
                 {
                     First = x.CreateChild("first"),
                     First = x.CreateChild("first"),
                     Second = x.CreateChild("second"),
                     Second = x.CreateChild("second"),
@@ -203,7 +203,7 @@ namespace QuestPDF.UnitTests
                 .ExpectCanvasTranslate(0, 0)
                 .ExpectCanvasTranslate(0, 0)
                 .ExpectChildDraw("second", new Size(400, 300))
                 .ExpectChildDraw("second", new Size(400, 300))
                 .ExpectCanvasTranslate(0, 0)
                 .ExpectCanvasTranslate(0, 0)
-                .CheckState<SimpleStack>(x => x.IsFirstRendered)
+                .CheckState<BinaryStack>(x => x.IsFirstRendered)
                 .CheckDrawResult();
                 .CheckDrawResult();
         }
         }
         
         
@@ -211,7 +211,7 @@ namespace QuestPDF.UnitTests
         public void Draw_TogglesFirstRenderedFlag_WhenSecondFullyRenders()
         public void Draw_TogglesFirstRenderedFlag_WhenSecondFullyRenders()
         {
         {
             TestPlan
             TestPlan
-                .For(x => new SimpleStack
+                .For(x => new BinaryStack
                 {
                 {
                     First = x.CreateChild("first"),
                     First = x.CreateChild("first"),
                     Second = x.CreateChild("second"),
                     Second = x.CreateChild("second"),
@@ -224,7 +224,7 @@ namespace QuestPDF.UnitTests
                 .ExpectChildDraw("second", new Size(400, 300))
                 .ExpectChildDraw("second", new Size(400, 300))
                 .ExpectCanvasTranslate(0, 0)
                 .ExpectCanvasTranslate(0, 0)
                 .CheckDrawResult()
                 .CheckDrawResult()
-                .CheckState<SimpleStack>(x => !x.IsFirstRendered);
+                .CheckState<BinaryStack>(x => !x.IsFirstRendered);
         }
         }
         
         
         #endregion
         #endregion

+ 0 - 151
QuestPDF/Drawing/CacheProxy.cs

@@ -1,151 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using QuestPDF.Elements;
-using QuestPDF.Infrastructure;
-
-namespace QuestPDF.Drawing
-{
-    internal class ElementProxy : ContainerElement
-    {
-        
-    }
-    
-    internal class CacheProxy : ElementProxy
-    {
-        public Size? AvailableSpace { get; set; }
-        public SpacePlan? MeasurementResult { get; set; }
-        
-        internal override SpacePlan Measure(Size availableSpace)
-        {
-            if (MeasurementResult != null &&
-                AvailableSpace != null &&
-                IsClose(AvailableSpace.Value.Width, availableSpace.Width) &&
-                IsClose(AvailableSpace.Value.Height, availableSpace.Height))
-            {
-                return MeasurementResult.Value;
-            }
-
-            AvailableSpace = availableSpace;
-            MeasurementResult = base.Measure(availableSpace);
-
-            return MeasurementResult.Value;
-        }
-
-        internal override void Draw(Size availableSpace)
-        { 
-            AvailableSpace = null;
-            MeasurementResult = null;
-            
-            base.Draw(availableSpace);
-        }
-
-        private bool IsClose(float x, float y)
-        {
-            return Math.Abs(x - y) < Size.Epsilon;
-        }
-    }
-
-    internal class DebuggingState
-    {
-        private DebugStackItem Root { get; set; }
-        private Stack<DebugStackItem> Stack { get; set; }
-
-        public DebuggingState()
-        {
-            Reset();
-        }
-        
-        public void Reset()
-        {
-            Root = null;
-            Stack = new Stack<DebugStackItem>();
-        }
-        
-        public void RegisterMeasure(IElement element, Size availableSpace)
-        {
-            if (element.GetType() == typeof(Container))
-                return;
-            
-            var item = new DebugStackItem
-            {
-                Element = element,
-                AvailableSpace = availableSpace
-            };
-
-            Root ??= item;
-            
-            if (Stack.Any())
-                Stack.Peek().Stack.Add(item);
-
-            Stack.Push(item);
-        }
-
-        public void RegisterMeasureResult(IElement element, SpacePlan spacePlan)
-        {
-            if (element.GetType() == typeof(Container))
-                return;
-            
-            var item = Stack.Pop();
-
-            if (item.Element != element)
-                throw new Exception();
-            
-            item.SpacePlan = spacePlan;
-        }
-        
-        public string BuildTrace()
-        {
-            var builder = new StringBuilder();
-            var nestingLevel = 0;
-
-            Traverse(Root);
-            return builder.ToString();
-
-            void Traverse(DebugStackItem item)
-            {
-                var spaceIndent = new string(' ', nestingLevel * 4);
-                var wrapIndent = new string('>', nestingLevel * 4);
-
-                var firstIndent = item.SpacePlan.Type == SpacePlanType.Wrap ? wrapIndent : spaceIndent;
-                
-                builder.AppendLine(firstIndent + item.Element);
-                builder.AppendLine(spaceIndent + "Available space: " + item.AvailableSpace);
-                builder.AppendLine(spaceIndent + "Take space: " + item.SpacePlan);
-                
-                nestingLevel++;
-                item.Stack.ToList().ForEach(Traverse);
-                nestingLevel--;
-            }
-        }
-    }
-    
-    public class DebugStackItem
-    {
-        public IElement Element { get; internal set; }
-        public Size AvailableSpace { get; internal set; }
-        public SpacePlan SpacePlan { get; internal set; }
-
-        public ICollection<DebugStackItem> Stack { get; internal set; } = new List<DebugStackItem>();
-    }
-
-    internal class DebuggingProxy : ElementProxy
-    {
-        private DebuggingState DebuggingState { get; }
-
-        public DebuggingProxy(DebuggingState debuggingState)
-        {
-            DebuggingState = debuggingState;
-        }
-        
-        internal override SpacePlan Measure(Size availableSpace)
-        {
-            DebuggingState.RegisterMeasure(Child, availableSpace);
-            var spacePlan = base.Measure(availableSpace);
-            DebuggingState.RegisterMeasureResult(Child, spacePlan);
-
-            return spacePlan;
-        }
-    }
-}

+ 21 - 4
QuestPDF/Drawing/DocumentGenerator.cs

@@ -2,6 +2,7 @@ using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using QuestPDF.Drawing.Exceptions;
 using QuestPDF.Drawing.Exceptions;
+using QuestPDF.Drawing.Proxy;
 using QuestPDF.Elements;
 using QuestPDF.Elements;
 using QuestPDF.Infrastructure;
 using QuestPDF.Infrastructure;
 
 
@@ -35,8 +36,16 @@ namespace QuestPDF.Drawing
             var metadata = document.GetMetadata();
             var metadata = document.GetMetadata();
             var pageContext = new PageContext();
             var pageContext = new PageContext();
 
 
-            //ApplyCaching(content);
-            var debuggingState = ApplyDebugging(content);
+            DebuggingState debuggingState = null;
+
+            if (System.Diagnostics.Debugger.IsAttached)
+            {
+                debuggingState = ApplyDebugging(content);
+            }
+            else
+            {
+                ApplyCaching(content);
+            }
 
 
             RenderPass(pageContext, new FreeCanvas(), content, metadata, debuggingState);
             RenderPass(pageContext, new FreeCanvas(), content, metadata, debuggingState);
             RenderPass(pageContext, canvas, content, metadata, debuggingState);
             RenderPass(pageContext, canvas, content, metadata, debuggingState);
@@ -62,7 +71,7 @@ namespace QuestPDF.Drawing
                 if (spacePlan.Type == SpacePlanType.Wrap)
                 if (spacePlan.Type == SpacePlanType.Wrap)
                 {
                 {
                     canvas.EndDocument();
                     canvas.EndDocument();
-                    throw new DocumentDrawingException("An exception occured during document drawing.");
+                    ThrowLayoutException();
                 }
                 }
 
 
                 try
                 try
@@ -81,7 +90,7 @@ namespace QuestPDF.Drawing
                 if (currentPage >= documentMetadata.DocumentLayoutExceptionThreshold)
                 if (currentPage >= documentMetadata.DocumentLayoutExceptionThreshold)
                 {
                 {
                     canvas.EndDocument();
                     canvas.EndDocument();
-                    throw new DocumentLayoutException("Composed layout generates infinite document.");
+                    ThrowLayoutException();
                 }
                 }
                 
                 
                 if (spacePlan.Type == SpacePlanType.FullRender)
                 if (spacePlan.Type == SpacePlanType.FullRender)
@@ -91,6 +100,14 @@ namespace QuestPDF.Drawing
             }
             }
             
             
             canvas.EndDocument();
             canvas.EndDocument();
+
+            void ThrowLayoutException()
+            {
+                throw new DocumentLayoutException("Composed layout generates infinite document.")
+                {
+                    ElementTrace = debuggingState?.BuildTrace() ?? "Debug trace is available only in the DEBUG mode."
+                };
+            }
         }
         }
 
 
         private static void ApplyCaching(Container content)
         private static void ApplyCaching(Container content)

+ 2 - 0
QuestPDF/Drawing/Exceptions/DocumentLayoutException.cs

@@ -4,6 +4,8 @@ namespace QuestPDF.Drawing.Exceptions
 {
 {
     public class DocumentLayoutException : Exception
     public class DocumentLayoutException : Exception
     {
     {
+        public string ElementTrace { get; set; }
+        
         public DocumentLayoutException()
         public DocumentLayoutException()
         {
         {
             
             

+ 40 - 0
QuestPDF/Drawing/Proxy/CacheProxy.cs

@@ -0,0 +1,40 @@
+using System;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Drawing.Proxy
+{
+    internal class CacheProxy : ElementProxy
+    {
+        public Size? AvailableSpace { get; set; }
+        public SpacePlan? MeasurementResult { get; set; }
+        
+        internal override SpacePlan Measure(Size availableSpace)
+        {
+            if (MeasurementResult != null &&
+                AvailableSpace != null &&
+                IsClose(AvailableSpace.Value.Width, availableSpace.Width) &&
+                IsClose(AvailableSpace.Value.Height, availableSpace.Height))
+            {
+                return MeasurementResult.Value;
+            }
+
+            AvailableSpace = availableSpace;
+            MeasurementResult = base.Measure(availableSpace);
+
+            return MeasurementResult.Value;
+        }
+
+        internal override void Draw(Size availableSpace)
+        { 
+            AvailableSpace = null;
+            MeasurementResult = null;
+            
+            base.Draw(availableSpace);
+        }
+
+        private bool IsClose(float x, float y)
+        {
+            return Math.Abs(x - y) < Size.Epsilon;
+        }
+    }
+}

+ 14 - 0
QuestPDF/Drawing/Proxy/DebugStackItem.cs

@@ -0,0 +1,14 @@
+using System.Collections.Generic;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Drawing.Proxy
+{
+    public class DebugStackItem
+    {
+        public IElement Element { get; internal set; }
+        public Size AvailableSpace { get; internal set; }
+        public SpacePlan SpacePlan { get; internal set; }
+
+        public ICollection<DebugStackItem> Stack { get; internal set; } = new List<DebugStackItem>();
+    }
+}

+ 23 - 0
QuestPDF/Drawing/Proxy/DebuggingProxy.cs

@@ -0,0 +1,23 @@
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Drawing.Proxy
+{
+    internal class DebuggingProxy : ElementProxy
+    {
+        private DebuggingState DebuggingState { get; }
+
+        public DebuggingProxy(DebuggingState debuggingState)
+        {
+            DebuggingState = debuggingState;
+        }
+        
+        internal override SpacePlan Measure(Size availableSpace)
+        {
+            DebuggingState.RegisterMeasure(Child, availableSpace);
+            var spacePlan = base.Measure(availableSpace);
+            DebuggingState.RegisterMeasureResult(Child, spacePlan);
+
+            return spacePlan;
+        }
+    }
+}

+ 130 - 0
QuestPDF/Drawing/Proxy/DebuggingState.cs

@@ -0,0 +1,130 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using QuestPDF.Elements;
+using QuestPDF.Helpers;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Drawing.Proxy
+{
+    internal class DebuggingState
+    {
+        private DebugStackItem Root { get; set; }
+        private Stack<DebugStackItem> Stack { get; set; }
+
+        public DebuggingState()
+        {
+            Reset();
+        }
+        
+        public void Reset()
+        {
+            Root = null;
+            Stack = new Stack<DebugStackItem>();
+        }
+        
+        public void RegisterMeasure(IElement element, Size availableSpace)
+        {
+            if (element.GetType() == typeof(Container))
+                return;
+            
+            var item = new DebugStackItem
+            {
+                Element = element,
+                AvailableSpace = availableSpace
+            };
+
+            Root ??= item;
+            
+            if (Stack.Any())
+                Stack.Peek().Stack.Add(item);
+
+            Stack.Push(item);
+        }
+
+        public void RegisterMeasureResult(IElement element, SpacePlan spacePlan)
+        {
+            if (element.GetType() == typeof(Container))
+                return;
+            
+            var item = Stack.Pop();
+
+            if (item.Element != element)
+                throw new Exception();
+            
+            item.SpacePlan = spacePlan;
+        }
+        
+        public string BuildTrace()
+        {
+            var builder = new StringBuilder();
+            var nestingLevel = 0;
+
+            Traverse(Root);
+            return builder.ToString();
+
+            void Traverse(DebugStackItem item)
+            {
+                var indent = new string(' ', nestingLevel * 4);
+                var title = item.Element.GetType().Name;
+
+                if (item.Element is DebugPointer debugPointer)
+                {
+                    title = debugPointer.Target;
+                    title += debugPointer.Highlight ? " 🌟" : string.Empty;
+                }
+                
+                if (item.SpacePlan.Type == SpacePlanType.Wrap)
+                    title = "🔥 " + title;
+
+                builder.AppendLine(indent + title);
+                builder.AppendLine(indent + new string('-', title.Length));
+                
+                builder.AppendLine(indent + "Available space: " + item.AvailableSpace);
+                builder.AppendLine(indent + "Requested space: " + item.SpacePlan);
+                
+                foreach (var configuration in GetElementConfiguration(item.Element))
+                    builder.AppendLine(indent + configuration);
+
+                builder.AppendLine();
+                
+                nestingLevel++;
+                item.Stack.ToList().ForEach(Traverse);
+                nestingLevel--;
+            }
+
+            static IEnumerable<string> GetElementConfiguration(IElement element)
+            {
+                if (element is DebugPointer)
+                    return Enumerable.Empty<string>();
+                
+                return element
+                    .GetType()
+                    .GetProperties()
+                    .Select(x => new
+                    {
+                        Property = x.Name.PrettifyName(),
+                        Value = x.GetValue(element)
+                    })
+                    .Where(x => !(x.Value is IElement))
+                    .Where(x => !(x.Value is IEnumerable<IElement>))
+                    .Where(x => !(x.Value is TextStyle))
+                    .Select(x => $"{x.Property}: {FormatValue(x.Value)}");
+
+                string FormatValue(object value)
+                {
+                    const int maxLength = 100;
+                    
+                    var text = value?.ToString() ?? "-";
+
+                    if (text.Length < maxLength)
+                        return text;
+
+                    return text.AsSpan(0, maxLength).ToString() + "...";
+                }
+            }
+        }
+    }
+}

+ 9 - 0
QuestPDF/Drawing/Proxy/ElementProxy.cs

@@ -0,0 +1,9 @@
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Drawing.Proxy
+{
+    internal class ElementProxy : ContainerElement
+    {
+        
+    }
+}

+ 4 - 8
QuestPDF/Drawing/SpacePlan.cs

@@ -3,13 +3,6 @@ using QuestPDF.Infrastructure;
 
 
 namespace QuestPDF.Drawing
 namespace QuestPDF.Drawing
 {
 {
-    public enum SpacePlanType
-    {
-        Wrap,
-        PartialRender,
-        FullRender
-    }
-    
     public readonly struct SpacePlan
     public readonly struct SpacePlan
     {
     {
         public readonly SpacePlanType Type;
         public readonly SpacePlanType Type;
@@ -35,7 +28,10 @@ namespace QuestPDF.Drawing
 
 
         public override string ToString()
         public override string ToString()
         {
         {
-            return $"{Type}({Width:N2};{Height:N2})";
+            if (Type == SpacePlanType.Wrap)
+                return Type.ToString();
+            
+            return $"{Type} | width {Width:N2} | height {Height:N2}";
         }
         }
 
 
         public static implicit operator Size(SpacePlan spacePlan)
         public static implicit operator Size(SpacePlan spacePlan)

+ 9 - 0
QuestPDF/Drawing/SpacePlanType.cs

@@ -0,0 +1,9 @@
+namespace QuestPDF.Drawing
+{
+    public enum SpacePlanType
+    {
+        Wrap,
+        PartialRender,
+        FullRender
+    }
+}

+ 2 - 2
QuestPDF/Elements/Debug.cs → QuestPDF/Elements/DebugArea.cs

@@ -5,7 +5,7 @@ using SkiaSharp;
 
 
 namespace QuestPDF.Elements
 namespace QuestPDF.Elements
 {
 {
-    internal class Debug : IComponent
+    internal class DebugArea : IComponent
     {
     {
         public IElement? Child { get; set; }
         public IElement? Child { get; set; }
         
         
@@ -30,7 +30,7 @@ namespace QuestPDF.Elements
                         .Box()
                         .Box()
                         .Background(Colors.White)
                         .Background(Colors.White)
                         .Padding(2)
                         .Padding(2)
-                        .Text(Text, TextStyle.Default.Color(Color).FontType("Consolas").Size(8));
+                        .Text(Text, TextStyle.Default.Color(Color).FontType(Fonts.Consolas).Size(8));
                 });
                 });
         }
         }
     }
     }

+ 10 - 0
QuestPDF/Elements/DebugPointer.cs

@@ -0,0 +1,10 @@
+using System.ComponentModel;
+
+namespace QuestPDF.Elements
+{
+    internal class DebugPointer : Container
+    {
+        public string Target { get; set; }
+        public bool Highlight { get; set; }
+    }
+}

+ 3 - 3
QuestPDF/Elements/Decoration.cs

@@ -11,7 +11,7 @@ namespace QuestPDF.Elements
         Append
         Append
     }
     }
     
     
-    internal class SimpleDecoration : Element
+    internal class BinaryDecoration : Element
     {
     {
         public Element DecorationElement { get; set; } = Empty.Instance;
         public Element DecorationElement { get; set; } = Empty.Instance;
         public Element ContentElement { get; set; } = Empty.Instance;
         public Element ContentElement { get; set; } = Empty.Instance;
@@ -83,11 +83,11 @@ namespace QuestPDF.Elements
 
 
         public void Compose(IContainer container)
         public void Compose(IContainer container)
         {
         {
-            container.Element(new SimpleDecoration
+            container.Element(new BinaryDecoration
             {
             {
                 Type = DecorationType.Prepend,
                 Type = DecorationType.Prepend,
                 DecorationElement = Header,
                 DecorationElement = Header,
-                ContentElement = new SimpleDecoration
+                ContentElement = new BinaryDecoration
                 {
                 {
                     Type = DecorationType.Append,
                     Type = DecorationType.Append,
                     ContentElement = Content,
                     ContentElement = Content,

+ 2 - 12
QuestPDF/Elements/Row.cs

@@ -24,11 +24,6 @@ namespace QuestPDF.Elements
             Size = size;
             Size = size;
             SetWidth(size);
             SetWidth(size);
         }
         }
-        
-        public override string ToString()
-        {
-            return $"RowColumn: Constant({Size})";
-        }
     }
     }
     
     
     internal class RelativeRowElement : RowElement
     internal class RelativeRowElement : RowElement
@@ -37,14 +32,9 @@ namespace QuestPDF.Elements
         {
         {
             Size = size;
             Size = size;
         }
         }
-        
-        public override string ToString()
-        {
-            return $"RowColumn: Relative({Size})";
-        }
     }
     }
     
     
-    internal class SimpleRow : Element
+    internal class BinaryRow : Element
     {
     {
         internal Element Left { get; set; }
         internal Element Left { get; set; }
         internal Element Right { get; set; }
         internal Element Right { get; set; }
@@ -179,7 +169,7 @@ namespace QuestPDF.Elements
 
 
             var half = elements.Length / 2;
             var half = elements.Length / 2;
             
             
-            return new SimpleRow
+            return new BinaryRow
             {
             {
                 Left = BuildTree(elements.Slice(0, half)),
                 Left = BuildTree(elements.Slice(0, half)),
                 Right = BuildTree(elements.Slice(half))
                 Right = BuildTree(elements.Slice(half))

+ 2 - 2
QuestPDF/Elements/Stack.cs

@@ -9,7 +9,7 @@ using IContainer = QuestPDF.Infrastructure.IContainer;
 
 
 namespace QuestPDF.Elements
 namespace QuestPDF.Elements
 {
 {
-    internal class SimpleStack : Element, IStateResettable
+    internal class BinaryStack : Element, IStateResettable
     {
     {
         internal Element First { get; set; } = Empty.Instance;
         internal Element First { get; set; } = Empty.Instance;
         internal Element Second { get; set; } = Empty.Instance;
         internal Element Second { get; set; } = Empty.Instance;
@@ -135,7 +135,7 @@ namespace QuestPDF.Elements
 
 
             var half = elements.Length / 2;
             var half = elements.Length / 2;
                 
                 
-            return new SimpleStack
+            return new BinaryStack
             {
             {
                 First = BuildTree(elements.Slice(0, half)),
                 First = BuildTree(elements.Slice(0, half)),
                 Second = BuildTree(elements.Slice(half))
                 Second = BuildTree(elements.Slice(half))

+ 5 - 2
QuestPDF/Fluent/ComponentExtentions.cs

@@ -22,7 +22,7 @@ namespace QuestPDF.Fluent
                 var existingValue = Component.GetPropertyValue(selector);
                 var existingValue = Component.GetPropertyValue(selector);
 
 
                 if (existingValue != null)
                 if (existingValue != null)
-                    throw new DocumentComposeException($"The slot {selector.GetPropertyName()} of the component {(typeof( T).Name)} was already used.");
+                    throw new DocumentComposeException($"The slot {selector.GetPropertyName()} of the component {(typeof(T).Name)} was already used.");
 
 
                 var slot = new Slot();
                 var slot = new Slot();
                 Component.SetPropertyValue(selector, slot);
                 Component.SetPropertyValue(selector, slot);
@@ -55,7 +55,10 @@ namespace QuestPDF.Fluent
         {
         {
             var descriptor = new ComponentDescriptor<T>(component);
             var descriptor = new ComponentDescriptor<T>(component);
             handler?.Invoke(descriptor);
             handler?.Invoke(descriptor);
-            
+
+            if (System.Diagnostics.Debugger.IsAttached)
+                element = element.DebugPointer(component.GetType().Name, highlight: false);
+
             component.Compose(element.Container());
             component.Compose(element.Container());
         }
         }
         
         

+ 24 - 6
QuestPDF/Fluent/DebugExtensions.cs

@@ -6,11 +6,11 @@ namespace QuestPDF.Fluent
 {
 {
     public static class DebugExtensions
     public static class DebugExtensions
     {
     {
-        public static IContainer Debug(this IContainer parent, string text, string color)
+        public static IContainer DebugArea(this IContainer parent, string text, string color)
         {
         {
             var container = new Container();
             var container = new Container();
 
 
-            parent.Component(new Debug
+            parent.Component(new DebugArea
             {
             {
                 Child = container,
                 Child = container,
                 Text = text,
                 Text = text,
@@ -20,14 +20,32 @@ namespace QuestPDF.Fluent
             return container;
             return container;
         }
         }
         
         
-        public static IContainer Debug(this IContainer parent, string text)
+        public static IContainer DebugArea(this IContainer parent, string text)
         {
         {
-            return parent.Debug(text, Colors.Red.Medium);
+            return parent.DebugArea(text, Colors.Red.Medium);
         }
         }
 
 
-        public static IContainer Debug(this IContainer parent)
+        public static IContainer DebugArea(this IContainer parent)
         {
         {
-            return parent.Debug(string.Empty, Colors.Red.Medium);
+            return parent.DebugArea(string.Empty, Colors.Red.Medium);
+        }
+        
+        /// <summary>
+        /// Creates a virtual element that is visible on the elements trace when the layout overflow exception is thrown.
+        /// This can be used to easily identify elements inside the elements trace tree and faster find issue root cause.
+        /// </summary>
+        public static IContainer DebugPointer(this IContainer parent, string elementTraceText)
+        {
+            return parent.DebugPointer(elementTraceText, true);
+        }
+        
+        internal static IContainer DebugPointer(this IContainer parent, string elementTraceText, bool highlight)
+        {
+            return parent.Element(new DebugPointer
+            {
+                Target = elementTraceText,
+                Highlight = highlight
+            });
         }
         }
     }
     }
 }
 }

+ 6 - 0
QuestPDF/Helpers/Helpers.cs

@@ -2,6 +2,7 @@
 using System.IO;
 using System.IO;
 using System.Linq.Expressions;
 using System.Linq.Expressions;
 using System.Reflection;
 using System.Reflection;
+using System.Text.RegularExpressions;
 
 
 namespace QuestPDF.Helpers
 namespace QuestPDF.Helpers
 {
 {
@@ -35,5 +36,10 @@ namespace QuestPDF.Helpers
             var property = selector.ToPropertyInfo() ?? throw new Exception("Expected property with getter and setter.");
             var property = selector.ToPropertyInfo() ?? throw new Exception("Expected property with getter and setter.");
             property?.SetValue(target, value);
             property?.SetValue(target, value);
         }
         }
+
+        internal static string PrettifyName(this string text)
+        {
+            return Regex.Replace(text, @"([a-z])([A-Z])", "$1 $2");
+        }
     }
     }
 }
 }

+ 4 - 2
QuestPDF/Infrastructure/Element.cs

@@ -1,4 +1,6 @@
 using System;
 using System;
+using System.Collections;
+using System.Collections.Generic;
 using QuestPDF.Drawing;
 using QuestPDF.Drawing;
 
 
 namespace QuestPDF.Infrastructure
 namespace QuestPDF.Infrastructure
@@ -27,9 +29,9 @@ namespace QuestPDF.Infrastructure
         internal abstract SpacePlan Measure(Size availableSpace);
         internal abstract SpacePlan Measure(Size availableSpace);
         internal abstract void Draw(Size availableSpace);
         internal abstract void Draw(Size availableSpace);
 
 
-        public override string ToString()
+        protected virtual IEnumerable<string> GetDebugInformation()
         {
         {
-            return GetType().Name;
+            yield break;
         }
         }
     }
     }
 }
 }