Implemented experimental, tree-based simple stack and row
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user