VisitChildren -> GetChildren traversal refactorization

This commit is contained in:
Marcin Ziąbek 2022-01-05 01:10:05 +01:00
Родитель 7f651932b9
Коммит eb297177ea
21 изменённых файлов: 127 добавлений и 102 удалений

Просмотреть файл

@ -11,6 +11,7 @@ using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Drawing.Proxy;
using QuestPDF.Elements;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using QuestPDF.ReportSample.Layouts;
@ -55,7 +56,7 @@ namespace QuestPDF.ReportSample
var sw = new Stopwatch();
sw.Start();
Content.HandleVisitor(x =>
Content.VisitChildren(x =>
{
if (x is ICacheable)
x.CreateProxy(y => new CacheProxy(y));

Просмотреть файл

@ -213,7 +213,7 @@ namespace QuestPDF.UnitTests.TestEngine
public TestPlan CheckMeasureResult(SpacePlan expected)
{
Element.HandleVisitor(x => x?.Initialize(null, Canvas));
Element.VisitChildren(x => x?.Initialize(null, Canvas));
var actual = Element.Measure(OperationInput);
@ -228,7 +228,7 @@ namespace QuestPDF.UnitTests.TestEngine
public TestPlan CheckDrawResult()
{
Element.HandleVisitor(x => x?.Initialize(null, Canvas));
Element.VisitChildren(x => x?.Initialize(null, Canvas));
Element.Draw(OperationInput);
return this;
}
@ -271,10 +271,10 @@ namespace QuestPDF.UnitTests.TestEngine
availableSpace ??= new Size(400, 300);
var canvas = new FreeCanvas();
value.HandleVisitor(x => x.Initialize(null, canvas));
value.VisitChildren(x => x.Initialize(null, canvas));
var valueMeasure = value.Measure(availableSpace.Value);
expected.HandleVisitor(x => x.Initialize(null, canvas));
expected.VisitChildren(x => x.Initialize(null, canvas));
var expectedMeasure = expected.Measure(availableSpace.Value);
valueMeasure.Should().BeEquivalentTo(expectedMeasure);
@ -285,11 +285,11 @@ namespace QuestPDF.UnitTests.TestEngine
availableSpace ??= new Size(400, 300);
var valueCanvas = new OperationRecordingCanvas();
value.HandleVisitor(x => x.Initialize(null, valueCanvas));
value.VisitChildren(x => x.Initialize(null, valueCanvas));
value.Draw(availableSpace.Value);
var expectedCanvas = new OperationRecordingCanvas();
expected.HandleVisitor(x => x.Initialize(null, expectedCanvas));
expected.VisitChildren(x => x.Initialize(null, expectedCanvas));
expected.Draw(availableSpace.Value);
valueCanvas.Operations.Should().BeEquivalentTo(expectedCanvas.Operations);

Просмотреть файл

@ -7,6 +7,8 @@ using QuestPDF.Drawing.Proxy;
using QuestPDF.Elements;
using QuestPDF.Elements.Text;
using QuestPDF.Elements.Text.Items;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Drawing
@ -42,6 +44,7 @@ namespace QuestPDF.Drawing
var container = new DocumentContainer();
document.Compose(container);
var content = container.Compose();
ApplyDefaultTextStyle(content, TextStyle.LibraryDefault);
var metadata = document.GetMetadata();
var pageContext = new PageContext();
@ -58,8 +61,8 @@ namespace QuestPDF.Drawing
internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DocumentMetadata documentMetadata, DebuggingState? debuggingState)
where TCanvas : ICanvas, IRenderingCanvas
{
content.HandleVisitor(x => x?.Initialize(pageContext, canvas));
content.HandleVisitor(x => (x as IStateResettable)?.ResetState());
content.VisitChildren(x => x?.Initialize(pageContext, canvas));
content.VisitChildren(x => (x as IStateResettable)?.ResetState());
canvas.BeginDocument();
@ -122,7 +125,7 @@ namespace QuestPDF.Drawing
private static void ApplyCaching(Container content)
{
content.HandleVisitor(x =>
content.VisitChildren(x =>
{
if (x is ICacheable)
x.CreateProxy(y => new CacheProxy(y));
@ -133,7 +136,7 @@ namespace QuestPDF.Drawing
{
var debuggingState = new DebuggingState();
content.HandleVisitor(x =>
content.VisitChildren(x =>
{
x.CreateProxy(y => new DebuggingProxy(debuggingState, y));
});
@ -141,26 +144,38 @@ namespace QuestPDF.Drawing
return debuggingState;
}
internal static void ApplyDefaultTextStyle(this Element content, TextStyle documentDefaultTextStyle)
private static void ApplyDefaultTextStyle(this Element? content, TextStyle documentDefaultTextStyle)
{
documentDefaultTextStyle.ApplyGlobalStyle(TextStyle.LibraryDefault);
if (content == null)
return;
content.HandleVisitor(element =>
if (content is TextBlock textBlock)
{
var text = element as TextBlock;
if (text == null)
return;
foreach (var child in text.Children)
foreach (var textBlockItem in textBlock.Items)
{
if (child is TextBlockSpan textSpan)
if (textBlockItem is TextBlockSpan textSpan)
{
textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle);
if (child is TextBlockElement textElement)
}
else if (textBlockItem is TextBlockElement textElement)
{
ApplyDefaultTextStyle(textElement.Element, documentDefaultTextStyle);
}
}
});
return;
}
var targetTextStyle = documentDefaultTextStyle;
if (content is DefaultTextStyle defaultTextStyleElement)
{
defaultTextStyleElement.TextStyle.ApplyParentStyle(documentDefaultTextStyle);
targetTextStyle = defaultTextStyleElement.TextStyle;
}
foreach (var child in content.GetChildren())
ApplyDefaultTextStyle(child, targetTextStyle);
}
}
}

Просмотреть файл

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using QuestPDF.Drawing;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
@ -17,12 +18,10 @@ namespace QuestPDF.Elements
public Element ContentElement { get; set; } = Empty.Instance;
public DecorationType Type { get; set; }
internal override void HandleVisitor(Action<Element?> visit)
internal override IEnumerable<Element?> GetChildren()
{
DecorationElement?.HandleVisitor(visit);
ContentElement?.HandleVisitor(visit);
base.HandleVisitor(visit);
yield return DecorationElement;
yield return ContentElement;
}
internal override void CreateProxy(Func<Element, Element> create)

Просмотреть файл

@ -0,0 +1,10 @@
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class DefaultTextStyle : ContainerElement
{
public TextStyle TextStyle { get; set; } = TextStyle.Default;
}
}

Просмотреть файл

@ -44,12 +44,11 @@ namespace QuestPDF.Elements
ChildrenQueue = new Queue<InlinedElement>(Elements);
}
internal override void HandleVisitor(Action<Element?> visit)
internal override IEnumerable<Element?> GetChildren()
{
Elements.ForEach(x => x.HandleVisitor(visit));
base.HandleVisitor(visit);
return Elements;
}
internal override SpacePlan Measure(Size availableSpace)
{
if (!ChildrenQueue.Any())

Просмотреть файл

@ -15,12 +15,11 @@ namespace QuestPDF.Elements
{
public List<Layer> Children { get; set; } = new List<Layer>();
internal override void HandleVisitor(Action<Element?> visit)
internal override IEnumerable<Element?> GetChildren()
{
Children.ForEach(x => x.HandleVisitor(visit));
base.HandleVisitor(visit);
return Children;
}
internal override SpacePlan Measure(Size availableSpace)
{
return Children

Просмотреть файл

@ -38,14 +38,12 @@ namespace QuestPDF.Elements
IsRightRendered = false;
}
internal override void HandleVisitor(Action<Element?> visit)
internal override IEnumerable<Element?> GetChildren()
{
Left.HandleVisitor(visit);
Right.HandleVisitor(visit);
base.HandleVisitor(visit);
yield return Left;
yield return Right;
}
internal override void CreateProxy(Func<Element?, Element?> create)
{
Left = create(Left);
@ -99,16 +97,15 @@ namespace QuestPDF.Elements
{
public float Spacing { get; set; } = 0;
public ICollection<RowElement> Children { get; internal set; } = new List<RowElement>();
public ICollection<RowElement> Items { get; internal set; } = new List<RowElement>();
private Element? RootElement { get; set; }
internal override void HandleVisitor(Action<Element?> visit)
internal override IEnumerable<Element?> GetChildren()
{
if (RootElement == null)
ComposeTree();
RootElement.HandleVisitor(visit);
base.HandleVisitor(visit);
return Items;
}
internal override SpacePlan Measure(Size availableSpace)
@ -127,20 +124,20 @@ namespace QuestPDF.Elements
private void ComposeTree()
{
Children = AddSpacing(Children, Spacing);
Items = AddSpacing(Items, Spacing);
var elements = Children.Cast<Element>().ToArray();
var elements = Items.Cast<Element>().ToArray();
RootElement = BuildTree(elements);
}
private void UpdateElementsWidth(float availableWidth)
{
var constantWidth = Children.Sum(x => x.ConstantSize);
var relativeWidth = Children.Sum(x => x.RelativeSize);
var constantWidth = Items.Sum(x => x.ConstantSize);
var relativeWidth = Items.Sum(x => x.RelativeSize);
var widthPerRelativeUnit = (relativeWidth > 0) ? (availableWidth - constantWidth) / relativeWidth : 0;
foreach (var row in Children)
foreach (var row in Items)
{
row.SetWidth(row.ConstantSize + row.RelativeSize * widthPerRelativeUnit);
}

Просмотреть файл

@ -16,14 +16,12 @@ namespace QuestPDF.Elements
internal bool IsFirstRendered { get; set; } = false;
internal override void HandleVisitor(Action<Element?> visit)
internal override IEnumerable<Element?> GetChildren()
{
First?.HandleVisitor(visit);
Second?.HandleVisitor(visit);
base.HandleVisitor(visit);
yield return First;
yield return Second;
}
public void ResetState()
{
IsFirstRendered = false;
@ -97,12 +95,12 @@ namespace QuestPDF.Elements
internal class Stack : IComponent
{
public ICollection<Element> Children { get; } = new List<Element>();
public ICollection<Element> Items { get; } = new List<Element>();
public float Spacing { get; set; } = 0;
public void Compose(IContainer container)
{
var elements = AddSpacing(Spacing, Children);
var elements = AddSpacing(Spacing, Items);
container
.PaddingBottom(-Spacing)

Просмотреть файл

@ -12,51 +12,50 @@ namespace QuestPDF.Elements.Table
internal class Table : Element, IStateResettable
{
public List<TableColumnDefinition> Columns { get; } = new List<TableColumnDefinition>();
public List<TableCell> Children { get; } = new List<TableCell>();
public List<TableCell> Cells { get; } = new List<TableCell>();
public bool ExtendLastCellsToTableBottom { get; set; }
// cache for efficient cell finding
// index of first array - number of row
// nested array - collection of all cells starting at given row
private TableCell[][] OrderedChildren { get; set; }
private TableCell[][] OrderedCells { get; set; }
private int StartingRowsCount { get; set; }
private int RowsCount { get; set; }
private int CurrentRow { get; set; }
internal override void HandleVisitor(Action<Element?> visit)
internal override IEnumerable<Element?> GetChildren()
{
Children.ToList().ForEach(x => x.HandleVisitor(visit));
base.HandleVisitor(visit);
return Cells;
}
public void ResetState()
{
if (StartingRowsCount == default)
StartingRowsCount = Children.Select(x => x.Row).DefaultIfEmpty(0).Max();
StartingRowsCount = Cells.Select(x => x.Row).DefaultIfEmpty(0).Max();
if (RowsCount == default)
RowsCount = Children.Select(x => x.Row + x.RowSpan - 1).DefaultIfEmpty(0).Max();
RowsCount = Cells.Select(x => x.Row + x.RowSpan - 1).DefaultIfEmpty(0).Max();
if (OrderedChildren == default)
if (OrderedCells == default)
{
var groups = Children
var groups = Cells
.GroupBy(x => x.Row)
.ToDictionary(x => x.Key, x => x.OrderBy(y => y.Column).ToArray());
OrderedChildren = Enumerable
OrderedCells = Enumerable
.Range(0, RowsCount + 1)
.Select(x => groups.TryGetValue(x, out var output) ? output : Array.Empty<TableCell>())
.ToArray();
}
Children.ForEach(x => x.IsRendered = false);
Cells.ForEach(x => x.IsRendered = false);
CurrentRow = 1;
}
internal override SpacePlan Measure(Size availableSpace)
{
if (!Children.Any())
if (!Cells.Any())
return SpacePlan.FullRender(Size.Zero);
UpdateColumnsWidth(availableSpace.Width);
@ -149,16 +148,16 @@ namespace QuestPDF.Elements.Table
{
var rowBottomOffsets = new DynamicDictionary<int, float>();
var childrenToTry = Enumerable
var cellsToTry = Enumerable
.Range(CurrentRow, RowsCount - CurrentRow + 1)
.SelectMany(x => OrderedChildren[x]);
.SelectMany(x => OrderedCells[x]);
var currentRow = CurrentRow;
var maxRenderingRow = RowsCount;
var commands = new List<TableCellRenderingCommand>();
foreach (var cell in childrenToTry)
foreach (var cell in cellsToTry)
{
// update position of previous row
if (cell.Row > currentRow)

Просмотреть файл

@ -9,7 +9,7 @@ namespace QuestPDF.Elements.Table
{
public static void PlanCellPositions(this Table table)
{
PlanCellPositions(table.Columns.Count, table.Children);
PlanCellPositions(table.Columns.Count, table.Cells);
}
private static void PlanCellPositions(int columnsCount, ICollection<TableCell> cells)

Просмотреть файл

@ -10,7 +10,7 @@ namespace QuestPDF.Elements.Table
{
public static void ValidateCellPositions(this Table table)
{
ValidateCellPositions(table.Columns.Count, table.Children);
ValidateCellPositions(table.Columns.Count, table.Cells);
}
private static void ValidateCellPositions(int columnsCount, ICollection<TableCell> cells)

Просмотреть файл

@ -1,5 +1,6 @@
using QuestPDF.Drawing;
using QuestPDF.Elements.Text.Calculation;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Items
@ -10,8 +11,8 @@ namespace QuestPDF.Elements.Text.Items
public TextMeasurementResult? Measure(TextMeasurementRequest request)
{
Element.HandleVisitor(x => (x as IStateResettable)?.ResetState());
Element.HandleVisitor(x => x.Initialize(request.PageContext, request.Canvas));
Element.VisitChildren(x => (x as IStateResettable)?.ResetState());
Element.VisitChildren(x => x.Initialize(request.PageContext, request.Canvas));
var measurement = Element.Measure(new Size(request.AvailableWidth, Size.Max.Height));
@ -35,8 +36,8 @@ namespace QuestPDF.Elements.Text.Items
public void Draw(TextDrawingRequest request)
{
Element.HandleVisitor(x => (x as IStateResettable)?.ResetState());
Element.HandleVisitor(x => x.Initialize(request.PageContext, request.Canvas));
Element.VisitChildren(x => (x as IStateResettable)?.ResetState());
Element.VisitChildren(x => x.Initialize(request.PageContext, request.Canvas));
request.Canvas.Translate(new Position(0, request.TotalAscent));
Element.Draw(new Size(request.TextSize.Width, -request.TotalAscent));

Просмотреть файл

@ -11,16 +11,16 @@ namespace QuestPDF.Elements.Text
internal class TextBlock : Element, IStateResettable
{
public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
public List<ITextBlockItem> Children { get; set; } = new List<ITextBlockItem>();
public List<ITextBlockItem> Items { get; set; } = new List<ITextBlockItem>();
public string Text => string.Join(" ", Children.Where(x => x is TextBlockSpan).Cast<TextBlockSpan>().Select(x => x.Text));
public string Text => string.Join(" ", Items.Where(x => x is TextBlockSpan).Cast<TextBlockSpan>().Select(x => x.Text));
private Queue<ITextBlockItem> RenderingQueue { get; set; }
private int CurrentElementIndex { get; set; }
public void ResetState()
{
RenderingQueue = new Queue<ITextBlockItem>(Children);
RenderingQueue = new Queue<ITextBlockItem>(Items);
CurrentElementIndex = 0;
}

Просмотреть файл

@ -27,7 +27,7 @@ namespace QuestPDF.Fluent
{
var element = new RowElement(constantWidth, relativeWidth);
Row.Children.Add(element);
Row.Items.Add(element);
return element;
}
}

Просмотреть файл

@ -16,7 +16,7 @@ namespace QuestPDF.Fluent
public IContainer Item()
{
var container = new Container();
Stack.Children.Add(container);
Stack.Items.Add(container);
return container;
}
}

Просмотреть файл

@ -61,13 +61,13 @@ namespace QuestPDF.Fluent
public ITableCellContainer Cell()
{
var cell = new TableCell();
Table.Children.Add(cell);
Table.Cells.Add(cell);
return cell;
}
internal void ApplyDefaultCellStyle()
{
foreach (var cell in Table.Children)
foreach (var cell in Table.Cells)
{
var container = new Container();
DefaultCellStyleFunc(container).Element(cell.Child);

Просмотреть файл

@ -47,7 +47,7 @@ namespace QuestPDF.Fluent
if (!TextBlocks.Any())
TextBlocks.Add(new TextBlock());
TextBlocks.Last().Children.Add(item);
TextBlocks.Last().Items.Add(item);
}
public void Span(string? text, TextStyle? style = null)
@ -73,7 +73,7 @@ namespace QuestPDF.Fluent
.Skip(1)
.Select(x => new TextBlock
{
Children = new List<ITextBlockItem> { x }
Items = new List<ITextBlockItem> { x }
})
.ToList()
.ForEach(TextBlocks.Add);
@ -173,11 +173,8 @@ namespace QuestPDF.Fluent
internal void Compose(IContainer container)
{
TextBlocks.ToList().ForEach(x => x.Alignment = Alignment);
foreach (var textBlockSpan in TextBlocks.SelectMany(x => x.Children).Where(x => x is TextBlockSpan).Cast<TextBlockSpan>())
textBlockSpan.Style.ApplyParentStyle(DefaultStyle);
container.Stack(stack =>
container.DefaultTextStyle(DefaultStyle).Stack(stack =>
{
stack.Spacing(Spacing);

Просмотреть файл

@ -1,8 +1,10 @@
using System;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.RegularExpressions;
using QuestPDF.Infrastructure;
namespace QuestPDF.Helpers
{
@ -41,5 +43,13 @@ namespace QuestPDF.Helpers
{
return Regex.Replace(text, @"([a-z])([A-Z])", "$1 $2");
}
internal static void VisitChildren(this Element? element, Action<Element?> handler)
{
handler(element);
foreach (var child in element.GetChildren().Where(x => x != null))
VisitChildren(child, handler);
}
}
}

Просмотреть файл

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using QuestPDF.Drawing;
using QuestPDF.Elements;
@ -14,10 +15,9 @@ namespace QuestPDF.Infrastructure
set => Child = value as Element;
}
internal override void HandleVisitor(Action<Element?> visit)
internal override IEnumerable<Element?> GetChildren()
{
Child?.HandleVisitor(visit);
base.HandleVisitor(visit);
yield return Child;
}
internal override void CreateProxy(Func<Element?, Element?> create)

Просмотреть файл

@ -10,9 +10,9 @@ namespace QuestPDF.Infrastructure
internal IPageContext PageContext { get; set; }
internal ICanvas Canvas { get; set; }
internal virtual void HandleVisitor(Action<Element?> visit)
internal virtual IEnumerable<Element?> GetChildren()
{
visit(this);
yield break;
}
internal void Initialize(IPageContext pageContext, ICanvas canvas)