Browse Source

Merge documents (#572)

* Transferred changes from another https://github.com/QuestPDF/QuestPDF/pull/367

* Update qodana.yml

* Update qodana.yml

* Simplified generated document structure

* Fixed: section links work correctly in merged documents

* Code refactoring

* Code refactoring

* Fixed rendering merged documents

* Merging documents: minor code adjustments
Marcin Ziąbek 2 years ago
parent
commit
ec92801b0e

+ 3 - 3
.github/workflows/qodana.yml

@@ -16,6 +16,6 @@ jobs:
         with:
         with:
           fetch-depth: 0
           fetch-depth: 0
       - name: 'Qodana Scan'
       - name: 'Qodana Scan'
-        uses: JetBrains/qodana-action@v2023.1.0
-        with:
-          pr-mode: false
+        uses: JetBrains/qodana-action@main
+        env:
+          QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}

+ 1 - 1
Source/QuestPDF.Examples/Engine/RenderingTest.cs

@@ -109,7 +109,7 @@ namespace QuestPDF.Examples.Engine
             Render(document);
             Render(document);
         }
         }
         
         
-        private void Render(IDocument document)
+        public void Render(IDocument document)
         {
         {
             if (ResultType == RenderingTestResult.Images)
             if (ResultType == RenderingTestResult.Images)
             {
             {

+ 97 - 0
Source/QuestPDF.Examples/MergedDocumentExamples.cs

@@ -0,0 +1,97 @@
+using System.Linq;
+using NUnit.Framework;
+using QuestPDF.Examples.Engine;
+using QuestPDF.Fluent;
+using QuestPDF.Helpers;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Examples
+{
+    [TestFixture]
+    public class MergedDocumentExamples
+    {
+        [Test]
+        public void Merge_ContinuousPageNumbers()
+        {
+            var mergedDocument = Document
+                .Merge(
+                    GenerateReport("Short Document 1", 5),
+                    GenerateReport("Medium Document 2", 10),
+                    GenerateReport("Long Document 3", 15))
+                .UseContinuousPageNumbers();
+
+            RenderingTest
+                .Create()
+                .ProducePdf()
+                .ShowResults()
+                .Render(mergedDocument);
+        }
+
+        [Test]
+        public void Merge_SeparatePageNumbers()
+        {
+            var mergedDocument = Document
+                .Merge(
+                    GenerateReport("Short Document 1", 5),
+                    GenerateReport("Medium Document 2", 10),
+                    GenerateReport("Long Document 3", 15))
+                .UseOriginalPageNumbers();
+
+            RenderingTest
+                .Create()
+                .ProducePdf()
+                .ShowResults()
+                .Render(mergedDocument);
+        }
+
+        private static Document GenerateReport(string title, int itemsCount)
+        {
+            return Document.Create(document =>
+            {
+                document.Page(page =>
+                {
+                    page.Margin(0.5f, Unit.Inch);
+                    page.Size(PageSizes.A5);
+                    
+                    page.Header()
+                        .Text(title)
+                        .Bold()
+                        .FontSize(24)
+                        .FontColor(Colors.Blue.Accent2);
+                    
+                    page.Content()
+                        .PaddingVertical(20)
+                        .Column(column =>
+                        {
+                            column.Spacing(10);
+
+                            foreach (var i in Enumerable.Range(1, itemsCount))
+                            {
+                                column
+                                    .Item()
+                                    .Width(200)
+                                    .Height(50)
+                                    .Background(Colors.Grey.Lighten3)
+                                    .AlignMiddle()
+                                    .AlignCenter()
+                                    .Text($"Item {i}")
+                                    .FontSize(16);
+                            }
+                        });
+                    
+                    page.Footer()
+                        .AlignCenter()
+                        .PaddingVertical(20)
+                        .Text(text =>
+                        {
+                            text.DefaultTextStyle(TextStyle.Default.FontSize(16));
+                            
+                            text.CurrentPageNumber();
+                            text.Span(" / ");
+                            text.TotalPages();
+                        });
+                });
+            });
+        }
+    }
+}

+ 80 - 0
Source/QuestPDF.Examples/MergedDocumentSectionLinksTests.cs

@@ -0,0 +1,80 @@
+using NUnit.Framework;
+using QuestPDF.Examples.Engine;
+using QuestPDF.Fluent;
+using QuestPDF.Helpers;
+
+namespace QuestPDF.Examples
+{
+    [TestFixture]
+    public class MergedDocumentSectionLinksTests
+    {
+        [Test]
+        public void Merge_ContinuousPageNumbers()
+        {
+            var mergedDocument = Document.Merge(
+                    CreateDocument("Document 1"),
+                    CreateDocument("Document 2"),
+                    CreateDocument("Document 3"))
+                .UseContinuousPageNumbers();
+
+            RenderingTest
+                .Create()
+                .ProducePdf()
+                .ShowResults()
+                .Render(mergedDocument);
+        }
+
+        [Test]
+        public void Merge_SeparatePageNumbers()
+        {
+            var mergedDocument = Document.Merge(
+                    CreateDocument("Document 1"),
+                    CreateDocument("Document 2"),
+                    CreateDocument("Document 3"))
+                .UseOriginalPageNumbers();
+
+            RenderingTest
+                .Create()
+                .ProducePdf()
+                .ShowResults()
+                .Render(mergedDocument);
+        }
+
+        private static Document CreateDocument(string content)
+        {
+            return Document.Create(document =>
+            {
+                document.Page(page =>
+                {
+                    page.Content()
+                        .AlignMiddle()
+                        .AlignCenter()
+                        .Column(column =>
+                        {
+                            column.Item().Text(content).FontSize(40);
+                            
+                            column.Item().PageBreak();
+                            
+                            column.Item().Text(content).FontSize(40);
+                            column.Item().AlignCenter().SectionLink("next").Text("Next page").FontSize(16).Underline().FontColor(Colors.Blue.Medium);
+                            
+                            column.Item().PageBreak();
+                            
+                            column.Item().Text(content).FontSize(40);
+                            column.Item().AlignCenter().Section("next").Text("Next page").FontSize(16).FontColor(Colors.Green.Medium);
+                        });
+                    
+                    page.Footer()
+                        .AlignCenter()
+                        .PaddingVertical(20)
+                        .Text(text =>
+                        {
+                            text.CurrentPageNumber();
+                            text.Span(" / ");
+                            text.TotalPages();
+                        });
+                });
+            });
+        }
+    }
+}

+ 26 - 14
Source/QuestPDF/Drawing/DocumentContainer.cs

@@ -14,22 +14,34 @@ namespace QuestPDF.Drawing
         internal Container Compose()
         internal Container Compose()
         {
         {
             var container = new Container();
             var container = new Container();
-            
-            container
-                .Column(column =>
+            ComposeContainer(container);
+            return container;
+
+            void ComposeContainer(IContainer container)
+            {
+                if (Pages.Count == 0)
+                    return;
+                
+                if (Pages.Count == 1)
                 {
                 {
-                    Pages
-                        .SelectMany(x => new List<Action>()
-                        {
-                            () => column.Item().PageBreak(),
-                            () => column.Item().Component(x)
-                        })
-                        .Skip(1)
-                        .ToList()
-                        .ForEach(x => x());
-                });
+                    container.Component(Pages.First());
+                    return;
+                }
 
 
-            return container;
+                container
+                    .Column(column =>
+                    {
+                        Pages
+                            .SelectMany(x => new List<Action>()
+                            {
+                                () => column.Item().PageBreak(),
+                                () => column.Item().Component(x)
+                            })
+                            .Skip(1)
+                            .ToList()
+                            .ForEach(x => x());
+                    });
+            }
         }
         }
     }
     }
 }
 }

