diff --git a/QuestPDF.ReportSample/PerformanceTests.cs b/QuestPDF.ReportSample/PerformanceTests.cs index 2d2a27e..85d810a 100644 --- a/QuestPDF.ReportSample/PerformanceTests.cs +++ b/QuestPDF.ReportSample/PerformanceTests.cs @@ -51,7 +51,7 @@ namespace QuestPDF.ReportSample Content = documentContainer.Compose(); PageContext = new PageContext(); - DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, null); + DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content); var sw = new Stopwatch(); sw.Start(); @@ -69,7 +69,7 @@ namespace QuestPDF.ReportSample [Benchmark] public void GenerationTest() { - DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, null); + DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content); } } } \ No newline at end of file diff --git a/QuestPDF.ReportSample/Tests.cs b/QuestPDF.ReportSample/Tests.cs index 6cc6b2f..e4b9bcd 100644 --- a/QuestPDF.ReportSample/Tests.cs +++ b/QuestPDF.ReportSample/Tests.cs @@ -53,8 +53,8 @@ namespace QuestPDF.ReportSample 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); } } } \ No newline at end of file diff --git a/QuestPDF/Drawing/DocumentGenerator.cs b/QuestPDF/Drawing/DocumentGenerator.cs index c708ee9..fa31288 100644 --- a/QuestPDF/Drawing/DocumentGenerator.cs +++ b/QuestPDF/Drawing/DocumentGenerator.cs @@ -75,24 +75,22 @@ namespace QuestPDF.Drawing ApplyDefaultTextStyle(content, TextStyle.LibraryDefault); ApplyContentDirection(content, ContentDirection.LeftToRight); - var debuggingState = Settings.EnableDebugging ? ApplyDebugging(content) : null; - if (Settings.EnableCaching) ApplyCaching(content); var pageContext = new PageContext(); - RenderPass(pageContext, new FreeCanvas(), content, debuggingState); + RenderPass(pageContext, new FreeCanvas(), content); if (applyInspection) ApplyInspection(content); - RenderPass(pageContext, canvas, content, debuggingState); + RenderPass(pageContext, canvas, content); if (applyInspection) inspectionResultHandler(DocumentHierarchyProcessor.ExtractDocumentHierarchy(content)); } - internal static void RenderPass(PageContext pageContext, TCanvas canvas, Container content, DebuggingState? debuggingState) + internal static void RenderPass(PageContext pageContext, TCanvas canvas, Container content) where TCanvas : ICanvas, IRenderingCanvas { InjectDependencies(content, pageContext, canvas); @@ -105,7 +103,6 @@ namespace QuestPDF.Drawing while(true) { pageContext.SetPageNumber(currentPage); - debuggingState?.Reset(); 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." + $"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) { content.VisitChildren(x => diff --git a/QuestPDF/Drawing/DocumentMetadata.cs b/QuestPDF/Drawing/DocumentMetadata.cs index 43420f6..e7692eb 100644 --- a/QuestPDF/Drawing/DocumentMetadata.cs +++ b/QuestPDF/Drawing/DocumentMetadata.cs @@ -33,7 +33,7 @@ namespace QuestPDF.Drawing 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 { get => Settings.EnableDebugging; diff --git a/QuestPDF/Drawing/Exceptions/DocumentLayoutException.cs b/QuestPDF/Drawing/Exceptions/DocumentLayoutException.cs index 8476107..ccfa042 100644 --- a/QuestPDF/Drawing/Exceptions/DocumentLayoutException.cs +++ b/QuestPDF/Drawing/Exceptions/DocumentLayoutException.cs @@ -5,9 +5,9 @@ namespace QuestPDF.Drawing.Exceptions { 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; } diff --git a/QuestPDF/Drawing/Proxy/CacheProxy.cs b/QuestPDF/Drawing/Proxy/CacheProxy.cs index 415f2cc..ce7a767 100644 --- a/QuestPDF/Drawing/Proxy/CacheProxy.cs +++ b/QuestPDF/Drawing/Proxy/CacheProxy.cs @@ -37,7 +37,7 @@ namespace QuestPDF.Drawing.Proxy 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; } diff --git a/QuestPDF/Drawing/Proxy/DebugStackItem.cs b/QuestPDF/Drawing/Proxy/DebugStackItem.cs deleted file mode 100644 index 5d1beed..0000000 --- a/QuestPDF/Drawing/Proxy/DebugStackItem.cs +++ /dev/null @@ -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 Stack { get; } = new List(); - } -} \ No newline at end of file diff --git a/QuestPDF/Drawing/Proxy/DebuggingProxy.cs b/QuestPDF/Drawing/Proxy/DebuggingProxy.cs index 9adb90f..849c863 100644 --- a/QuestPDF/Drawing/Proxy/DebuggingProxy.cs +++ b/QuestPDF/Drawing/Proxy/DebuggingProxy.cs @@ -1,24 +1,34 @@ -using QuestPDF.Infrastructure; +using System.Collections.Generic; +using QuestPDF.Infrastructure; namespace QuestPDF.Drawing.Proxy { internal class DebuggingProxy : ElementProxy { - private DebuggingState DebuggingState { get; } + internal List Measurements { get; } = new(); - public DebuggingProxy(DebuggingState debuggingState, Element child) + public DebuggingProxy(Element child) { - DebuggingState = debuggingState; Child = child; } 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; } } + + internal struct MeasureDetails + { + public Size AvailableSpace { get; } + public SpacePlan SpacePlan { get;} + + public MeasureDetails(Size availableSpace, SpacePlan spacePlan) + { + AvailableSpace = availableSpace; + SpacePlan = spacePlan; + } + } } \ No newline at end of file diff --git a/QuestPDF/Drawing/Proxy/DebuggingState.cs b/QuestPDF/Drawing/Proxy/DebuggingState.cs deleted file mode 100644 index a4232ca..0000000 --- a/QuestPDF/Drawing/Proxy/DebuggingState.cs +++ /dev/null @@ -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 Stack { get; set; } - - public DebuggingState() - { - Reset(); - } - - public void Reset() - { - Root = null; - Stack = new Stack(); - } - - 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() - }; - } - } - } -} \ No newline at end of file diff --git a/QuestPDF/Drawing/Proxy/LayoutErrorAnalyzer.cs b/QuestPDF/Drawing/Proxy/LayoutErrorAnalyzer.cs new file mode 100644 index 0000000..85bf659 --- /dev/null +++ b/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(); + return Map(proxies); + + LayoutErrorTrace Map(TreeNode 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() + }; + } + } + } +} \ No newline at end of file diff --git a/QuestPDF/Previewer/ApiRequests.cs b/QuestPDF/Previewer/ApiRequests.cs index 8b356cf..acbc040 100644 --- a/QuestPDF/Previewer/ApiRequests.cs +++ b/QuestPDF/Previewer/ApiRequests.cs @@ -40,5 +40,5 @@ internal sealed class GenericErrorApiRequest internal sealed class LayoutErrorApiRequest { - public LayoutRenderingTrace? Trace { get; set; } + public LayoutErrorTrace? Trace { get; set; } } \ No newline at end of file diff --git a/QuestPDF/Previewer/Models.cs b/QuestPDF/Previewer/Models.cs index cae512d..62c2c7c 100644 --- a/QuestPDF/Previewer/Models.cs +++ b/QuestPDF/Previewer/Models.cs @@ -25,17 +25,28 @@ internal sealed class SpacePlan public SpacePlanType Type { get; set; } } -internal sealed class LayoutRenderingTrace + + +internal sealed class LayoutErrorTrace { public string ElementType { get; set; } - public bool IsSingleChildContainer { get; internal set; } + public bool IsSingleChildContainer { get; set; } public IReadOnlyCollection ElementProperties { get; set; } + public IReadOnlyCollection Measurements { get; set; } + public IReadOnlyCollection Children { get; set; } +} + +internal sealed class LayoutErrorMeasurement +{ public Size AvailableSpace { get; set; } public SpacePlan SpacePlan { get; set; } - public IReadOnlyCollection Children { get; set; } } + + + + internal sealed class GenericError { @@ -48,3 +59,5 @@ internal sealed class GenericError + + diff --git a/QuestPDF/Previewer/PreviewerExtensions.cs b/QuestPDF/Previewer/PreviewerExtensions.cs index d51d42f..13ff32f 100644 --- a/QuestPDF/Previewer/PreviewerExtensions.cs +++ b/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) { - QuestPDF.Settings.EnableDebugging = true; - var previewerService = new PreviewerService(port); using var cancellationTokenSource = new CancellationTokenSource(); diff --git a/QuestPDF/Previewer/TreeTraversal.cs b/QuestPDF/Previewer/TreeTraversal.cs new file mode 100644 index 0000000..288c01b --- /dev/null +++ b/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 Value, ICollection> Children); + +internal static class TreeTraversal +{ + public static TreeNode ExtractProxyOfType(this Element root) where T : ElementProxy + { + return Traverse(root).FirstOrDefault(); + + IEnumerable> Traverse(Element element) + { + if (element is T proxy) + { + var result = new TreeNode(proxy, new List>()); + + 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; + } + } + } +} \ No newline at end of file diff --git a/QuestPDF/Settings.cs b/QuestPDF/Settings.cs index 06fcf2b..99d323e 100644 --- a/QuestPDF/Settings.cs +++ b/QuestPDF/Settings.cs @@ -1,4 +1,6 @@ -namespace QuestPDF +using System; + +namespace QuestPDF { public static class Settings { @@ -24,6 +26,7 @@ /// It includes layout calculation results and path to the problematic area. /// /// By default, this flag is enabled only when the debugger IS attached. + [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; /// diff --git a/SimpleApp/Program.cs b/SimpleApp/Program.cs index 6e56a77..8aea826 100644 --- a/SimpleApp/Program.cs +++ b/SimpleApp/Program.cs @@ -5,6 +5,9 @@ using QuestPDF.Helpers; using QuestPDF.Infrastructure; using QuestPDF.Previewer; +QuestPDF.Settings.EnableCaching = true; +QuestPDF.Settings.EnableDebugging = true; + Document .Create(container => { @@ -49,5 +52,5 @@ Document }); }); }) - .ShowInPreviewer(); + .GeneratePdf();