From 09e642295fd6afa5e50472734c69a1e88ce010e2 Mon Sep 17 00:00:00 2001 From: MarcinZiabek Date: Fri, 28 Oct 2022 23:22:11 +0200 Subject: [PATCH] Repeat content experiment --- QuestPDF.Examples/RepeatContentExamples.cs | 38 ++++++++++++++++ QuestPDF/Drawing/DocumentGenerator.cs | 51 +++++++++++++++++++++- QuestPDF/Elements/Canvas.cs | 17 ++++++-- QuestPDF/Elements/DynamicImage.cs | 20 +++++++-- QuestPDF/Elements/Image.cs | 16 +++++-- QuestPDF/Elements/Line.cs | 20 ++++++--- QuestPDF/Elements/RepeatContentSetter.cs | 9 ++++ QuestPDF/Elements/Text/TextBlock.cs | 24 ++++++++-- QuestPDF/Fluent/DecorationExtensions.cs | 12 ++--- QuestPDF/Fluent/LayerExtensions.cs | 2 +- QuestPDF/Fluent/LineExtensions.cs | 12 ++--- QuestPDF/Fluent/RepeatContentExtensions.cs | 27 ++++++++++++ QuestPDF/Infrastructure/IVisual.cs | 8 ++++ 13 files changed, 217 insertions(+), 39 deletions(-) create mode 100644 QuestPDF.Examples/RepeatContentExamples.cs create mode 100644 QuestPDF/Elements/RepeatContentSetter.cs create mode 100644 QuestPDF/Fluent/RepeatContentExtensions.cs create mode 100644 QuestPDF/Infrastructure/IVisual.cs diff --git a/QuestPDF.Examples/RepeatContentExamples.cs b/QuestPDF.Examples/RepeatContentExamples.cs new file mode 100644 index 0000000..142f419 --- /dev/null +++ b/QuestPDF.Examples/RepeatContentExamples.cs @@ -0,0 +1,38 @@ +using System.Linq; +using NUnit.Framework; +using QuestPDF.Examples.Engine; +using QuestPDF.Fluent; +using QuestPDF.Helpers; + +namespace QuestPDF.Examples +{ + public class RepeatContentExamples + { + [Test] + public void ItemTypes() + { + RenderingTest + .Create() + .ProducePdf() + .PageSize(PageSizes.A4) + .ShowResults() + .Render(container => + { + container + .Padding(25) + .Decoration(decoration => + { + decoration.Before().Text("Test").FontSize(22); + + decoration.Content().Column(column => + { + column.Spacing(20); + + foreach (var _ in Enumerable.Range(0, 10)) + column.Item().Background(Colors.Grey.Medium).ExtendHorizontal().Height(80); + }); + }); + }); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Drawing/DocumentGenerator.cs b/QuestPDF/Drawing/DocumentGenerator.cs index b64baac..39af704 100644 --- a/QuestPDF/Drawing/DocumentGenerator.cs +++ b/QuestPDF/Drawing/DocumentGenerator.cs @@ -65,13 +65,14 @@ namespace QuestPDF.Drawing document.Compose(container); var content = container.Compose(); ApplyDefaultTextStyle(content, TextStyle.LibraryDefault); - + ApplyContentRepeatState(content, false); + var debuggingState = Settings.EnableDebugging ? ApplyDebugging(content) : null; if (Settings.EnableCaching) ApplyCaching(content); - var pageContext = new PageContext(); + var pageContext = new PageContext(); RenderPass(pageContext, new FreeCanvas(), content, debuggingState); RenderPass(pageContext, canvas, content, debuggingState); } @@ -81,6 +82,8 @@ namespace QuestPDF.Drawing { content.VisitChildren(x => x?.Initialize(pageContext, canvas)); content.VisitChildren(x => (x as IStateResettable)?.ResetState()); + + ResetIsRenderedState(content); canvas.BeginDocument(); @@ -160,6 +163,50 @@ namespace QuestPDF.Drawing return debuggingState; } + + private static void ApplyContentRepeatState(Element? content, bool repeatContent) + { + if (content == null) + return; + + if (content is IVisual visual) + visual.RepeatContent = repeatContent; + + if (content is TextBlock textBlock) + { + foreach (var textBlockItem in textBlock.Items) + { + if (textBlockItem is TextBlockElement textElement) + { + ApplyContentRepeatState(textElement.Element, true); + } + } + + return; + } + + // TODO: apply RepeatState in dynamic content + //if (content is DynamicHost dynamicHost) + // dynamicHost.TextStyle = dynamicHost.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle); + + if (content is RepeatContentSetter repeatContentSetter) + repeatContent = repeatContentSetter.RepeatContent; + + foreach (var child in content.GetChildren()) + ApplyContentRepeatState(child, repeatContent); + } + + private static void ResetIsRenderedState(Element? content) + { + if (content == null) + return; + + if (content is IVisual visual) + visual.IsRendered = false; + + foreach (var child in content.GetChildren()) + ResetIsRenderedState(child); + } internal static void ApplyDefaultTextStyle(this Element? content, TextStyle documentDefaultTextStyle) { diff --git a/QuestPDF/Elements/Canvas.cs b/QuestPDF/Elements/Canvas.cs index 0d25db0..0f84c2a 100644 --- a/QuestPDF/Elements/Canvas.cs +++ b/QuestPDF/Elements/Canvas.cs @@ -7,19 +7,28 @@ namespace QuestPDF.Elements { public delegate void DrawOnCanvas(SKCanvas canvas, Size availableSpace); - internal class Canvas : Element, ICacheable + internal class Canvas : Element, IVisual, ICacheable { + public bool IsRendered { get; set; } + public bool RepeatContent { get; set; } + public DrawOnCanvas Handler { get; set; } internal override SpacePlan Measure(Size availableSpace) { - return availableSpace.IsNegative() - ? SpacePlan.Wrap() - : SpacePlan.FullRender(availableSpace); + if (availableSpace.IsNegative()) + return SpacePlan.Wrap(); + + if (IsRendered && !RepeatContent) + return SpacePlan.FullRender(Size.Zero); + + return SpacePlan.FullRender(availableSpace); } internal override void Draw(Size availableSpace) { + IsRendered = true; + var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas; if (Handler == null || skiaCanvas == null) diff --git a/QuestPDF/Elements/DynamicImage.cs b/QuestPDF/Elements/DynamicImage.cs index e3ac262..5a9824e 100644 --- a/QuestPDF/Elements/DynamicImage.cs +++ b/QuestPDF/Elements/DynamicImage.cs @@ -6,19 +6,31 @@ using SkiaSharp; namespace QuestPDF.Elements { - internal class DynamicImage : Element + internal class DynamicImage : Element, IVisual { + public bool IsRendered { get; set; } + public bool RepeatContent { get; set; } + public Func? Source { get; set; } internal override SpacePlan Measure(Size availableSpace) { - return availableSpace.IsNegative() - ? SpacePlan.Wrap() - : SpacePlan.FullRender(availableSpace); + if (availableSpace.IsNegative()) + return SpacePlan.Wrap(); + + if (IsRendered && !RepeatContent) + return SpacePlan.FullRender(Size.Zero); + + return SpacePlan.FullRender(availableSpace); } internal override void Draw(Size availableSpace) { + IsRendered = true; + + if (availableSpace.Width < Size.Epsilon || availableSpace.Height < Size.Epsilon) + return; + var imageData = Source?.Invoke(availableSpace); if (imageData == null) diff --git a/QuestPDF/Elements/Image.cs b/QuestPDF/Elements/Image.cs index e38475c..9679efb 100644 --- a/QuestPDF/Elements/Image.cs +++ b/QuestPDF/Elements/Image.cs @@ -5,8 +5,11 @@ using SkiaSharp; namespace QuestPDF.Elements { - internal class Image : Element, ICacheable + internal class Image : Element, IVisual, ICacheable { + public bool IsRendered { get; set; } + public bool RepeatContent { get; set; } + public SKImage? InternalImage { get; set; } ~Image() @@ -16,9 +19,13 @@ namespace QuestPDF.Elements internal override SpacePlan Measure(Size availableSpace) { - return availableSpace.IsNegative() - ? SpacePlan.Wrap() - : SpacePlan.FullRender(availableSpace); + if (availableSpace.IsNegative()) + return SpacePlan.Wrap(); + + if (IsRendered && !RepeatContent) + return SpacePlan.FullRender(Size.Zero); + + return SpacePlan.FullRender(availableSpace); } internal override void Draw(Size availableSpace) @@ -26,6 +33,7 @@ namespace QuestPDF.Elements if (InternalImage == null) return; + IsRendered = true; Canvas.DrawImage(InternalImage, Position.Zero, availableSpace); } } diff --git a/QuestPDF/Elements/Line.cs b/QuestPDF/Elements/Line.cs index 4ec04d1..9dce030 100644 --- a/QuestPDF/Elements/Line.cs +++ b/QuestPDF/Elements/Line.cs @@ -15,34 +15,42 @@ namespace QuestPDF.Elements Horizontal } - internal class Line : Element, ILine, ICacheable + internal class Line : Element, ILine, IVisual, ICacheable { + public bool IsRendered { get; set; } + public bool RepeatContent { get; set; } + public LineType Type { get; set; } = LineType.Vertical; public string Color { get; set; } = Colors.Black; - public float Size { get; set; } = 1; + public float Thickness { get; set; } = 1; internal override SpacePlan Measure(Size availableSpace) { if (availableSpace.IsNegative()) return SpacePlan.Wrap(); + + if (IsRendered && !RepeatContent) + return SpacePlan.FullRender(Size.Zero); return Type switch { - LineType.Vertical when availableSpace.Width + Infrastructure.Size.Epsilon >= Size => SpacePlan.FullRender(Size, 0), - LineType.Horizontal when availableSpace.Height + Infrastructure.Size.Epsilon >= Size => SpacePlan.FullRender(0, Size), + LineType.Vertical when availableSpace.Width + Infrastructure.Size.Epsilon >= Thickness => SpacePlan.FullRender(Thickness, 0), + LineType.Horizontal when availableSpace.Height + Infrastructure.Size.Epsilon >= Thickness => SpacePlan.FullRender(0, Thickness), _ => SpacePlan.Wrap() }; } internal override void Draw(Size availableSpace) { + IsRendered = true; + if (Type == LineType.Vertical) { - Canvas.DrawRectangle(new Position(-Size/2, 0), new Size(Size, availableSpace.Height), Color); + Canvas.DrawRectangle(new Position(-Thickness/2, 0), new Size(Thickness, availableSpace.Height), Color); } else if (Type == LineType.Horizontal) { - Canvas.DrawRectangle(new Position(0, -Size/2), new Size(availableSpace.Width, Size), Color); + Canvas.DrawRectangle(new Position(0, -Thickness/2), new Size(availableSpace.Width, Thickness), Color); } } } diff --git a/QuestPDF/Elements/RepeatContentSetter.cs b/QuestPDF/Elements/RepeatContentSetter.cs new file mode 100644 index 0000000..668c9b3 --- /dev/null +++ b/QuestPDF/Elements/RepeatContentSetter.cs @@ -0,0 +1,9 @@ +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class RepeatContentSetter : ContainerElement + { + public bool RepeatContent { get; set; } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Text/TextBlock.cs b/QuestPDF/Elements/Text/TextBlock.cs index c14f8cc..9dead81 100644 --- a/QuestPDF/Elements/Text/TextBlock.cs +++ b/QuestPDF/Elements/Text/TextBlock.cs @@ -4,12 +4,16 @@ using System.Linq; using QuestPDF.Drawing; using QuestPDF.Elements.Text.Calculation; using QuestPDF.Elements.Text.Items; +using QuestPDF.Helpers; using QuestPDF.Infrastructure; namespace QuestPDF.Elements.Text { - internal class TextBlock : Element, IStateResettable + internal class TextBlock : Element, IVisual, IStateResettable { + public bool IsRendered { get; set; } + public bool RepeatContent { get; set; } + public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left; public List Items { get; set; } = new List(); @@ -19,7 +23,7 @@ namespace QuestPDF.Elements.Text private int CurrentElementIndex { get; set; } private bool FontFallbackApplied { get; set; } = false; - + public void ResetState() { ApplyFontFallback(); @@ -53,6 +57,12 @@ namespace QuestPDF.Elements.Text internal override SpacePlan Measure(Size availableSpace) { + if (availableSpace.IsNegative()) + return SpacePlan.Wrap(); + + if (IsRendered && !RepeatContent) + return SpacePlan.FullRender(Size.Zero); + if (!RenderingQueue.Any()) return SpacePlan.FullRender(Size.Zero); @@ -80,6 +90,9 @@ namespace QuestPDF.Elements.Text internal override void Draw(Size availableSpace) { + if (IsRendered && !RepeatContent) + return; + var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height).ToList(); if (!lines.Any()) @@ -136,10 +149,13 @@ namespace QuestPDF.Elements.Text var lastElementMeasurement = lines.Last().Elements.Last().Measurement; CurrentElementIndex = lastElementMeasurement.IsLast ? 0 : lastElementMeasurement.NextIndex; - + if (!RenderingQueue.Any()) + { ResetState(); - + IsRendered = true; + } + float GetAlignmentOffset(float lineWidth) { if (Alignment == HorizontalAlignment.Left) diff --git a/QuestPDF/Fluent/DecorationExtensions.cs b/QuestPDF/Fluent/DecorationExtensions.cs index 97a4428..48d4eea 100644 --- a/QuestPDF/Fluent/DecorationExtensions.cs +++ b/QuestPDF/Fluent/DecorationExtensions.cs @@ -12,7 +12,7 @@ namespace QuestPDF.Fluent { var container = new Container(); Decoration.Before = container; - return container; + return container.RepeatContentWhenPaging(); } public void Before(Action handler) @@ -36,7 +36,7 @@ namespace QuestPDF.Fluent { var container = new Container(); Decoration.After = container; - return container; + return container.RepeatContentWhenPaging(); } public void After(Action handler) @@ -49,9 +49,7 @@ namespace QuestPDF.Fluent [Obsolete("This element has been renamed since version 2022.2. Please use the 'Before' method.")] public IContainer Header() { - var container = new Container(); - Decoration.Before = container; - return container; + return Before(); } [Obsolete("This element has been renamed since version 2022.2. Please use the 'Before' method.")] @@ -63,9 +61,7 @@ namespace QuestPDF.Fluent [Obsolete("This element has been renamed since version 2022.2. Please use the 'After' method.")] public IContainer Footer() { - var container = new Container(); - Decoration.After = container; - return container; + return After(); } [Obsolete("This element has been renamed since version 2022.2. Please use the 'After' method.")] diff --git a/QuestPDF/Fluent/LayerExtensions.cs b/QuestPDF/Fluent/LayerExtensions.cs index 47d236c..7fd60b6 100644 --- a/QuestPDF/Fluent/LayerExtensions.cs +++ b/QuestPDF/Fluent/LayerExtensions.cs @@ -24,7 +24,7 @@ namespace QuestPDF.Fluent return container; } - public IContainer Layer() => Layer(false); + public IContainer Layer() => Layer(false).RepeatContentWhenPaging(); public IContainer PrimaryLayer() => Layer(true); internal void Validate() diff --git a/QuestPDF/Fluent/LineExtensions.cs b/QuestPDF/Fluent/LineExtensions.cs index 2dacdcf..925252a 100644 --- a/QuestPDF/Fluent/LineExtensions.cs +++ b/QuestPDF/Fluent/LineExtensions.cs @@ -6,11 +6,11 @@ namespace QuestPDF.Fluent { public static class LineExtensions { - private static ILine Line(this IContainer element, LineType type, float size) + private static ILine Line(this IContainer element, LineType type, float thickness) { var line = new Line { - Size = size, + Thickness = thickness, Type = type }; @@ -18,14 +18,14 @@ namespace QuestPDF.Fluent return line; } - public static ILine LineVertical(this IContainer element, float size, Unit unit = Unit.Point) + public static ILine LineVertical(this IContainer element, float thickness, Unit unit = Unit.Point) { - return element.Line(LineType.Vertical, size.ToPoints(unit)); + return element.Line(LineType.Vertical, thickness.ToPoints(unit)); } - public static ILine LineHorizontal(this IContainer element, float size, Unit unit = Unit.Point) + public static ILine LineHorizontal(this IContainer element, float thickness, Unit unit = Unit.Point) { - return element.Line(LineType.Horizontal, size.ToPoints(unit)); + return element.Line(LineType.Horizontal, thickness.ToPoints(unit)); } public static void LineColor(this ILine descriptor, string value) diff --git a/QuestPDF/Fluent/RepeatContentExtensions.cs b/QuestPDF/Fluent/RepeatContentExtensions.cs new file mode 100644 index 0000000..15d4fc1 --- /dev/null +++ b/QuestPDF/Fluent/RepeatContentExtensions.cs @@ -0,0 +1,27 @@ +using System; +using QuestPDF.Elements; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Fluent +{ + public static class RepeatContentExtensions + { + private static IContainer RepeatContent(this IContainer element, Action handler) + { + var repeatContentSetter = element as RepeatContentSetter ?? new RepeatContentSetter(); + handler(repeatContentSetter); + + return element.Element(repeatContentSetter); + } + + public static IContainer RepeatContentWhenPaging(this IContainer element) + { + return element.RepeatContent(x => x.RepeatContent = true); + } + + public static IContainer DoNotRepeatContentWhenPaging(this IContainer element) + { + return element.RepeatContent(x => x.RepeatContent = false); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Infrastructure/IVisual.cs b/QuestPDF/Infrastructure/IVisual.cs new file mode 100644 index 0000000..e0d8162 --- /dev/null +++ b/QuestPDF/Infrastructure/IVisual.cs @@ -0,0 +1,8 @@ +namespace QuestPDF.Infrastructure +{ + internal interface IVisual + { + public bool IsRendered { get; set; } + public bool RepeatContent { get; set; } + } +} \ No newline at end of file