+ 101 - 29
Source/QuestPDF/Drawing/DocumentGenerator.cs

@@ -53,7 +53,7 @@ namespace QuestPDF.Drawing
             documentSettings.ImageRasterDpi = imageGenerationSettings.RasterDpi;
             documentSettings.ImageRasterDpi = imageGenerationSettings.RasterDpi;
             
             
             var canvas = new ImageCanvas(imageGenerationSettings);
             var canvas = new ImageCanvas(imageGenerationSettings);
-            RenderDocument(canvas, document, documentSettings, useOriginalImages: true);
+            RenderDocument(canvas, document, documentSettings);
 
 
             return canvas.Images;
             return canvas.Images;
         }
         }
@@ -94,40 +94,98 @@ namespace QuestPDF.Drawing
             return canvas.Pictures;
             return canvas.Pictures;
         }
         }
         
         
-        internal static void RenderDocument<TCanvas>(TCanvas canvas, IDocument document, DocumentSettings settings, bool useOriginalImages = false)
-            where TCanvas : ICanvas, IRenderingCanvas
+        private static void RenderDocument<TCanvas>(TCanvas canvas, IDocument document, DocumentSettings settings) where TCanvas : ICanvas, IRenderingCanvas
         {
         {
-            var container = new DocumentContainer();
-            document.Compose(container);
-            var content = container.Compose();
+            canvas.BeginDocument();
             
             
-            ApplyInheritedAndGlobalTexStyle(content, TextStyle.Default);
-            ApplyContentDirection(content, settings.ContentDirection);
-            ApplyDefaultImageConfiguration(content, settings.ImageRasterDpi, settings.ImageCompressionQuality, useOriginalImages);
+            if (document is MergedDocument mergedDocument)
+                RenderMergedDocument(canvas, mergedDocument, settings);
             
             
-            var debuggingState = Settings.EnableDebugging ? ApplyDebugging(content) : null;
+            else
+                RenderSingleDocument(canvas, document, settings);
             
             
-            if (Settings.EnableCaching)
-                ApplyCaching(content);
+            canvas.EndDocument();
+        }
+
+        private static void RenderSingleDocument<TCanvas>(TCanvas canvas, IDocument document, DocumentSettings settings)
+            where TCanvas : ICanvas, IRenderingCanvas
+        {
+            var debuggingState = new DebuggingState();
+            var useOriginalImages = canvas is ImageCanvas;
+
+            var content = ConfigureContent(document, settings, debuggingState, 0, useOriginalImages);
 
 
             var pageContext = new PageContext();
             var pageContext = new PageContext();
             RenderPass(pageContext, new FreeCanvas(), content, debuggingState);
             RenderPass(pageContext, new FreeCanvas(), content, debuggingState);
+            pageContext.ResetPageNumber();
             RenderPass(pageContext, canvas, content, debuggingState);
             RenderPass(pageContext, canvas, content, debuggingState);
         }
         }
         
         
