Improved infinite layout debugging experience
This commit is contained in:
@@ -23,8 +23,8 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -155,7 +155,7 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.Debug("Grid example", Colors.Blue.Medium)
|
||||
.DebugArea("Grid example", Colors.Blue.Medium)
|
||||
.Grid(grid =>
|
||||
{
|
||||
grid.Columns(3);
|
||||
|
||||
@@ -66,13 +66,13 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
|
||||
void PhotosElement(IContainer container, ReportSectionPhotos model)
|
||||
{
|
||||
if (model.Photos.Count == 0)
|
||||
if (model.Photos.Count == 0)
|
||||
{
|
||||
container.Text("No photos", Typography.Normal);
|
||||
return;
|
||||
}
|
||||
|
||||
container.Debug("Photos").Grid(grid =>
|
||||
container.DebugArea("Photos").Grid(grid =>
|
||||
{
|
||||
grid.Spacing(5);
|
||||
grid.Columns(3);
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Measure_ReturnsWrap_WhenDecorationReturnsWrap()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleDecoration
|
||||
.For(x => new BinaryDecoration
|
||||
{
|
||||
Type = DecorationType.Append,
|
||||
DecorationElement = x.CreateChild("decoration"),
|
||||
@@ -30,7 +30,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Measure_ReturnsWrap_WhenDecorationReturnsPartialRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleDecoration
|
||||
.For(x => new BinaryDecoration
|
||||
{
|
||||
Type = DecorationType.Append,
|
||||
DecorationElement = x.CreateChild("decoration"),
|
||||
@@ -45,7 +45,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Measure_ReturnsWrap_WhenContentReturnsWrap()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleDecoration
|
||||
.For(x => new BinaryDecoration
|
||||
{
|
||||
Type = DecorationType.Append,
|
||||
DecorationElement = x.CreateChild("decoration"),
|
||||
@@ -61,7 +61,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Measure_ReturnsPartialRender_WhenContentReturnsPartialRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleDecoration
|
||||
.For(x => new BinaryDecoration
|
||||
{
|
||||
Type = DecorationType.Append,
|
||||
DecorationElement = x.CreateChild("decoration"),
|
||||
@@ -77,7 +77,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Measure_ReturnsFullRender_WhenContentReturnsFullRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleDecoration
|
||||
.For(x => new BinaryDecoration
|
||||
{
|
||||
Type = DecorationType.Append,
|
||||
DecorationElement = x.CreateChild("decoration"),
|
||||
@@ -97,7 +97,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Draw_Prepend()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleDecoration
|
||||
.For(x => new BinaryDecoration
|
||||
{
|
||||
Type = DecorationType.Prepend,
|
||||
DecorationElement = x.CreateChild("decoration"),
|
||||
@@ -116,7 +116,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Draw_Append()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleDecoration
|
||||
.For(x => new BinaryDecoration
|
||||
{
|
||||
Type = DecorationType.Append,
|
||||
DecorationElement = x.CreateChild("decoration"),
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Measure_ReturnsWrap_WhenLeftChildReturnsWrap()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleRow
|
||||
.For(x => new BinaryRow
|
||||
{
|
||||
Left = x.CreateChild("left"),
|
||||
Right = x.CreateChild("right")
|
||||
@@ -29,7 +29,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Measure_ReturnsWrap_WhenRightChildReturnsWrap()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleRow
|
||||
.For(x => new BinaryRow
|
||||
{
|
||||
Left = x.CreateChild("left"),
|
||||
Right = x.CreateChild("right")
|
||||
@@ -44,7 +44,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Measure_ReturnsPartialRender_WhenLeftChildReturnsPartialRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleRow
|
||||
.For(x => new BinaryRow
|
||||
{
|
||||
Left = x.CreateChild("left"),
|
||||
Right = x.CreateChild("right")
|
||||
@@ -59,7 +59,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Measure_ReturnsPartialRender_WhenRightChildReturnsPartialRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleRow
|
||||
.For(x => new BinaryRow
|
||||
{
|
||||
Left = x.CreateChild("left"),
|
||||
Right = x.CreateChild("right")
|
||||
@@ -74,7 +74,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Measure_ReturnsFullRender_WhenBothChildrenReturnFullRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleRow
|
||||
.For(x => new BinaryRow
|
||||
{
|
||||
Left = x.CreateChild("left"),
|
||||
Right = x.CreateChild("right")
|
||||
@@ -93,7 +93,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Draw()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleRow
|
||||
.For(x => new BinaryRow
|
||||
{
|
||||
Left = x.CreateChild("left"),
|
||||
Right = x.CreateChild("right")
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Measure_ReturnsWrap_WhenFirstChildWraps()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleStack
|
||||
.For(x => new BinaryStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
@@ -29,7 +29,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Measure_ReturnsPartialRender_WhenFirstChildReturnsPartialRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleStack
|
||||
.For(x => new BinaryStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
@@ -43,7 +43,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Measure_ReturnsPartialRender_WhenSecondChildWraps()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleStack
|
||||
.For(x => new BinaryStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
@@ -58,7 +58,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Measure_ReturnsPartialRender_WhenSecondChildReturnsPartialRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleStack
|
||||
.For(x => new BinaryStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
@@ -73,7 +73,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Measure_ReturnsFullRender_WhenSecondChildReturnsFullRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleStack
|
||||
.For(x => new BinaryStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
@@ -88,7 +88,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Measure_UsesEmpty_WhenFirstChildIsRendered()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleStack
|
||||
.For(x => new BinaryStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second"),
|
||||
@@ -108,7 +108,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Draw_WhenFirstChildWraps()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleStack
|
||||
.For(x => new BinaryStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
@@ -122,7 +122,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Draw_WhenFirstChildPartiallyRenders()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleStack
|
||||
.For(x => new BinaryStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
@@ -137,7 +137,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Draw_WhenFirstChildFullyRenders_AndSecondChildWraps()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleStack
|
||||
.For(x => new BinaryStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
@@ -153,7 +153,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Draw_WhenFirstChildFullyRenders_AndSecondChildPartiallyRenders()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleStack
|
||||
.For(x => new BinaryStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
@@ -172,7 +172,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Draw_WhenFirstChildFullyRenders_AndSecondChildFullyRenders()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleStack
|
||||
.For(x => new BinaryStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
@@ -191,7 +191,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Draw_UsesEmpty_WhenFirstChildIsRendered()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleStack
|
||||
.For(x => new BinaryStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second"),
|
||||
@@ -203,7 +203,7 @@ namespace QuestPDF.UnitTests
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw("second", new Size(400, 300))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.CheckState<SimpleStack>(x => x.IsFirstRendered)
|
||||
.CheckState<BinaryStack>(x => x.IsFirstRendered)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Draw_TogglesFirstRenderedFlag_WhenSecondFullyRenders()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleStack
|
||||
.For(x => new BinaryStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second"),
|
||||
@@ -224,7 +224,7 @@ namespace QuestPDF.UnitTests
|
||||
.ExpectChildDraw("second", new Size(400, 300))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.CheckDrawResult()
|
||||
.CheckState<SimpleStack>(x => !x.IsFirstRendered);
|
||||
.CheckState<BinaryStack>(x => !x.IsFirstRendered);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Drawing.Proxy;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
@@ -35,8 +36,16 @@ namespace QuestPDF.Drawing
|
||||
var metadata = document.GetMetadata();
|
||||
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, canvas, content, metadata, debuggingState);
|
||||
@@ -62,7 +71,7 @@ namespace QuestPDF.Drawing
|
||||
if (spacePlan.Type == SpacePlanType.Wrap)
|
||||
{
|
||||
canvas.EndDocument();
|
||||
throw new DocumentDrawingException("An exception occured during document drawing.");
|
||||
ThrowLayoutException();
|
||||
}
|
||||
|
||||
try
|
||||
@@ -81,7 +90,7 @@ namespace QuestPDF.Drawing
|
||||
if (currentPage >= documentMetadata.DocumentLayoutExceptionThreshold)
|
||||
{
|
||||
canvas.EndDocument();
|
||||
throw new DocumentLayoutException("Composed layout generates infinite document.");
|
||||
ThrowLayoutException();
|
||||
}
|
||||
|
||||
if (spacePlan.Type == SpacePlanType.FullRender)
|
||||
@@ -91,6 +100,14 @@ namespace QuestPDF.Drawing
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace QuestPDF.Drawing.Exceptions
|
||||
{
|
||||
public class DocumentLayoutException : Exception
|
||||
{
|
||||
public string ElementTrace { get; set; }
|
||||
|
||||
public DocumentLayoutException()
|
||||
{
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() + "...";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Drawing.Proxy
|
||||
{
|
||||
internal class ElementProxy : ContainerElement
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,6 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
public enum SpacePlanType
|
||||
{
|
||||
Wrap,
|
||||
PartialRender,
|
||||
FullRender
|
||||
}
|
||||
|
||||
public readonly struct SpacePlan
|
||||
{
|
||||
public readonly SpacePlanType Type;
|
||||
@@ -35,7 +28,10 @@ namespace QuestPDF.Drawing
|
||||
|
||||
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)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
public enum SpacePlanType
|
||||
{
|
||||
Wrap,
|
||||
PartialRender,
|
||||
FullRender
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Debug : IComponent
|
||||
internal class DebugArea : IComponent
|
||||
{
|
||||
public IElement? Child { get; set; }
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace QuestPDF.Elements
|
||||
.Box()
|
||||
.Background(Colors.White)
|
||||
.Padding(2)
|
||||
.Text(Text, TextStyle.Default.Color(Color).FontType("Consolas").Size(8));
|
||||
.Text(Text, TextStyle.Default.Color(Color).FontType(Fonts.Consolas).Size(8));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class DebugPointer : Container
|
||||
{
|
||||
public string Target { get; set; }
|
||||
public bool Highlight { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ namespace QuestPDF.Elements
|
||||
Append
|
||||
}
|
||||
|
||||
internal class SimpleDecoration : Element
|
||||
internal class BinaryDecoration : Element
|
||||
{
|
||||
public Element DecorationElement { get; set; } = Empty.Instance;
|
||||
public Element ContentElement { get; set; } = Empty.Instance;
|
||||
@@ -83,11 +83,11 @@ namespace QuestPDF.Elements
|
||||
|
||||
public void Compose(IContainer container)
|
||||
{
|
||||
container.Element(new SimpleDecoration
|
||||
container.Element(new BinaryDecoration
|
||||
{
|
||||
Type = DecorationType.Prepend,
|
||||
DecorationElement = Header,
|
||||
ContentElement = new SimpleDecoration
|
||||
ContentElement = new BinaryDecoration
|
||||
{
|
||||
Type = DecorationType.Append,
|
||||
ContentElement = Content,
|
||||
|
||||
@@ -24,11 +24,6 @@ namespace QuestPDF.Elements
|
||||
Size = size;
|
||||
SetWidth(size);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"RowColumn: Constant({Size})";
|
||||
}
|
||||
}
|
||||
|
||||
internal class RelativeRowElement : RowElement
|
||||
@@ -37,14 +32,9 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
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 Right { get; set; }
|
||||
@@ -179,7 +169,7 @@ namespace QuestPDF.Elements
|
||||
|
||||
var half = elements.Length / 2;
|
||||
|
||||
return new SimpleRow
|
||||
return new BinaryRow
|
||||
{
|
||||
Left = BuildTree(elements.Slice(0, half)),
|
||||
Right = BuildTree(elements.Slice(half))
|
||||
|
||||
@@ -9,7 +9,7 @@ using IContainer = QuestPDF.Infrastructure.IContainer;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class SimpleStack : Element, IStateResettable
|
||||
internal class BinaryStack : Element, IStateResettable
|
||||
{
|
||||
internal Element First { get; set; } = Empty.Instance;
|
||||
internal Element Second { get; set; } = Empty.Instance;
|
||||
@@ -135,7 +135,7 @@ namespace QuestPDF.Elements
|
||||
|
||||
var half = elements.Length / 2;
|
||||
|
||||
return new SimpleStack
|
||||
return new BinaryStack
|
||||
{
|
||||
First = BuildTree(elements.Slice(0, half)),
|
||||
Second = BuildTree(elements.Slice(half))
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace QuestPDF.Fluent
|
||||
var existingValue = Component.GetPropertyValue(selector);
|
||||
|
||||
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();
|
||||
Component.SetPropertyValue(selector, slot);
|
||||
@@ -55,7 +55,10 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
var descriptor = new ComponentDescriptor<T>(component);
|
||||
handler?.Invoke(descriptor);
|
||||
|
||||
|
||||
if (System.Diagnostics.Debugger.IsAttached)
|
||||
element = element.DebugPointer(component.GetType().Name, highlight: false);
|
||||
|
||||
component.Compose(element.Container());
|
||||
}
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
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();
|
||||
|
||||
parent.Component(new Debug
|
||||
parent.Component(new DebugArea
|
||||
{
|
||||
Child = container,
|
||||
Text = text,
|
||||
@@ -20,14 +20,32 @@ namespace QuestPDF.Fluent
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.IO;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace QuestPDF.Helpers
|
||||
{
|
||||
@@ -35,5 +36,10 @@ namespace QuestPDF.Helpers
|
||||
var property = selector.ToPropertyInfo() ?? throw new Exception("Expected property with getter and setter.");
|
||||
property?.SetValue(target, value);
|
||||
}
|
||||
|
||||
internal static string PrettifyName(this string text)
|
||||
{
|
||||
return Regex.Replace(text, @"([a-z])([A-Z])", "$1 $2");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using QuestPDF.Drawing;
|
||||
|
||||
namespace QuestPDF.Infrastructure
|
||||
@@ -27,9 +29,9 @@ namespace QuestPDF.Infrastructure
|
||||
internal abstract SpacePlan Measure(Size availableSpace);
|
||||
internal abstract void Draw(Size availableSpace);
|
||||
|
||||
public override string ToString()
|
||||
protected virtual IEnumerable<string> GetDebugInformation()
|
||||
{
|
||||
return GetType().Name;
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user