API improvements

This commit is contained in:
Marcin Ziąbek
2021-10-22 01:38:08 +02:00
parent d3e8386418
commit fc33056434
6 changed files with 187 additions and 110 deletions
+106 -51
View File
@@ -9,68 +9,123 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class TableWithSubtotals : IDynamic
public class OrderItem
{
private ICollection<int> Values { get; }
private Queue<int> ValuesQueue { get; set; }
public TableWithSubtotals(ICollection<int> values)
public string ItemName { get; set; } = Placeholders.Label();
public int Price { get; set; } = Placeholders.Random.Next(1, 11) * 10;
public int Count { get; set; } = Placeholders.Random.Next(1, 11);
}
public class OrdersTable : IDynamicComponent
{
private ICollection<OrderItem> Items { get; }
private ICollection<OrderItem> ItemsLeft { get; set; }
public OrdersTable(ICollection<OrderItem> items)
{
Values = values;
Items = items;
}
public void Reset()
public void Compose(DynamicContext context, IDynamicContainer container)
{
ValuesQueue = new Queue<int>(Values);
if (context.Operation == DynamicLayoutOperation.Reset)
{
ItemsLeft = new List<OrderItem>(Items);
return;
}
var header = ComposeHeader(context);
var sampleFooter = ComposeFooter(context, Enumerable.Empty<OrderItem>());
var decorationHeight = header.Size.Height + sampleFooter.Size.Height;
var rows = GetItemsForPage(context, decorationHeight).ToList();
var footer = ComposeFooter(context, rows.Select(x => x.Item));
if (ItemsLeft.Count > rows.Count)
container.HasMoreContent();
if (context.Operation == DynamicLayoutOperation.Draw)
ItemsLeft = ItemsLeft.Skip(rows.Count).ToList();
container.Box().Decoration(decoration =>
{
decoration.Header().Element(header);
decoration.Content().Box().Stack(stack =>
{
foreach (var row in rows)
stack.Item().Element(row.Element);
});
decoration.Footer().Element(footer);
});
}
public bool Compose(DynamicContext context, IContainer container)
private IDynamicElement ComposeHeader(DynamicContext context)
{
var internalQueue = new Queue<int>(ValuesQueue);
container.Box().Border(2).Background(Colors.Grey.Lighten3).Stack(stack =>
return context.CreateElement(element =>
{
var summaryHeight = 40f;
var totalHeight = summaryHeight;
var total = 0;
while (internalQueue.Any())
{
var value = internalQueue.Peek();
var structure = context.Content(content =>
element
.BorderBottom(1)
.BorderColor(Colors.Grey.Darken2)
.Padding(5)
.Row(row =>
{
content
.Padding(10)
.Text(value);
var textStyle = TextStyle.Default.SemiBold();
row.ConstantColumn(30).Text("#", textStyle);
row.RelativeColumn().Text("Item name", textStyle);
row.ConstantColumn(50).AlignRight().Text("Count", textStyle);
row.ConstantColumn(50).AlignRight().Text("Price", textStyle);
row.ConstantColumn(50).AlignRight().Text("Total", textStyle);
});
var structureHeight = structure.Measure().Height;
if (totalHeight + structureHeight > context.AvailableSize.Height)
break;
totalHeight += structureHeight;
total += value;
stack.Item().Border(1).Element(structure);
internalQueue.Dequeue();
}
stack
.Item()
.ShowEntire()
.Border(2)
.Background(Colors.Grey.Lighten1)
.Padding(10)
.Text($"Total: {total}", TextStyle.Default.SemiBold());
});
}
private IDynamicElement ComposeFooter(DynamicContext context, IEnumerable<OrderItem> items)
{
var total = items.Sum(x => x.Count * x.Price);
if (context.IsDrawStep)
ValuesQueue = internalQueue;
return context.CreateElement(element =>
{
element
.Padding(5)
.AlignRight()
.Text($"Subtotal: {total}$", TextStyle.Default.Size(14).SemiBold());
});
}
private IEnumerable<(OrderItem Item, IDynamicElement Element)> GetItemsForPage(DynamicContext context, float decorationHeight)
{
var totalHeight = decorationHeight;
var counter = Items.Count - ItemsLeft.Count + 1;
return internalQueue.Any();
foreach (var orderItem in ItemsLeft)
{
var element = context.CreateElement(content =>
{
content
.BorderBottom(1)
.BorderColor(Colors.Grey.Lighten2)
.Padding(5)
.Row(row =>
{
row.ConstantColumn(30).Text(counter++);
row.RelativeColumn().Text(orderItem.ItemName);
row.ConstantColumn(50).AlignRight().Text(orderItem.Count);
row.ConstantColumn(50).AlignRight().Text($"{orderItem.Price}$");
row.ConstantColumn(50).AlignRight().Text($"{orderItem.Count*orderItem.Price}$");
});
});
var elementHeight = element.Size.Height;
if (totalHeight + elementHeight > context.AvailableSize.Height)
break;
totalHeight += elementHeight;
yield return (orderItem, element);
}
}
}
@@ -81,12 +136,12 @@ namespace QuestPDF.Examples
{
RenderingTest
.Create()
.PageSize(300, 500)
.PageSize(PageSizes.A5)
.FileName()
.ShowResults()
.Render(container =>
{
var values = Enumerable.Range(0, 15).ToList();
var items = Enumerable.Range(0, 25).Select(x => new OrderItem()).ToList();
container
.Background(Colors.White)
@@ -107,7 +162,7 @@ namespace QuestPDF.Examples
decoration
.Content()
.Dynamic(new TableWithSubtotals(values));
.Dynamic(new OrdersTable(items));
});
});
}
+69 -46
View File
@@ -1,72 +1,56 @@
using System;
using QuestPDF.Drawing;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class DynamicHost : Element, IStateResettable
{
private IDynamic Child { get; }
private IDynamicComponent Child { get; }
public DynamicHost(IDynamic child)
public DynamicHost(IDynamicComponent child)
{
Child = child;
}
public void ResetState()
{
Child.Reset();
GetContent(Size.Zero, DynamicLayoutOperation.Reset);
}
internal override SpacePlan Measure(Size availableSpace)
{
var content = GetContent(availableSpace, false);
var measurement = content.element.Measure(availableSpace);
var content = GetContent(availableSpace, DynamicLayoutOperation.Measure);
var measurement = content.Measure(availableSpace);
if (measurement.Type == SpacePlanType.FullRender)
return content.hasMore ? SpacePlan.PartialRender(measurement) : measurement;
return measurement;
if (measurement.Type != SpacePlanType.FullRender)
throw new DocumentLayoutException("Dynamic component generated content that does not fit on a single page.");
return content.HasMoreContent
? SpacePlan.PartialRender(measurement)
: SpacePlan.FullRender(measurement);
}
internal override void Draw(Size availableSpace)
{
GetContent(availableSpace, true).element.Draw(availableSpace);
GetContent(availableSpace, DynamicLayoutOperation.Draw).Draw(availableSpace);
}
(Element element, bool hasMore) GetContent(Size availableSize, bool isDrawState)
DynamicContainer GetContent(Size availableSize, DynamicLayoutOperation operation)
{
var context = new DynamicContext
{
PageNumber = PageContext.GetLocationPage(Infrastructure.PageContext.CurrentPageSlot),
PageContext = PageContext,
Canvas = Canvas,
AvailableSize = availableSize,
IsDrawStep = isDrawState
Operation = operation
};
var container = new Container();
var hasMore = Child.Compose(context, container);
container.HandleVisitor(x => x?.Initialize(PageContext, Canvas));
container.HandleVisitor(x => (x as IStateResettable)?.ResetState());
return (container, hasMore);
}
}
public class DynamicContext
{
internal IPageContext PageContext { get; set; }
internal ICanvas Canvas { get; set; }
public Size AvailableSize { get; internal set; }
public bool IsDrawStep { get; internal set; }
public IDynamicElement Content(Action<IContainer> content)
{
var container = new DynamicElement(() => AvailableSize);
content(container);
var container = new DynamicContainer();
Child.Compose(context, container);
container.HandleVisitor(x => x?.Initialize(PageContext, Canvas));
container.HandleVisitor(x => (x as IStateResettable)?.ResetState());
@@ -75,23 +59,62 @@ namespace QuestPDF.Elements
}
}
public enum DynamicLayoutOperation
{
Reset,
Measure,
Draw
}
public class DynamicContext
{
internal IPageContext PageContext { get; set; }
internal ICanvas Canvas { get; set; }
public int PageNumber { get; internal set; }
public Size AvailableSize { get; internal set; }
public DynamicLayoutOperation Operation { get; internal set; }
public IDynamicElement CreateElement(Action<IContainer> content)
{
var container = new DynamicElement();
content(container);
container.HandleVisitor(x => x?.Initialize(PageContext, Canvas));
container.HandleVisitor(x => (x as IStateResettable)?.ResetState());
container.Size = container.Measure(AvailableSize);
return container;
}
}
public interface IDynamicContainer : IContainer
{
}
internal class DynamicContainer : Container, IDynamicContainer
{
internal bool HasMoreContent { get; set; }
}
public static class DynamicContainerExtensions
{
public static IDynamicContainer HasMoreContent(this IDynamicContainer container)
{
(container as DynamicContainer).HasMoreContent = true;
return container;
}
}
public interface IDynamicElement : IElement
{
Size Measure();
Size Size { get; }
}
internal class DynamicElement : ContainerElement, IDynamicElement
{
private Func<Size> AvailableSizeSource { get; }
public DynamicElement(Func<Size> availableSizeSource)
{
AvailableSizeSource = availableSizeSource;
}
Size IDynamicElement.Measure()
{
return Measure(AvailableSizeSource());
}
public Size Size { get; internal set; }
}
}
+2 -2
View File
@@ -41,12 +41,12 @@ namespace QuestPDF.Fluent
return handler(parent.Container()).Container();
}
public static void Dynamic<TDynamic>(this IContainer element) where TDynamic : IDynamic, new()
public static void Dynamic<TDynamic>(this IContainer element) where TDynamic : IDynamicComponent, new()
{
element.Dynamic(new TDynamic());
}
public static void Dynamic(this IContainer element, IDynamic dynamicElement)
public static void Dynamic(this IContainer element, IDynamicComponent dynamicElement)
{
element.Element(new DynamicHost(dynamicElement));
}
+1 -1
View File
@@ -7,7 +7,7 @@ namespace QuestPDF.Helpers
{
public static class Placeholders
{
private static Random Random = new Random();
public static readonly Random Random = new Random();
#region Word Cache
-10
View File
@@ -1,10 +0,0 @@
using QuestPDF.Elements;
namespace QuestPDF.Infrastructure
{
public interface IDynamic
{
void Reset();
bool Compose(DynamicContext context, IContainer container);
}
}
@@ -0,0 +1,9 @@
using QuestPDF.Elements;
namespace QuestPDF.Infrastructure
{
public interface IDynamicComponent
{
void Compose(DynamicContext context, IDynamicContainer container);
}
}