-        internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DebuggingState? debuggingState)
+        private static void RenderMergedDocument<TCanvas>(TCanvas canvas, MergedDocument document, DocumentSettings settings)
             where TCanvas : ICanvas, IRenderingCanvas
             where TCanvas : ICanvas, IRenderingCanvas
         {
         {
-            InjectDependencies(content, pageContext, canvas);
-            content.VisitChildren(x => (x as IStateResettable)?.ResetState());
+            var debuggingState = new DebuggingState();
+            var useOriginalImages = canvas is ImageCanvas;
             
             
-            canvas.BeginDocument();
+            var documentContents = Enumerable
+                .Range(0, document.Documents.Count)
+                .Select(index => ConfigureContent(document.Documents[index], settings, debuggingState, index, useOriginalImages))
+                .ToList();
+
+            if (document.PageNumberStrategy == MergedDocumentPageNumberStrategy.Continuous)
+            {
+                var documentPageContext = new PageContext();
+                
+                foreach (var content in documentContents)
+                    RenderPass(documentPageContext, new FreeCanvas(), content, debuggingState);
+                
+                documentPageContext.ResetPageNumber();
+                
+                foreach (var content in documentContents)
+                    RenderPass(documentPageContext, canvas, content, debuggingState);
+            }
+            else
+            {
+                foreach (var content in documentContents)
+                {
+                    var pageContext = new PageContext();
+                    RenderPass(pageContext, new FreeCanvas(), content, debuggingState);
+                    pageContext.ResetPageNumber();
+                    RenderPass(pageContext, canvas, content, debuggingState);
+                }
+            }
+        }
+
+        private static Container ConfigureContent(IDocument document, DocumentSettings settings, DebuggingState debuggingState, int documentIndex, bool useOriginalImages)
+        {
+            var container = new DocumentContainer();
+            document.Compose(container);
+            
+            var content = container.Compose();
+            
+            content.ApplyDocumentId(documentIndex);
+            content.ApplyInheritedAndGlobalTexStyle(TextStyle.Default);
+            content.ApplyContentDirection(settings.ContentDirection);
+            content.ApplyDefaultImageConfiguration(settings.ImageRasterDpi, settings.ImageCompressionQuality, useOriginalImages);
+                    
+            if (Settings.EnableCaching)
+                content.ApplyCaching();
+
+            if (Settings.EnableDebugging)
+                content.ApplyDebugging(debuggingState);
 
 
-            var currentPage = 1;
+            return content;
+        }
+
+        private static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DebuggingState? debuggingState)
+            where TCanvas : ICanvas, IRenderingCanvas
+        {
+            InjectDependencies(content, pageContext, canvas);
+            content.VisitChildren(x => (x as IStateResettable)?.ResetState());
             
             
             while(true)
             while(true)
             {
             {
-                pageContext.SetPageNumber(currentPage);
+                pageContext.IncrementPageNumber();
                 debuggingState?.Reset();
                 debuggingState?.Reset();
                 
                 
                 var spacePlan = content.Measure(Size.Max);
                 var spacePlan = content.Measure(Size.Max);
@@ -151,7 +209,7 @@ namespace QuestPDF.Drawing
 
 
                 canvas.EndPage();
                 canvas.EndPage();
 
 
-                if (currentPage >= Settings.DocumentLayoutExceptionThreshold)
+                if (pageContext.CurrentPage >= Settings.DocumentLayoutExceptionThreshold)
                 {
                 {
                     canvas.EndDocument();
                     canvas.EndDocument();
                     ThrowLayoutException();
                     ThrowLayoutException();
@@ -159,11 +217,7 @@ namespace QuestPDF.Drawing
                 
                 
                 if (spacePlan.Type == SpacePlanType.FullRender)
                 if (spacePlan.Type == SpacePlanType.FullRender)
                     break;
                     break;
-
-                currentPage++;
             }
             }
-            
-            canvas.EndDocument();
 
 
             void ThrowLayoutException()
             void ThrowLayoutException()
             {
             {
@@ -191,7 +245,7 @@ namespace QuestPDF.Drawing
             });
             });
         }
         }
 
 
