Implemented experimental, tree-based simple stack and row

This commit is contained in:
Marcin Ziąbek
2021-05-02 14:40:08 +02:00
parent 779245b260
commit 0d86039c0c
13 changed files with 345 additions and 25 deletions
+12 -12
View File
@@ -45,20 +45,20 @@ namespace QuestPDF.UnitTests
{
row.RelativeColumn(6).Element(childA);
row.RelativeColumn(4).Element(childB);
row.RelativeColumn(2).Element(new Empty());
row.RelativeColumn(2).Element(Empty.Instance);
});
stack.Item().Row(row =>
{
row.RelativeColumn(4).Element(childC);
row.RelativeColumn(2).Element(childD);
row.RelativeColumn(6).Element(new Empty());
row.RelativeColumn(6).Element(Empty.Instance);
});
stack.Item().Row(row =>
{
row.RelativeColumn(8).Element(childE);
row.RelativeColumn(4).Element(new Empty());
row.RelativeColumn(4).Element(Empty.Instance);
});
});
@@ -97,25 +97,25 @@ namespace QuestPDF.UnitTests
{
stack.Item().Row(row =>
{
row.RelativeColumn(1).Element(new Empty());
row.RelativeColumn(1).Element(Empty.Instance);
row.RelativeColumn(6).Element(childA);
row.RelativeColumn(4).Element(childB);
row.RelativeColumn(1).Element(new Empty());
row.RelativeColumn(1).Element(Empty.Instance);
});
stack.Item().Row(row =>
{
row.RelativeColumn(3).Element(new Empty());
row.RelativeColumn(3).Element(Empty.Instance);
row.RelativeColumn(4).Element(childC);
row.RelativeColumn(2).Element(childD);
row.RelativeColumn(3).Element(new Empty());
row.RelativeColumn(3).Element(Empty.Instance);
});
stack.Item().Row(row =>
{
row.RelativeColumn(2).Element(new Empty());
row.RelativeColumn(2).Element(Empty.Instance);
row.RelativeColumn(8).Element(childE);
row.RelativeColumn(2).Element(new Empty());
row.RelativeColumn(2).Element(Empty.Instance);
});
});
@@ -154,21 +154,21 @@ namespace QuestPDF.UnitTests
{
stack.Item().Row(row =>
{
row.RelativeColumn(2).Element(new Empty());
row.RelativeColumn(2).Element(Empty.Instance);
row.RelativeColumn(6).Element(childA);
row.RelativeColumn(4).Element(childB);
});
stack.Item().Row(row =>
{
row.RelativeColumn(6).Element(new Empty());
row.RelativeColumn(6).Element(Empty.Instance);
row.RelativeColumn(4).Element(childC);
row.RelativeColumn(2).Element(childD);
});
stack.Item().Row(row =>
{
row.RelativeColumn(4).Element(new Empty());
row.RelativeColumn(4).Element(Empty.Instance);
row.RelativeColumn(8).Element(childE);
});
});
+3 -3
View File
@@ -5,9 +5,9 @@ namespace QuestPDF.Elements
{
internal class Decoration : IComponent
{
public Element Header { get; set; } = new Empty();
public Element Content { get; set; } = new Empty();
public Element Footer { get; set; } = new Empty();
public Element Header { get; set; } = Empty.Instance;
public Element Content { get; set; } = Empty.Instance;
public Element Footer { get; set; } = Empty.Instance;
public void Compose(IContainer container)
{
+2
View File
@@ -5,6 +5,8 @@ namespace QuestPDF.Elements
{
internal class Empty : Element
{
internal static Empty Instance { get; } = new Empty();
internal override ISpacePlan Measure(Size availableSpace)
{
return new FullRender(Size.Zero);
+3 -3
View File
@@ -5,9 +5,9 @@ namespace QuestPDF.Elements
{
internal class Page : IComponent
{
public Element Header { get; set; } = new Empty();
public Element Content { get; set; } = new Empty();
public Element Footer { get; set; } = new Empty();
public Element Header { get; set; } = Empty.Instance;
public Element Content { get; set; } = Empty.Instance;
public Element Footer { get; set; } = Empty.Instance;
public void Compose(IContainer container)
{
+2 -2
View File
@@ -12,8 +12,8 @@ namespace QuestPDF.Elements
internal class SimpleDecoration : Element
{
public Element DecorationElement { get; set; } = new Empty();
public Element ContentElement { get; set; } = new Empty();
public Element DecorationElement { get; set; } = Empty.Instance;
public Element ContentElement { get; set; } = Empty.Instance;
public DecorationType Type { get; set; }
internal override ISpacePlan Measure(Size availableSpace)
+47
View File
@@ -0,0 +1,47 @@
using System;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class SimpleRow : Element
{
internal Element Left { get; set; }
internal Element Right { get; set; }
internal override ISpacePlan Measure(Size availableSpace)
{
var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height)) as Size;
if (leftMeasurement == null)
return new Wrap();
var rightMeasurement = Right.Measure(new Size(availableSpace.Width - leftMeasurement.Width, availableSpace.Height)) as Size;
if (rightMeasurement == null)
return new Wrap();
var totalWidth = leftMeasurement.Width + rightMeasurement.Width;
var totalHeight = Math.Max(leftMeasurement.Height, rightMeasurement.Height);
var targetSize = new Size(totalWidth, totalHeight);
if (leftMeasurement is PartialRender || rightMeasurement is PartialRender)
return new PartialRender(targetSize);
return new FullRender(targetSize);
}
internal override void Draw(ICanvas canvas, Size availableSpace)
{
var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height));
var leftWidth = (leftMeasurement as Size)?.Width ?? 0;
Left.Draw(canvas, new Size(leftWidth, availableSpace.Height));
canvas.Translate(new Position(leftWidth, 0));
Right.Draw(canvas, new Size(availableSpace.Width - leftWidth, availableSpace.Height));
canvas.Translate(new Position(-leftWidth, 0));
}
}
}
+71
View File
@@ -0,0 +1,71 @@
using System;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class SimpleStack : Element
{
internal Element Current { get; set; } = Empty.Instance;
internal Element Next { get; set; } = Empty.Instance;
private bool IsFirstRendered { get; set; } = false;
internal override ISpacePlan Measure(Size availableSpace)
{
var firstElement = IsFirstRendered ? Empty.Instance : Current;
var firstSize = firstElement.Measure(availableSpace) as Size;
if (firstSize == null)
return new Wrap();
if (firstSize is PartialRender partialRender)
return partialRender;
var spaceForSecond = new Size(availableSpace.Width, availableSpace.Height - firstSize.Height);
var secondSize = Next.Measure(spaceForSecond) as Size;
if (secondSize == null)
return new PartialRender(firstSize);
var totalWidth = Math.Max(firstSize.Width, secondSize.Width);
var totalHeight = firstSize.Height + secondSize.Height;
var targetSize = new Size(totalWidth, totalHeight);
if (secondSize is PartialRender)
return new PartialRender(targetSize);
return new FullRender(targetSize);
}
internal override void Draw(ICanvas canvas, Size availableSpace)
{
var firstElement = IsFirstRendered ? Empty.Instance : Current;
var firstMeasurement = firstElement.Measure(availableSpace);
if (firstMeasurement is FullRender)
IsFirstRendered = true;
var firstSize = firstMeasurement as Size;
if (firstSize != null)
firstElement.Draw(canvas, firstSize);
var firstHeight = firstSize?.Height ?? 0;
var spaceForSecond = new Size(availableSpace.Width, availableSpace.Height - firstHeight);
var secondMeasurement = Next?.Measure(spaceForSecond);
if (secondMeasurement is Wrap)
return;
canvas.Translate(new Position(0, firstHeight));
Next.Draw(canvas, secondMeasurement as Size);
canvas.Translate(new Position(0, -firstHeight));
}
}
}
+29
View File
@@ -0,0 +1,29 @@
using System;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class SimpleStackWithCache : SimpleStack
{
private ISpacePlan? Cache { get; set; }
private Size? CacheForSize { get; set; }
internal override ISpacePlan Measure(Size availableSpace)
{
if (Cache != null && CacheForSize != null && CacheForSize.Equals(availableSpace))
return Cache;
CacheForSize = availableSpace;
Cache = base.Measure(availableSpace);
return Cache;
}
internal override void Draw(ICanvas canvas, Size availableSpace)
{
base.Draw(canvas, availableSpace);
Cache = null;
CacheForSize = null;
}
}
}
+94
View File
@@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class TreeRow : Element
{
public ICollection<RowElement> Children { get; internal set; } = new List<RowElement>();
public float Spacing { get; set; } = 0;
public Element Compose(float availableWidth)
{
var elements = ReduceRows(AddSpacing(Children));
return BuildTree(elements.ToArray());
ICollection<Element> ReduceRows(ICollection<RowElement> elements)
{
var constantWidth = elements
.Where(x => x is ConstantRowElement)
.Cast<ConstantRowElement>()
.Sum(x => x.Width);
var relativeWidth = elements
.Where(x => x is RelativeRowElement)
.Cast<RelativeRowElement>()
.Sum(x => x.Width);
var widthPerRelativeUnit = (availableWidth - constantWidth) / relativeWidth;
return elements
.Select(x =>
{
if (x is RelativeRowElement r)
return new ConstantRowElement(r.Width * widthPerRelativeUnit)
{
Child = x.Child
};
return x;
})
.Select(x => new Constrained
{
MinWidth = x.Width,
MaxWidth = x.Width,
Child = x.Child
})
.Cast<Element>()
.ToList();
}
ICollection<RowElement> AddSpacing(ICollection<RowElement> elements)
{
if (Spacing < Size.Epsilon)
return elements;
return elements
.SelectMany(x => new[] { new ConstantRowElement(Spacing), x })
.Skip(1)
.ToList();
}
Element BuildTree(Span<Element> elements)
{
if (elements.IsEmpty)
return Empty.Instance;
if (elements.Length == 1)
return elements[0];
var half = elements.Length / 2;
return new SimpleRow
{
Left = BuildTree(elements.Slice(0, half)),
Right = BuildTree(elements.Slice(half))
};
}
}
internal override ISpacePlan Measure(Size availableSpace)
{
return Compose(availableSpace.Width).Measure(availableSpace);
}
internal override void Draw(ICanvas canvas, Size availableSpace)
{
Compose(availableSpace.Width).Draw(canvas, availableSpace);
}
}
}
+58
View File
@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
using IComponent = QuestPDF.Infrastructure.IComponent;
using IContainer = QuestPDF.Infrastructure.IContainer;
namespace QuestPDF.Elements
{
internal class TreeStack : IComponent
{
public ICollection<Element> Children { get; internal set; } = new List<Element>();
public float Spacing { get; set; } = 0;
public void Compose(IContainer container)
{
var elements = AddSpacing(Children);
container
.PaddingBottom(-Spacing)
.Element(BuildTree(elements.ToArray()));
ICollection<Element> AddSpacing(ICollection<Element> elements)
{
if (Spacing < Size.Epsilon)
return elements;
return elements
.Select(x => new Padding
{
Bottom = Spacing,
Child = x
})
.Cast<Element>()
.ToList();
}
Element BuildTree(Span<Element> elements)
{
if (elements.IsEmpty)
return Empty.Instance;
if (elements.Length == 1)
return elements[0];
var half = elements.Length / 2;
return new SimpleStack
{
Current = BuildTree(elements.Slice(0, half)),
Next = BuildTree(elements.Slice(half))
};
}
}
}
}
+11 -1
View File
@@ -33,9 +33,18 @@ namespace QuestPDF.Fluent
}
internal Element CreateRow()
{
return new TreeRow
{
Children = Elements,
Spacing = RowSpacing
};
}
internal Element CreateRow2()
{
if (Elements.Count == 0)
return new Empty();
return Empty.Instance;
if (RowSpacing <= Size.Epsilon)
return new Row
@@ -67,6 +76,7 @@ namespace QuestPDF.Fluent
var descriptor = new RowDescriptor();
handler(descriptor);
element.Element(descriptor.CreateRow());
//element.Element(descriptor.CreateRow2());
}
}
}
+12 -3
View File
@@ -28,10 +28,19 @@ namespace QuestPDF.Fluent
handler?.Invoke(Item());
}
internal Element CreateStack()
internal IComponent CreateStack()
{
return new TreeStack
{
Children = Items,
Spacing = StackSpacing
};
}
internal Element CreateStack2()
{
if (Items.Count == 0)
return new Empty();
return Empty.Instance;
if (StackSpacing <= Size.Epsilon)
return new Stack
@@ -67,7 +76,7 @@ namespace QuestPDF.Fluent
{
var descriptor = new StackDescriptor();
handler(descriptor);
element.Element(descriptor.CreateStack());
element.Component(descriptor.CreateStack());
}
}
}
+1 -1
View File
@@ -5,7 +5,7 @@ namespace QuestPDF.Infrastructure
{
internal abstract class ContainerElement : Element, IContainer
{
internal Element? Child { get; set; } = new Empty();
internal Element? Child { get; set; } = Empty.Instance;
IElement? IContainer.Child
{