Browse Source

Repeat content experiment

MarcinZiabek 3 years ago
parent
commit
09e642295f

+ 38 - 0
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);
+                            }); 
+                        });
+                });
+        }
+    }
+}

+ 49 - 2
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)
         {

+ 13 - 4
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)

+ 16 - 4
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<Size, byte[]>? 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)

+ 12 - 4
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);
         }
     }

+ 14 - 6
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);
             }
         }
     }

+ 9 - 0
QuestPDF/Elements/RepeatContentSetter.cs

@@ -0,0 +1,9 @@
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Elements
+{
+    internal class RepeatContentSetter : ContainerElement
+    {
+        public bool RepeatContent { get; set; }
+    }
+}

+ 20 - 4
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<ITextBlockItem> Items { get; set; } = new List<ITextBlockItem>();
 
@@ -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)

+ 4 - 8
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<IContainer> handler)
@@ -36,7 +36,7 @@ namespace QuestPDF.Fluent
         {
             var container = new Container();
             Decoration.After = container;
-            return container;
+            return container.RepeatContentWhenPaging();
         }
         
         public void After(Action<IContainer> 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.")]

+ 1 - 1
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()

+ 6 - 6
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)

+ 27 - 0
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<RepeatContentSetter> 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);
+        }
+    }
+}

+ 8 - 0
QuestPDF/Infrastructure/IVisual.cs

@@ -0,0 +1,8 @@
+namespace QuestPDF.Infrastructure
+{
+    internal interface IVisual
+    {
+        public bool IsRendered { get; set; }
+        public bool RepeatContent { get; set; }
+    }
+}