-        private static void ApplyCaching(Container content)
+        private static void ApplyCaching(this Container content)
         {
         {
             content.VisitChildren(x =>
             content.VisitChildren(x =>
             {
             {
@@ -200,16 +254,15 @@ namespace QuestPDF.Drawing
             });
             });
         }
         }
 
 
-        private static DebuggingState ApplyDebugging(Container content)
+        private static void ApplyDebugging(this Container content, DebuggingState? debuggingState)
         {
         {
-            var debuggingState = new DebuggingState();
-
+            if (debuggingState == null)
+                return;
+            
             content.VisitChildren(x =>
             content.VisitChildren(x =>
             {
             {
                 x.CreateProxy(y => new DebuggingProxy(debuggingState, y));
                 x.CreateProxy(y => new DebuggingProxy(debuggingState, y));
             });
             });
-
-            return debuggingState;
         }
         }
         
         
         internal static void ApplyContentDirection(this Element? content, ContentDirection direction)
         internal static void ApplyContentDirection(this Element? content, ContentDirection direction)
@@ -293,5 +346,24 @@ namespace QuestPDF.Drawing
             foreach (var child in content.GetChildren())
             foreach (var child in content.GetChildren())
                 ApplyInheritedAndGlobalTexStyle(child, documentDefaultTextStyle);
                 ApplyInheritedAndGlobalTexStyle(child, documentDefaultTextStyle);
         }
         }
+        
+        /// <summary>
+        /// This method is important when merging multiple documents together.
+        /// Applying unique document Id to all section names and associated links, prevents from name collisions.
+        /// </summary>
+        internal static void ApplyDocumentId(this Element? content, int documentId = 0)
+        {
+            content.VisitChildren(x =>
+            {
+                if (x is Section section)
+                    section.DocumentId = documentId;
+                
+                else if (x is SectionLink sectionLink)
+                    sectionLink.DocumentId = documentId;
+                
+                else if (x is DynamicHost dynamicHost)
+                    dynamicHost.DocumentId = documentId;
+            });
+        }
     }
     }
 }
 }

+ 5 - 0
Source/QuestPDF/Elements/Dynamic.cs

@@ -14,6 +14,8 @@ namespace QuestPDF.Elements
         internal TextStyle TextStyle { get; set; } = TextStyle.Default;
         internal TextStyle TextStyle { get; set; } = TextStyle.Default;
         public ContentDirection ContentDirection { get; set; }
         public ContentDirection ContentDirection { get; set; }
         
         
+        internal int DocumentId { get; set; }
+        
         internal int? ImageTargetDpi { get; set; }
         internal int? ImageTargetDpi { get; set; }
         internal ImageCompressionQuality? ImageCompressionQuality { get; set; }
         internal ImageCompressionQuality? ImageCompressionQuality { get; set; }
         internal bool UseOriginalImage { get; set; }
         internal bool UseOriginalImage { get; set; }
