Improved collecting layout information when the InfiniteLayoutException is thrown

This commit is contained in:
MarcinZiabek
2022-12-08 23:48:17 +01:00
parent 57c2a8cb38
commit 423ca330f7
16 changed files with 164 additions and 146 deletions
+2 -2
View File
@@ -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);
}
}
}
+2 -2
View File
@@ -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);
}
}
}
+9 -20
View File
@@ -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<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DebuggingState? debuggingState)
internal static void RenderPass<TCanvas>(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 =>
+1 -1
View File
@@ -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;
@@ -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;
}
+1 -1
View File
@@ -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;
}
-13
View File
@@ -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
View File
@@ -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<MeasureDetails> 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;
}
}
}
-89
View File
@@ -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()
};
}
}
}
}
@@ -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
View File
@@ -40,5 +40,5 @@ internal sealed class GenericErrorApiRequest
internal sealed class LayoutErrorApiRequest
{
public LayoutRenderingTrace? Trace { get; set; }
public LayoutErrorTrace? Trace { get; set; }
}
+16 -3
View File
@@ -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<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 SpacePlan SpacePlan { get; set; }
public IReadOnlyCollection<LayoutRenderingTrace> Children { get; set; }
}
internal sealed class GenericError
{
@@ -48,3 +59,5 @@ internal sealed class GenericError
@@ -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();
+38
View File
@@ -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
View File
@@ -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.
/// </summary>
/// <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;
/// <summary>
+4 -1
View File
@@ -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();