From d3e83864183ced806451e865a6c8cc6cbc7e3160 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Zi=C4=85bek?= Date: Tue, 19 Oct 2021 01:40:56 +0200 Subject: [PATCH] Dynamic element prototype --- QuestPDF.Examples/DynamicExamples.cs | 115 ++++++++++++++++++++++++++ QuestPDF/Drawing/DocumentGenerator.cs | 2 +- QuestPDF/Elements/Dynamic.cs | 97 ++++++++++++++++++++++ QuestPDF/Fluent/ElementExtensions.cs | 10 +++ QuestPDF/Infrastructure/Element.cs | 5 -- QuestPDF/Infrastructure/IDynamic.cs | 10 +++ 6 files changed, 233 insertions(+), 6 deletions(-) create mode 100644 QuestPDF.Examples/DynamicExamples.cs create mode 100644 QuestPDF/Elements/Dynamic.cs create mode 100644 QuestPDF/Infrastructure/IDynamic.cs diff --git a/QuestPDF.Examples/DynamicExamples.cs b/QuestPDF.Examples/DynamicExamples.cs new file mode 100644 index 0000000..c03ef65 --- /dev/null +++ b/QuestPDF.Examples/DynamicExamples.cs @@ -0,0 +1,115 @@ +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 TableWithSubtotals : IDynamic + { + private ICollection Values { get; } + private Queue ValuesQueue { get; set; } + + public TableWithSubtotals(ICollection values) + { + Values = values; + } + + public void Reset() + { + ValuesQueue = new Queue(Values); + } + + public bool Compose(DynamicContext context, IContainer container) + { + var internalQueue = new Queue(ValuesQueue); + + container.Box().Border(2).Background(Colors.Grey.Lighten3).Stack(stack => + { + var summaryHeight = 40f; + + var totalHeight = summaryHeight; + var total = 0; + + while (internalQueue.Any()) + { + var value = internalQueue.Peek(); + + var structure = context.Content(content => + { + content + .Padding(10) + .Text(value); + }); + + 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()); + }); + + if (context.IsDrawStep) + ValuesQueue = internalQueue; + + return internalQueue.Any(); + } + } + + public static class DynamicExamples + { + [Test] + public static void Dynamic() + { + RenderingTest + .Create() + .PageSize(300, 500) + .FileName() + .ShowResults() + .Render(container => + { + var values = Enumerable.Range(0, 15).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 TableWithSubtotals(values)); + }); + }); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Drawing/DocumentGenerator.cs b/QuestPDF/Drawing/DocumentGenerator.cs index f116137..08a2a8e 100644 --- a/QuestPDF/Drawing/DocumentGenerator.cs +++ b/QuestPDF/Drawing/DocumentGenerator.cs @@ -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; diff --git a/QuestPDF/Elements/Dynamic.cs b/QuestPDF/Elements/Dynamic.cs new file mode 100644 index 0000000..ce8122e --- /dev/null +++ b/QuestPDF/Elements/Dynamic.cs @@ -0,0 +1,97 @@ +using System; +using QuestPDF.Drawing; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class DynamicHost : Element, IStateResettable + { + private IDynamic Child { get; } + + public DynamicHost(IDynamic child) + { + Child = child; + } + + public void ResetState() + { + Child.Reset(); + } + + internal override SpacePlan Measure(Size availableSpace) + { + var content = GetContent(availableSpace, false); + var measurement = content.element.Measure(availableSpace); + + if (measurement.Type == SpacePlanType.FullRender) + return content.hasMore ? SpacePlan.PartialRender(measurement) : measurement; + + return measurement; + } + + internal override void Draw(Size availableSpace) + { + GetContent(availableSpace, true).element.Draw(availableSpace); + } + + (Element element, bool hasMore) GetContent(Size availableSize, bool isDrawState) + { + var context = new DynamicContext + { + PageContext = PageContext, + Canvas = Canvas, + + AvailableSize = availableSize, + IsDrawStep = isDrawState + }; + + 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 content) + { + var container = new DynamicElement(() => AvailableSize); + content(container); + + container.HandleVisitor(x => x?.Initialize(PageContext, Canvas)); + container.HandleVisitor(x => (x as IStateResettable)?.ResetState()); + + return container; + } + } + + public interface IDynamicElement : IElement + { + Size Measure(); + } + + internal class DynamicElement : ContainerElement, IDynamicElement + { + private Func AvailableSizeSource { get; } + + public DynamicElement(Func availableSizeSource) + { + AvailableSizeSource = availableSizeSource; + } + + Size IDynamicElement.Measure() + { + return Measure(AvailableSizeSource()); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Fluent/ElementExtensions.cs b/QuestPDF/Fluent/ElementExtensions.cs index f41d050..99d7566 100644 --- a/QuestPDF/Fluent/ElementExtensions.cs +++ b/QuestPDF/Fluent/ElementExtensions.cs @@ -41,6 +41,16 @@ namespace QuestPDF.Fluent return handler(parent.Container()).Container(); } + public static void Dynamic(this IContainer element) where TDynamic : IDynamic, new() + { + element.Dynamic(new TDynamic()); + } + + public static void Dynamic(this IContainer element, IDynamic dynamicElement) + { + element.Element(new DynamicHost(dynamicElement)); + } + public static IContainer AspectRatio(this IContainer element, float ratio, AspectRatioOption option = AspectRatioOption.FitWidth) { return element.Element(new AspectRatio diff --git a/QuestPDF/Infrastructure/Element.cs b/QuestPDF/Infrastructure/Element.cs index a77f3b8..55b56c5 100644 --- a/QuestPDF/Infrastructure/Element.cs +++ b/QuestPDF/Infrastructure/Element.cs @@ -28,10 +28,5 @@ namespace QuestPDF.Infrastructure internal abstract SpacePlan Measure(Size availableSpace); internal abstract void Draw(Size availableSpace); - - protected virtual IEnumerable GetDebugInformation() - { - yield break; - } } } \ No newline at end of file diff --git a/QuestPDF/Infrastructure/IDynamic.cs b/QuestPDF/Infrastructure/IDynamic.cs new file mode 100644 index 0000000..1208634 --- /dev/null +++ b/QuestPDF/Infrastructure/IDynamic.cs @@ -0,0 +1,10 @@ +using QuestPDF.Elements; + +namespace QuestPDF.Infrastructure +{ + public interface IDynamic + { + void Reset(); + bool Compose(DynamicContext context, IContainer container); + } +} \ No newline at end of file