@@ -58,6 +60,7 @@ namespace QuestPDF.Elements
             {
             {
                 PageContext = PageContext,
                 PageContext = PageContext,
                 Canvas = Canvas,
                 Canvas = Canvas,
+                DocumentId = DocumentId,
                 
                 
                 TextStyle = TextStyle,
                 TextStyle = TextStyle,
                 ContentDirection = ContentDirection,
                 ContentDirection = ContentDirection,
@@ -84,6 +87,7 @@ namespace QuestPDF.Elements
     {
     {
         internal IPageContext PageContext { get; set; }
         internal IPageContext PageContext { get; set; }
         internal ICanvas Canvas { get; set; }
         internal ICanvas Canvas { get; set; }
+        internal int DocumentId { get; set; }
         
         
         internal TextStyle TextStyle { get; set; }
         internal TextStyle TextStyle { get; set; }
         internal ContentDirection ContentDirection { get; set; }
         internal ContentDirection ContentDirection { get; set; }
@@ -101,6 +105,7 @@ namespace QuestPDF.Elements
             var container = new DynamicElement();
             var container = new DynamicElement();
             content(container);
             content(container);
             
             
+            container.ApplyDocumentId(DocumentId);
             container.ApplyInheritedAndGlobalTexStyle(TextStyle);
             container.ApplyInheritedAndGlobalTexStyle(TextStyle);
             container.ApplyContentDirection(ContentDirection);
             container.ApplyContentDirection(ContentDirection);
             container.ApplyDefaultImageConfiguration(ImageTargetDpi, ImageCompressionQuality, UseOriginalImage);
             container.ApplyDefaultImageConfiguration(ImageTargetDpi, ImageCompressionQuality, UseOriginalImage);

+ 11 - 3
Source/QuestPDF/Elements/Section.cs

@@ -4,7 +4,8 @@ namespace QuestPDF.Elements
 {
 {
     internal class Section : ContainerElement, IStateResettable
     internal class Section : ContainerElement, IStateResettable
     {
     {
-        public string LocationName { get; set; }
+        public int DocumentId { get; set; }
+        public string SectionName { get; set; }
         private bool IsRendered { get; set; }
         private bool IsRendered { get; set; }
         
         
         public void ResetState()
         public void ResetState()
@@ -14,14 +15,21 @@ namespace QuestPDF.Elements
         
         
         internal override void Draw(Size availableSpace)
         internal override void Draw(Size availableSpace)
         {
         {
+            var targetName = GetTargetName(DocumentId, SectionName);
+            
             if (!IsRendered)
             if (!IsRendered)
             {
             {
-                Canvas.DrawSection(LocationName);
+                Canvas.DrawSection(targetName);
                 IsRendered = true;
                 IsRendered = true;
             }
             }
             
             
-            PageContext.SetSectionPage(LocationName);
+            PageContext.SetSectionPage(targetName);
             base.Draw(availableSpace);
             base.Draw(availableSpace);
         }
         }
+        
+        internal static string GetTargetName(int documentId, string locationName)
+        {
+            return $"{documentId} | {locationName}";
+        }
     }
     }
 }
 }

+ 3 - 1
Source/QuestPDF/Elements/SectionLink.cs

@@ -5,6 +5,7 @@ namespace QuestPDF.Elements
 {
 {
     internal class SectionLink : ContainerElement
     internal class SectionLink : ContainerElement
     {
     {
+        public int DocumentId { get; set; }
         public string SectionName { get; set; }
         public string SectionName { get; set; }
         
         
         internal override void Draw(Size availableSpace)
         internal override void Draw(Size availableSpace)
@@ -14,7 +15,8 @@ namespace QuestPDF.Elements
             if (targetSize.Type == SpacePlanType.Wrap)
             if (targetSize.Type == SpacePlanType.Wrap)
                 return;
                 return;
 
 
-            Canvas.DrawSectionLink(SectionName, targetSize);
+            var targetName = Section.GetTargetName(DocumentId, SectionName);
+            Canvas.DrawSectionLink(targetName, targetSize);
             base.Draw(availableSpace);
             base.Draw(availableSpace);
         }
         }
     }
     }

+ 1 - 1
Source/QuestPDF/Fluent/ElementExtensions.cs

@@ -126,7 +126,7 @@ namespace QuestPDF.Fluent
         {
         {
             return element.Element(new Section
             return element.Element(new Section
             {
             {
-                LocationName = sectionName
+                SectionName = sectionName
             });
             });
         }
         }
         
         

+ 11 - 0
Source/QuestPDF/Fluent/MinimalApi.cs

@@ -1,4 +1,5 @@
 using System;
 using System;
+using System.Collections.Generic;
 using QuestPDF.Drawing;
 using QuestPDF.Drawing;
 using QuestPDF.Infrastructure;
 using QuestPDF.Infrastructure;
 
 
@@ -32,6 +33,16 @@ namespace QuestPDF.Fluent
             return this;
             return this;
         }
         }
         
         
+        public static MergedDocument Merge(IEnumerable<IDocument> documents)
+        {
+            return new MergedDocument(documents);
+        }
+
+        public static MergedDocument Merge(params IDocument[] documents)
+        {
+            return new MergedDocument(documents);
+        }
+        
         #region IDocument
         #region IDocument
 
 
         public DocumentMetadata GetMetadata() => Metadata;
         public DocumentMetadata GetMetadata() => Metadata;

+ 81 - 0
Source/QuestPDF/Infrastructure/IMergedDocument.cs

@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace QuestPDF.Infrastructure
+{
+    internal enum MergedDocumentPageNumberStrategy
+    {
+        Original,
+        Continuous,
+    }
+
+    public sealed class MergedDocument : IDocument
+    {
+        internal IReadOnlyList<IDocument> Documents { get; }
+        internal MergedDocumentPageNumberStrategy PageNumberStrategy { get; private set; } = MergedDocumentPageNumberStrategy.Original;
+        
+        internal DocumentMetadata Metadata { get; private set; } = DocumentMetadata.Default;
+        internal DocumentSettings Settings { get; private set; } = DocumentSettings.Default;
+
+        internal MergedDocument(IEnumerable<IDocument> documents)
+        {
+            Documents = documents?.ToList() ?? throw new NullReferenceException(nameof(documents));
+        }
+
+        public void Compose(IDocumentContainer container)
+        {
+            foreach (var document in Documents)
+            {
+                document.Compose(container);
+            }
+        }
+
+        public DocumentMetadata GetMetadata()
+        {
+            return Metadata;
+        }
+        
+        public DocumentSettings GetSettings()
+        {
+            return Settings;
+        }
+
+        /// <summary>
+        /// Each document is considered as separate in terms of page numbering.
+        /// That means, all page number related APIs will return values based on original documents.
+        /// All documents will simply be merged together.
+        /// For example: let's suppose that two documents are merged, first with 2 pages and second with 3 pages.
+        /// The resulting document will have 5 pages, and page numbers will be: 1, 2, 1, 2, 3.
+        /// </summary>
+        public MergedDocument UseOriginalPageNumbers()
+        {
+            PageNumberStrategy = MergedDocumentPageNumberStrategy.Original;
+            return this;
+        }
+
+        /// <summary>
+        /// Content from all documents will be merged together, and considered as one/single document.
+        /// That means, all page number related APIs will return continuous numbers.
+        /// For example: let's suppose that two documents are merged, first with 2 pages and second with 3 pages.
+        /// The resulting document will have 5 pages, and page numbers will be: 1, 2, 3, 4, 5.
+        /// </summary>
+        public MergedDocument UseContinuousPageNumbers()
+        {
+            PageNumberStrategy = MergedDocumentPageNumberStrategy.Continuous;
+            return this;
+        }
+
+        public MergedDocument WithMetadata(DocumentMetadata metadata)
+        {
+            Metadata = metadata ?? Metadata;
+            return this;
+        }
+
+        public MergedDocument WithSettings(DocumentSettings settings)
+        {
+            Settings = settings ?? Settings;
+            return this;
+        }
+    }
+}

+ 8 - 3
Source/QuestPDF/Infrastructure/PageContext.cs

@@ -11,12 +11,17 @@ namespace QuestPDF.Infrastructure
         private List<DocumentLocation> Locations { get; } = new();
         private List<DocumentLocation> Locations { get; } = new();
         public int CurrentPage { get; private set; }
         public int CurrentPage { get; private set; }
 
 
-        internal void SetPageNumber(int number)
+        internal void ResetPageNumber()
         {
         {
-            CurrentPage = number;
-            SetSectionPage(DocumentLocation);
+            CurrentPage = 0;
         }
         }
         
         
+        internal void IncrementPageNumber()
+        {
+            CurrentPage++;
+            SetSectionPage(DocumentLocation);
+        }
+
         public void SetSectionPage(string name)
         public void SetSectionPage(string name)
         {
         {
             var location = GetLocation(name);
             var location = GetLocation(name);