Browse Source

Improved collecting layout information when the InfiniteLayoutException is thrown

MarcinZiabek 3 years ago
parent
commit
423ca330f7

+ 2 - 2
QuestPDF.ReportSample/PerformanceTests.cs

@@ -51,7 +51,7 @@ namespace QuestPDF.ReportSample
             Content = documentContainer.Compose();
             Content = documentContainer.Compose();
 
 
             PageContext = new PageContext();
             PageContext = new PageContext();
-            DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, null);
+            DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content);
 
 
             var sw = new Stopwatch();
             var sw = new Stopwatch();
             sw.Start();
             sw.Start();
@@ -69,7 +69,7 @@ namespace QuestPDF.ReportSample
         [Benchmark]
         [Benchmark]
         public void GenerationTest()
         public void GenerationTest()
         {
         {
-            DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, null);
+            DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content);
         }
         }
     }
     }
 }
 }

+ 2 - 2
QuestPDF.ReportSample/Tests.cs

@@ -53,8 +53,8 @@ namespace QuestPDF.ReportSample
             
             
             var pageContext = new PageContext();
             var pageContext = new PageContext();
 
 
-            DocumentGenerator.RenderPass(pageContext, new FreeCanvas(), content, null);
-            DocumentGenerator.RenderPass(pageContext, new FreeCanvas(), content, null);
+            DocumentGenerator.RenderPass(pageContext, new FreeCanvas(), content);
+            DocumentGenerator.RenderPass(pageContext, new FreeCanvas(), content);
         }
         }
     }
     }
 }
 }

+ 9 - 20
QuestPDF/Drawing/DocumentGenerator.cs

@@ -75,24 +75,22 @@ namespace QuestPDF.Drawing
             ApplyDefaultTextStyle(content, TextStyle.LibraryDefault);
             ApplyDefaultTextStyle(content, TextStyle.LibraryDefault);
             ApplyContentDirection(content, ContentDirection.LeftToRight);
             ApplyContentDirection(content, ContentDirection.LeftToRight);
             
             
-            var debuggingState = Settings.EnableDebugging ? ApplyDebugging(content) : null;
-            
             if (Settings.EnableCaching)
             if (Settings.EnableCaching)
                 ApplyCaching(content);
                 ApplyCaching(content);
 
 
             var pageContext = new PageContext();
             var pageContext = new PageContext();
-            RenderPass(pageContext, new FreeCanvas(), content, debuggingState);
+            RenderPass(pageContext, new FreeCanvas(), content);
             
             
             if (applyInspection)
             if (applyInspection)
                 ApplyInspection(content);
                 ApplyInspection(content);
             
             
-            RenderPass(pageContext, canvas, content, debuggingState);
+            RenderPass(pageContext, canvas, content);
 
 
             if (applyInspection)
             if (applyInspection)
                 inspectionResultHandler(DocumentHierarchyProcessor.ExtractDocumentHierarchy(content));
                 inspectionResultHandler(DocumentHierarchyProcessor.ExtractDocumentHierarchy(content));
         }
         }
         
         
-        internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DebuggingState? debuggingState)
+        internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content)
             where TCanvas : ICanvas, IRenderingCanvas
             where TCanvas : ICanvas, IRenderingCanvas
         {
         {
             InjectDependencies(content, pageContext, canvas);
             InjectDependencies(content, pageContext, canvas);
@@ -105,7 +103,6 @@ namespace QuestPDF.Drawing
             while(true)
             while(true)
             {
             {
                 pageContext.SetPageNumber(currentPage);
                 pageContext.SetPageNumber(currentPage);
-                debuggingState?.Reset();
                 
                 
                 var spacePlan = content.Measure(Size.Max);
                 var spacePlan = content.Measure(Size.Max);
 
 
@@ -150,9 +147,13 @@ namespace QuestPDF.Drawing
                               $"2) The layout configuration of your document is invalid. Some of the elements require more space than is provided." +
                               $"2) The layout configuration of your document is invalid. Some of the elements require more space than is provided." +
                               $"Please analyze your documents structure to detect this element and fix its size constraints.";
                               $"Please analyze your documents structure to detect this element and fix its size constraints.";
 
 
-                var elementTrace = debuggingState?.BuildTrace(); // ?? "Debug trace is available only in the DEBUG mode.";
+                content.RemoveExistingProxies();
+                content.ApplyLayoutDebugging();
+                content.Measure(Size.Max);
+                
+                var layoutTrace = content.CollectLayoutErrorTrace();
 
 
-                throw new DocumentLayoutException(message, elementTrace);
+                throw new DocumentLayoutException(message, layoutTrace);
             }
             }
         }
         }
 
 
