Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fc33056434 | |||
| d3e8386418 |
@@ -0,0 +1,170 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class OrderItem
|
||||
{
|
||||
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)
|
||||
{
|
||||
Items = items;
|
||||
}
|
||||
|
||||
public void Compose(DynamicContext context, IDynamicContainer container)
|
||||
{
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
private IDynamicElement ComposeHeader(DynamicContext context)
|
||||
{
|
||||
return context.CreateElement(element =>
|
||||
{
|
||||
element
|
||||
.BorderBottom(1)
|
||||
.BorderColor(Colors.Grey.Darken2)
|
||||
.Padding(5)
|
||||
.Row(row =>
|
||||
{
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private IDynamicElement ComposeFooter(DynamicContext context, IEnumerable<OrderItem> items)
|
||||
{
|
||||
var total = items.Sum(x => x.Count * x.Price);
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class DynamicExamples
|
||||
{
|
||||
[Test]
|
||||
public static void Dynamic()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A5)
|
||||
.FileName()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
var items = Enumerable.Range(0, 25).Select(x => new OrderItem()).ToList();
|
||||
|
||||
container
|
||||
.Background(Colors.White)
|
||||
.Padding(25)
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
decoration
|
||||
.Header()
|
||||
.PaddingBottom(5)
|
||||
.Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(TextStyle.Default.SemiBold().Color(Colors.Blue.Darken2).Size(16));
|
||||
text.Span("Page ");
|
||||
text.CurrentPageNumber();
|
||||
text.Span(" of ");
|
||||
text.TotalPages();
|
||||
});
|
||||
|
||||
decoration
|
||||
.Content()
|
||||
.Dynamic(new OrdersTable(items));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,7 +125,7 @@ namespace QuestPDF.Drawing
|
||||
|
||||
content.HandleVisitor(x =>
|
||||
{
|
||||
x.CreateProxy(y => new DebuggingProxy(debuggingState, x));
|
||||
x.CreateProxy(y => new DebuggingProxy(debuggingState, y));
|
||||
});
|
||||
|
||||
return debuggingState;
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class DynamicHost : Element, IStateResettable
|
||||
{
|
||||
private IDynamicComponent Child { get; }
|
||||
|
||||
public DynamicHost(IDynamicComponent child)
|
||||
{
|
||||
Child = child;
|
||||
}
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
GetContent(Size.Zero, DynamicLayoutOperation.Reset);
|
||||
}
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
var content = GetContent(availableSpace, DynamicLayoutOperation.Measure);
|
||||
var measurement = content.Measure(availableSpace);
|
||||
|
||||
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, DynamicLayoutOperation.Draw).Draw(availableSpace);
|
||||
}
|
||||
|
||||
DynamicContainer GetContent(Size availableSize, DynamicLayoutOperation operation)
|
||||
{
|
||||
var context = new DynamicContext
|
||||
{
|
||||
PageNumber = PageContext.GetLocationPage(Infrastructure.PageContext.CurrentPageSlot),
|
||||
PageContext = PageContext,
|
||||
Canvas = Canvas,
|
||||
|
||||
AvailableSize = availableSize,
|
||||
Operation = operation
|
||||
};
|
||||
|
||||
var container = new DynamicContainer();
|
||||
Child.Compose(context, container);
|
||||
|
||||
container.HandleVisitor(x => x?.Initialize(PageContext, Canvas));
|
||||
container.HandleVisitor(x => (x as IStateResettable)?.ResetState());
|
||||
|
||||
return container;
|
||||
}
|
||||
}
|
||||
|
||||
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 Size { get; }
|
||||
}
|
||||
|
||||
internal class DynamicElement : ContainerElement, IDynamicElement
|
||||
{
|
||||
public Size Size { get; internal set; }
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,16 @@ namespace QuestPDF.Fluent
|
||||
return handler(parent.Container()).Container();
|
||||
}
|
||||
|
||||
public static void Dynamic<TDynamic>(this IContainer element) where TDynamic : IDynamicComponent, new()
|
||||
{
|
||||
element.Dynamic(new TDynamic());
|
||||
}
|
||||
|
||||
public static void Dynamic(this IContainer element, IDynamicComponent dynamicElement)
|
||||
{
|
||||
element.Element(new DynamicHost(dynamicElement));
|
||||
}
|
||||
|
||||
public static IContainer AspectRatio(this IContainer element, float ratio, AspectRatioOption option = AspectRatioOption.FitWidth)
|
||||
{
|
||||
return element.Element(new AspectRatio
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -28,10 +28,5 @@ namespace QuestPDF.Infrastructure
|
||||
|
||||
internal abstract SpacePlan Measure(Size availableSpace);
|
||||
internal abstract void Draw(Size availableSpace);
|
||||
|
||||
protected virtual IEnumerable<string> GetDebugInformation()
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using QuestPDF.Elements;
|
||||
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
public interface IDynamicComponent
|
||||
{
|
||||
void Compose(DynamicContext context, IDynamicContainer container);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user