@@ -177,18 +178,6 @@ namespace QuestPDF.Drawing
             });
             });
         }
         }
 
 
-        private static DebuggingState ApplyDebugging(Container content)
-        {
-            var debuggingState = new DebuggingState();
-
-            content.VisitChildren(x =>
-            {
-                x.CreateProxy(y => y is ElementProxy ? y : new DebuggingProxy(debuggingState, y));
-            });
-
-            return debuggingState;
-        }
-        
         private static void ApplyInspection(Container content)
         private static void ApplyInspection(Container content)
         {
         {
             content.VisitChildren(x =>
             content.VisitChildren(x =>

+ 1 - 1
QuestPDF/Drawing/DocumentMetadata.cs

@@ -33,7 +33,7 @@ namespace QuestPDF.Drawing
             set => Settings.EnableCaching = value;
             set => Settings.EnableCaching = value;
         }
         }
         
         
-        [Obsolete("This API has been moved since version 2022.9. Please use the QuestPDF.Settings.EnableDebugging static property.")]
+        [Obsolete("The new implementation for debugging layout issues does not introduce additional performance overhead. Therefore, this setting is no longer used since the 2023.1 release. Please remove this setter from your code.")]
         public bool ApplyDebugging
         public bool ApplyDebugging
         {
         {
             get => Settings.EnableDebugging;
             get => Settings.EnableDebugging;

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

@@ -5,9 +5,9 @@ namespace QuestPDF.Drawing.Exceptions
 {
 {
     public class DocumentLayoutException : Exception
     public class DocumentLayoutException : Exception
     {
     {
-        internal LayoutRenderingTrace? ElementTrace { get; }
+        internal LayoutErrorTrace? ElementTrace { get; }
 
 
-        internal DocumentLayoutException(string message, LayoutRenderingTrace? elementTrace = null) : base(message)
+        internal DocumentLayoutException(string message, LayoutErrorTrace? elementTrace = null) : base(message)
         {
         {
             ElementTrace = elementTrace;
             ElementTrace = elementTrace;
         }
         }

+ 1 - 1
QuestPDF/Drawing/Proxy/CacheProxy.cs

@@ -37,7 +37,7 @@ namespace QuestPDF.Drawing.Proxy
             base.Draw(availableSpace);
             base.Draw(availableSpace);
         }
         }
 
 
-        private bool IsClose(float x, float y)
+        private static bool IsClose(float x, float y)
         {
         {
             return Math.Abs(x - y) < Size.Epsilon;
             return Math.Abs(x - y) < Size.Epsilon;
         }
         }

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

@@ -1,13 +0,0 @@
-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; } = new List<DebugStackItem>();
-    }
-}

+ 18 - 8
QuestPDF/Drawing/Proxy/DebuggingProxy.cs

@@ -1,24 +1,34 @@
-using QuestPDF.Infrastructure;
+using System.Collections.Generic;
+using QuestPDF.Infrastructure;
 
 
 namespace QuestPDF.Drawing.Proxy
 namespace QuestPDF.Drawing.Proxy
 {
 {
     internal class DebuggingProxy : ElementProxy
     internal class DebuggingProxy : ElementProxy
     {
     {
-        private DebuggingState DebuggingState { get; }
+        internal List<MeasureDetails> Measurements { get; } = new();
 
 
-        public DebuggingProxy(DebuggingState debuggingState, Element child)
+        public DebuggingProxy(Element child)
         {
         {
-            DebuggingState = debuggingState;
             Child = child;
             Child = child;
         }
         }
         
         
         internal override SpacePlan Measure(Size availableSpace)
         internal override SpacePlan Measure(Size availableSpace)
         {
         {
-            DebuggingState.RegisterMeasure(Child, availableSpace);
-            var spacePlan = base.Measure(availableSpace);
-            DebuggingState.RegisterMeasureResult(Child, spacePlan);
-
+            var spacePlan = Child.Measure(availableSpace);
+            Measurements.Add(new MeasureDetails(availableSpace, spacePlan));
             return spacePlan;
             return spacePlan;
         }
         }
     }
     }
+
+    internal struct MeasureDetails
+    {
+        public Size AvailableSpace { get; }
+        public SpacePlan SpacePlan { get;}
+
+        public MeasureDetails(Size availableSpace, SpacePlan spacePlan)
+        {
+            AvailableSpace = availableSpace;
+            SpacePlan = spacePlan;
+        }
+    }
 }
 }

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

@@ -1,89 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using QuestPDF.Elements;
-using QuestPDF.Helpers;
-using QuestPDF.Infrastructure;
-using QuestPDF.Previewer;
-using Size = QuestPDF.Infrastructure.Size;
-
-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 LayoutRenderingTrace BuildTrace()
-        {
-            return Traverse(Root);
-
-            LayoutRenderingTrace Traverse(DebugStackItem item)
-            {
-                return  new LayoutRenderingTrace
-                {
-                    ElementType = item.Element.GetType().Name,
-                    IsSingleChildContainer = item.Element is ContainerElement,
-                    ElementProperties = item.Element.GetElementConfiguration().ToList(),
-                    AvailableSpace = new QuestPDF.Previewer.Size()
-                    {
-                        Width = item.AvailableSpace.Width,
-                        Height = item.AvailableSpace.Height
-                    },
-                    SpacePlan = new QuestPDF.Previewer.SpacePlan()
-                    {
-                        Width = item.SpacePlan.Width,
-                        Height = item.SpacePlan.Height,
-                        Type = item.SpacePlan.Type
-                    },
-                    Children = item.Stack.Select(Traverse).ToList()
-                };
-            }
-        }
-    }
-}

+ 66 - 0
QuestPDF/Drawing/Proxy/LayoutErrorAnalyzer.cs

@@ -0,0 +1,66 @@
+using System.Linq;
+using QuestPDF.Elements;
+using QuestPDF.Helpers;
+using QuestPDF.Infrastructure;
+using QuestPDF.Previewer;
+
+namespace QuestPDF.Drawing.Proxy
+{
+    internal static class LayoutErrorAnalyzer
+    {
+        public static void ApplyLayoutDebugging(this Container content)
+        {
+            content.VisitChildren(x =>
+            {
+                x.CreateProxy(y => y is ElementProxy ? y : new DebuggingProxy(y));
+            });
+        }
+        
+        public static void RemoveExistingProxies(this Container content)
+        {
+            content.VisitChildren(x =>
+            {
+                x.CreateProxy(y => y is ElementProxy proxy ? proxy.Child : y);
+            });
+        }
+
+        public static LayoutErrorTrace CollectLayoutErrorTrace(this Container content)
+        {
+            var proxies = content.ExtractProxyOfType<DebuggingProxy>();
+            return Map(proxies);
+
+            LayoutErrorTrace Map(TreeNode<DebuggingProxy> proxy)
+            {
+                var element = proxy.Value.Child;
+                
+                return new LayoutErrorTrace
+                {
+                    ElementType = element.GetType().Name,
+                    IsSingleChildContainer = element is ContainerElement,
+                    ElementProperties = element.GetElementConfiguration().ToList(),
+                    
+                    Measurements = proxy
+                        .Value
+                        .Measurements
+                        .Select(x => new LayoutErrorMeasurement
+                        {
+                            AvailableSpace = new QuestPDF.Previewer.Size()
+                            {
+                                Width = x.AvailableSpace.Width,
+                                Height = x.AvailableSpace.Height
+                            },
+                            SpacePlan = new QuestPDF.Previewer.SpacePlan()
+                            {
+                                Width = x.SpacePlan.Width,
+                                Height = x.SpacePlan.Height,
+                                Type = x.SpacePlan.Type
+                            }
+                        })
+                        .ToList(),
+                    
+                    Children = proxy.Children.Select(Map).ToList()
+                };
+            }
+        }
+    }
+}

+ 1 - 1
QuestPDF/Previewer/ApiRequests.cs

@@ -40,5 +40,5 @@ internal sealed class GenericErrorApiRequest
     
     
 internal sealed class LayoutErrorApiRequest
 internal sealed class LayoutErrorApiRequest
 {
 {
-    public LayoutRenderingTrace? Trace { get; set; }
+    public LayoutErrorTrace? Trace { get; set; }
 }
 }

+ 16 - 3
QuestPDF/Previewer/Models.cs

@@ -25,17 +25,28 @@ internal sealed class SpacePlan
     public SpacePlanType Type { get; set; }
     public SpacePlanType Type { get; set; }
 }
 }
 
 
-internal sealed class LayoutRenderingTrace
+
+
+internal sealed class LayoutErrorTrace
 {
 {
     public string ElementType { get; set; }
     public string ElementType { get; set; }
-    public bool IsSingleChildContainer { get; internal set; }
+    public bool IsSingleChildContainer { get; set; }
     public IReadOnlyCollection<DocumentElementProperty> ElementProperties { get; set; }
     public IReadOnlyCollection<DocumentElementProperty> ElementProperties { get; set; }
+    public IReadOnlyCollection<LayoutErrorMeasurement> Measurements { get; set; }
+    public IReadOnlyCollection<LayoutErrorTrace> Children { get; set; }
+}
+
+internal sealed class LayoutErrorMeasurement
+{
     public Size AvailableSpace { get; set; }
     public Size AvailableSpace { get; set; }
     public SpacePlan SpacePlan { get; set; }
     public SpacePlan SpacePlan { get; set; }
-    public IReadOnlyCollection<LayoutRenderingTrace> Children { get; set; }
 }
 }
 
 
 
 
+
+
+
+
     
     
 internal sealed class GenericError
 internal sealed class GenericError
 {
 {
@@ -48,3 +59,5 @@ internal sealed class GenericError
 
 
 
 
 
 
+
+

+ 0 - 2
QuestPDF/Previewer/PreviewerExtensions.cs

@@ -19,8 +19,6 @@ namespace QuestPDF.Previewer
         
         
         public static async Task ShowInPreviewerAsync(this IDocument document, int port = 12500, CancellationToken cancellationToken = default)
         public static async Task ShowInPreviewerAsync(this IDocument document, int port = 12500, CancellationToken cancellationToken = default)
         {
         {
-            QuestPDF.Settings.EnableDebugging = true;
-            
             var previewerService = new PreviewerService(port);
             var previewerService = new PreviewerService(port);
             
             
             using var cancellationTokenSource = new CancellationTokenSource();
             using var cancellationTokenSource = new CancellationTokenSource();

+ 38 - 0
QuestPDF/Previewer/TreeTraversal.cs

@@ -0,0 +1,38 @@
+using System.Collections.Generic;
+using System.Linq;
+using QuestPDF.Drawing.Proxy;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Previewer;
+
+internal record TreeNode<T>(T Value, ICollection<TreeNode<T>> Children);
+
+internal static class TreeTraversal
+{
+    public static TreeNode<T> ExtractProxyOfType<T>(this Element root) where T : ElementProxy
+    {
+        return Traverse(root).FirstOrDefault();
+
+        IEnumerable<TreeNode<T>> Traverse(Element element)
+        {
+            if (element is T proxy)
+            {
+                var result = new TreeNode<T>(proxy, new List<TreeNode<T>>());
+                
+                proxy
+                    .Child!
+                    .GetChildren()
+                    .SelectMany(Traverse)
+                    .ToList()
+                    .ForEach(result.Children.Add);
+
+                yield return result;
+            }
+            else
+            {
+                foreach (var treeNode in element.GetChildren().SelectMany(Traverse))
+                    yield return treeNode;
+            }
+        }
+    }
+}

+ 4 - 1
QuestPDF/Settings.cs

@@ -1,4 +1,6 @@
-namespace QuestPDF
+using System;
+
+namespace QuestPDF
 {
 {
     public static class Settings
     public static class Settings
     {
     {
@@ -24,6 +26,7 @@
         /// It includes layout calculation results and path to the problematic area.
         /// It includes layout calculation results and path to the problematic area.
         /// </summary>
         /// </summary>
         /// <remarks>By default, this flag is enabled only when the debugger IS attached.</remarks>
         /// <remarks>By default, this flag is enabled only when the debugger IS attached.</remarks>
+        [Obsolete("The new implementation for debugging layout issues does not introduce any additional performance overhead. Therefore, this setting is no longer used since the 2023.1 release. Please remove this setter from your code.")]
         public static bool EnableDebugging { get; set; } = System.Diagnostics.Debugger.IsAttached;
         public static bool EnableDebugging { get; set; } = System.Diagnostics.Debugger.IsAttached;
 
 
         /// <summary>
         /// <summary>

+ 4 - 1
SimpleApp/Program.cs

@@ -5,6 +5,9 @@ using QuestPDF.Helpers;
 using QuestPDF.Infrastructure;
 using QuestPDF.Infrastructure;
 using QuestPDF.Previewer;
 using QuestPDF.Previewer;
 
 
+QuestPDF.Settings.EnableCaching = true;
+QuestPDF.Settings.EnableDebugging = true;
+
 Document
 Document
     .Create(container =>
     .Create(container =>
     {
     {
@@ -49,5 +52,5 @@ Document
                 });
                 });
         });
         });
     })
     })
-    .ShowInPreviewer();
+    .GeneratePdf();