Ver Fonte

Merge branch '2022.03'

# Conflicts:
#	QuestPDF.Examples/TextExamples.cs
#	QuestPDF/QuestPDF.csproj
MarcinZiabek há 3 anos atrás
pai
commit
92263685d7
55 ficheiros alterados com 1163 adições e 417 exclusões
  1. 5 3
      QuestPDF.Examples/BarcodeExamples.cs
  2. 4 1
      QuestPDF.Examples/CanvasExamples.cs
  3. 4 1
      QuestPDF.Examples/ChartExamples.cs
  4. 1 1
      QuestPDF.Examples/ContinousPage.cs
  5. 1 1
      QuestPDF.Examples/DefaultTextStyleExample.cs
  6. 4 3
      QuestPDF.Examples/DefaultTextStyleExamples.cs
  7. 23 11
      QuestPDF.Examples/ElementExamples.cs
  8. 1 1
      QuestPDF.Examples/EnsureSpaceExample.cs
  9. 2 2
      QuestPDF.Examples/FrameExample.cs
  10. 2 1
      QuestPDF.Examples/InlinedExamples.cs
  11. 95 0
      QuestPDF.Examples/MinimalApiExamples.cs
  12. 2 1
      QuestPDF.Examples/Padding.cs
  13. 49 0
      QuestPDF.Examples/PageBackgroundForeground.cs
  14. 1 1
      QuestPDF.Examples/ShowOnceExample.cs
  15. 2 1
      QuestPDF.Examples/SkipOnceExample.cs
  16. 7 7
      QuestPDF.Examples/TextBenchmark.cs
  17. 15 13
      QuestPDF.Examples/TextExamples.cs
  18. 28 0
      QuestPDF.ReportSample/Helpers.cs
  19. 2 2
      QuestPDF.ReportSample/Layouts/DifferentHeadersTemplate.cs
  20. 3 2
      QuestPDF.ReportSample/Layouts/SectionTemplate.cs
  21. 4 4
      QuestPDF.ReportSample/Layouts/StandardReport.cs
  22. 16 2
      QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs
  23. 1 1
      QuestPDF.UnitTests/ExternalLinkTests.cs
  24. 146 0
      QuestPDF.UnitTests/FontStyleSetTests.cs
  25. 1 1
      QuestPDF.UnitTests/InternalLinkTests.cs
  26. 1 1
      QuestPDF.UnitTests/InternalLocationTests.cs
  27. 4 4
      QuestPDF.UnitTests/TestEngine/MockCanvas.cs
  28. 3 3
      QuestPDF.UnitTests/TestEngine/OperationRecordingCanvas.cs
  29. 1 1
      QuestPDF.UnitTests/TestEngine/TestPlan.cs
  30. 53 17
      QuestPDF/Drawing/FontManager.cs
  31. 132 0
      QuestPDF/Drawing/FontStyleSet.cs
  32. 3 3
      QuestPDF/Drawing/FreeCanvas.cs
  33. 5 5
      QuestPDF/Drawing/SkiaCanvasBase.cs
  34. 4 1
      QuestPDF/Elements/DebugArea.cs
  35. 2 2
      QuestPDF/Elements/Hyperlink.cs
  36. 45 27
      QuestPDF/Elements/Page.cs
  37. 1 1
      QuestPDF/Elements/Placeholder.cs
  38. 3 4
      QuestPDF/Elements/Section.cs
  39. 3 3
      QuestPDF/Elements/SectionLink.cs
  40. 2 2
      QuestPDF/Elements/Text/Items/TextBlockHyperlink.cs
  41. 5 9
      QuestPDF/Elements/Text/Items/TextBlockPageNumber.cs
  42. 3 3
      QuestPDF/Elements/Text/Items/TextBlockSectionlLink.cs
  43. 33 7
      QuestPDF/Fluent/ElementExtensions.cs
  44. 35 0
      QuestPDF/Fluent/MinimalApi.cs
  45. 19 0
      QuestPDF/Fluent/PageExtensions.cs
  46. 127 45
      QuestPDF/Fluent/TextExtensions.cs
  47. 126 0
      QuestPDF/Fluent/TextSpanDescriptorExtensions.cs
  48. 19 1
      QuestPDF/Fluent/TextStyleExtensions.cs
  49. 3 3
      QuestPDF/Infrastructure/ICanvas.cs
  50. 12 4
      QuestPDF/Infrastructure/IPageContext.cs
  51. 25 24
      QuestPDF/Infrastructure/PageContext.cs
  52. 20 5
      QuestPDF/Infrastructure/TextStyle.cs
  53. 4 0
      QuestPDF/Infrastructure/Unit.cs
  54. 1 1
      QuestPDF/QuestPDF.csproj
  55. 50 181
      readme.md

+ 5 - 3
QuestPDF.Examples/BarcodeExamples.cs

@@ -13,11 +13,11 @@ namespace QuestPDF.Examples
         [Test]
         [Test]
         public void Example()
         public void Example()
         {
         {
-            FontManager.RegisterFontType("LibreBarcode39", File.OpenRead("LibreBarcode39-Regular.ttf"));
+            FontManager.RegisterFont(File.OpenRead("LibreBarcode39-Regular.ttf"));
             
             
             RenderingTest
             RenderingTest
                 .Create()
                 .Create()
-                .PageSize(400, 100)
+                .PageSize(400, 200)
                 .ShowResults()
                 .ShowResults()
                 .Render(container =>
                 .Render(container =>
                 {
                 {
@@ -25,7 +25,9 @@ namespace QuestPDF.Examples
                         .Background(Colors.White)
                         .Background(Colors.White)
                         .AlignCenter()
                         .AlignCenter()
                         .AlignMiddle()
                         .AlignMiddle()
-                        .Text("*QuestPDF*", TextStyle.Default.FontType("LibreBarcode39").Size(64));
+                        .Text("*QuestPDF*")
+                        .FontFamily("Libre Barcode 39")
+                        .FontSize(64);
                 });
                 });
         }
         }
     }
     }

+ 4 - 1
QuestPDF.Examples/CanvasExamples.cs

@@ -49,7 +49,10 @@ namespace QuestPDF.Examples
                                 .PrimaryLayer()
                                 .PrimaryLayer()
                                 .PaddingVertical(10)
                                 .PaddingVertical(10)
                                 .PaddingHorizontal(20)
                                 .PaddingHorizontal(20)
-                                .Text("Sample text", TextStyle.Default.Size(16).Color(Colors.Blue.Darken2).SemiBold());
+                                .Text("Sample text")
+                                .FontSize(16)
+                                .FontColor(Colors.Blue.Darken2)
+                                .SemiBold();
                         });
                         });
                 });
                 });
         }
         }

+ 4 - 1
QuestPDF.Examples/ChartExamples.cs

@@ -57,7 +57,10 @@ namespace QuestPDF.Examples
                             column
                             column
                                 .Item()
                                 .Item()
                                 .PaddingBottom(10)
                                 .PaddingBottom(10)
-                                .Text("Chart example", TextStyle.Default.Size(20).SemiBold().Color(Colors.Blue.Medium));
+                                .Text("Chart example")
+                                .FontSize(20)
+                                .SemiBold()
+                                .FontColor(Colors.Blue.Medium);
                             
                             
                             column
                             column
                                 .Item()
                                 .Item()

+ 1 - 1
QuestPDF.Examples/ContinousPage.cs

@@ -25,7 +25,7 @@ namespace QuestPDF.Examples
                 page.Content().PaddingVertical(10).Border(1).Padding(10).Column(column =>
                 page.Content().PaddingVertical(10).Border(1).Padding(10).Column(column =>
                 {
                 {
                     foreach (var index in Enumerable.Range(1, 100))
                     foreach (var index in Enumerable.Range(1, 100))
-                        column.Item().Text($"Line {index}", TextStyle.Default.Color(Placeholders.Color()));
+                        column.Item().Text($"Line {index}").FontColor(Placeholders.Color());
                 });
                 });
                 
                 
                 page.Footer().Text("Footer");
                 page.Footer().Text("Footer");

+ 1 - 1
QuestPDF.Examples/DefaultTextStyleExample.cs

@@ -38,7 +38,7 @@ namespace QuestPDF.Examples
                                 text.Line(Placeholders.Sentence());
                                 text.Line(Placeholders.Sentence());
                             
                             
                                 // this text has size 20 but also semibold and red
                                 // this text has size 20 but also semibold and red
-                                text.Span(Placeholders.Sentence(), TextStyle.Default.Color(Colors.Red.Medium));
+                                text.Span(Placeholders.Sentence()).FontColor(Colors.Red.Medium);
                             });
                             });
                         });
                         });
                     });
                     });

+ 4 - 3
QuestPDF.Examples/DefaultTextStyleExamples.cs

@@ -26,8 +26,8 @@ namespace QuestPDF.Examples
                         .DefaultTextStyle(TextStyle.Default.Bold().Underline())
                         .DefaultTextStyle(TextStyle.Default.Bold().Underline())
                         .Column(column =>
                         .Column(column =>
                         { 
                         { 
-                            column.Item().Text("Default style applies to all children", TextStyle.Default);
-                            column.Item().Text("You can override certain styles", TextStyle.Default.Underline(false).Color(Colors.Green.Darken2));
+                            column.Item().Text("Default style applies to all children");
+                            column.Item().Text("You can override certain styles").Underline(false).FontColor(Colors.Green.Darken2);
                             
                             
                             column.Item().PaddingTop(10).Border(1).Grid(grid =>
                             column.Item().PaddingTop(10).Border(1).Grid(grid =>
                             {
                             {
@@ -43,7 +43,8 @@ namespace QuestPDF.Examples
                                         .Height(50)
                                         .Height(50)
                                         .AlignCenter()
                                         .AlignCenter()
                                         .AlignMiddle()
                                         .AlignMiddle()
-                                        .Text(i, TextStyle.Default.Size(16 + i / 4));   
+                                        .Text(i)
+                                        .FontSize(16 + i / 4);   
                                 }
                                 }
                             });
                             });
                         });
                         });

+ 23 - 11
QuestPDF.Examples/ElementExamples.cs

@@ -43,7 +43,9 @@ namespace QuestPDF.Examples
                                 .Before()
                                 .Before()
                                 .Background(Colors.Grey.Medium)
                                 .Background(Colors.Grey.Medium)
                                 .Padding(10)
                                 .Padding(10)
-                                .Text("Notes", TextStyle.Default.Size(16).Color("#FFF"));
+                                .Text("Notes")
+                                .FontSize(16)
+                                .FontColor("#FFF");
                     
                     
                             decoration
                             decoration
                                 .Content()
                                 .Content()
@@ -280,12 +282,15 @@ namespace QuestPDF.Examples
                                 .Layer()
                                 .Layer()
                                 .AlignCenter()
                                 .AlignCenter()
                                 .AlignMiddle()
                                 .AlignMiddle()
-                                .Text("Watermark", TextStyle.Default.Size(48).Bold().Color(Colors.Green.Lighten3));
+                                .Text("Watermark")
+                                .FontSize(48)
+                                .Bold()
+                                .FontColor(Colors.Green.Lighten3);
 
 
                             layers
                             layers
                                 .Layer()
                                 .Layer()
                                 .AlignBottom()
                                 .AlignBottom()
-                                .Text(text => text.CurrentPageNumber(TextStyle.Default.Size(16).Color(Colors.Green.Medium)));
+                                .Text(text => text.CurrentPageNumber().FontSize(16).FontColor(Colors.Green.Medium));
                         });
                         });
                 });
                 });
         }
         }
@@ -416,7 +421,9 @@ namespace QuestPDF.Examples
                                     .Border(1)
                                     .Border(1)
                                     .BorderColor(Colors.Grey.Medium)
                                     .BorderColor(Colors.Grey.Medium)
                                     .Padding(10)
                                     .Padding(10)
-                                    .Text(font, TextStyle.Default.FontType(font).Size(16));
+                                    .Text(font)
+                                    .FontFamily(font)
+                                    .FontSize(16);
                             }
                             }
                         });
                         });
                 });
                 });
@@ -456,7 +463,7 @@ namespace QuestPDF.Examples
                             });
                             });
                     
                     
                             layers.Layer().Background("#8F00").Extend();
                             layers.Layer().Background("#8F00").Extend();
-                            layers.Layer().PaddingTop(40).Text("It works!", TextStyle.Default.Size(24));
+                            layers.Layer().PaddingTop(40).Text("It works!").FontSize(24);
                         });
                         });
                 });
                 });
         }
         }
@@ -477,7 +484,7 @@ namespace QuestPDF.Examples
                         //.MinimalBox()
                         //.MinimalBox()
                         .Background(Colors.Grey.Lighten2)
                         .Background(Colors.Grey.Lighten2)
                         .Padding(15)
                         .Padding(15)
-                        .Text("Test of the \n box element", TextStyle.Default.Size(20));
+                        .Text("Test of the \n box element").FontSize(20);
                 });
                 });
         }
         }
 
 
@@ -503,7 +510,8 @@ namespace QuestPDF.Examples
                             decoration
                             decoration
                                 .Before()
                                 .Before()
                                 .PaddingBottom(10)
                                 .PaddingBottom(10)
-                                .Text("Example: scale component", headerFontStyle);
+                                .Text("Example: scale component")
+                                .Style(headerFontStyle);
     
     
                             decoration
                             decoration
                                 .Content()
                                 .Content()
@@ -525,7 +533,8 @@ namespace QuestPDF.Examples
                                             .Background(fontColor)
                                             .Background(fontColor)
                                             .Scale(scale)
                                             .Scale(scale)
                                             .Padding(5)
                                             .Padding(5)
-                                            .Text($"Content with {scale} scale.", fontStyle);
+                                            .Text($"Content with {scale} scale.")
+                                            .Style(fontStyle);
                                     }
                                     }
                                 });
                                 });
                         });
                         });
@@ -555,7 +564,8 @@ namespace QuestPDF.Examples
                         .BorderColor(Colors.Green.Darken1)
                         .BorderColor(Colors.Green.Darken1)
                         
                         
                         .Padding(50)
                         .Padding(50)
-                        .Text("Moved text", TextStyle.Default.Size(25));
+                        .Text("Moved text")
+                        .FontSize(25);
                 });
                 });
         }
         }
 
 
@@ -591,7 +601,8 @@ namespace QuestPDF.Examples
                                     .MinimalBox()
                                     .MinimalBox()
                                     .Background(Colors.White)
                                     .Background(Colors.White)
                                     .Padding(10)
                                     .Padding(10)
-                                    .Text($"Rotated {turns * 90}°", TextStyle.Default.Size(16));
+                                    .Text($"Rotated {turns * 90}°")
+                                    .FontSize(16);
                             }
                             }
                         });
                         });
                 });
                 });
@@ -689,7 +700,8 @@ namespace QuestPDF.Examples
                                     .MinimalBox()
                                     .MinimalBox()
                                     .Background(Colors.White)
                                     .Background(Colors.White)
                                     .Padding(10)
                                     .Padding(10)
-                                    .Text($"Flipped {turns}", TextStyle.Default.Size(16));
+                                    .Text($"Flipped {turns}")
+                                    .FontSize(16);
                             }
                             }
                         });
                         });
                 });
                 });

+ 1 - 1
QuestPDF.Examples/EnsureSpaceExample.cs

@@ -23,7 +23,7 @@ namespace QuestPDF.Examples
                         page.Size(PageSizes.A7.Landscape());
                         page.Size(PageSizes.A7.Landscape());
                         page.Background(Colors.White);
                         page.Background(Colors.White);
                         
                         
-                        page.Header().Text("With ensure space", TextStyle.Default.SemiBold());
+                        page.Header().Text("With ensure space").SemiBold();
                         
                         
                         page.Content().Column(column =>
                         page.Content().Column(column =>
                         {
                         {

+ 2 - 2
QuestPDF.Examples/FrameExample.cs

@@ -17,9 +17,9 @@ namespace QuestPDF.Examples
                 .Padding(5);
                 .Padding(5);
         }
         }
         
         
-        public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text, TextStyle.Default.SemiBold());
+        public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text).SemiBold();
         public static IContainer ValueCell(this IContainer container) => container.Cell(false);
         public static IContainer ValueCell(this IContainer container) => container.Cell(false);
-        public static void ValueCell(this IContainer container, string text) => container.ValueCell().Text(text, TextStyle.Default);
+        public static void ValueCell(this IContainer container, string text) => container.ValueCell().Text(text);
     }
     }
     
     
     public class FrameExample
     public class FrameExample

+ 2 - 1
QuestPDF.Examples/InlinedExamples.cs

@@ -74,7 +74,8 @@ namespace QuestPDF.Examples
                                                     .PrimaryLayer()
                                                     .PrimaryLayer()
                                                     .AlignCenter()
                                                     .AlignCenter()
                                                     .AlignMiddle()
                                                     .AlignMiddle()
-                                                    .Text(sizeText, TextStyle.Default.Size(15));
+                                                    .Text(sizeText)
+                                                    .FontSize(15);
                                             });
                                             });
                                     }
                                     }
                                 });
                                 });

+ 95 - 0
QuestPDF.Examples/MinimalApiExamples.cs

@@ -0,0 +1,95 @@
+using System.Diagnostics;
+using NUnit.Framework;
+using QuestPDF.Examples.Engine;
+using QuestPDF.Fluent;
+using QuestPDF.Helpers;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Examples
+{
+    public class MinimalApiExamples
+    {
+        [Test]
+        public void MinimalApi()
+        {
+            Document
+                .Create(container =>
+                {
+                    container.Page(page =>
+                    {
+                        page.Size(PageSizes.A4);
+                        page.Margin(2, Unit.Centimetre);
+                        page.Background(Colors.White);
+                        page.DefaultTextStyle(TextStyle.Default.Size(20));
+                        
+                        page.Header()
+                            .Text("Hello PDF!").SemiBold().FontSize(36).FontColor(Colors.Blue.Medium);
+                        
+                        page.Content()
+                            .PaddingVertical(1, Unit.Centimetre)
+                            .Column(x =>
+                            {
+                                x.Spacing(20);
+                                
+                                x.Item().Text(Placeholders.LoremIpsum());
+                                x.Item().Image(Placeholders.Image(200, 100));
+                            });
+                        
+                        page.Footer()
+                            .AlignCenter()
+                            .Text(x =>
+                            {
+                                x.Span("Page ");
+                                x.CurrentPageNumber();
+                            });
+                    });
+                })
+                .GeneratePdf("hello.pdf");
+
+            Process.Start("explorer.exe", "hello.pdf");
+        }
+        
+        [Test]
+        public void MinimalApi2()
+        {
+            RenderingTest
+                .Create()
+                .ProduceImages()
+                .ShowResults()
+                .RenderDocument(container =>
+                {
+                    container.Page(page =>
+                    {
+                        page.Size(PageSizes.A4);
+                        page.Margin(2, Unit.Centimetre);
+                        page.Background(Colors.White);
+                        page.DefaultTextStyle(TextStyle.Default.Size(20));
+                        
+                        page.Header()
+                            .Text("Hello PDF!")
+                            .SemiBold()
+                            .FontSize(36)
+                            .FontColor(Colors.Blue.Medium);
+                        
+                        page.Content()
+                            .PaddingVertical(1, Unit.Centimetre)
+                            .Column(x =>
+                            {
+                                x.Spacing(20);
+                                
+                                x.Item().Text(Placeholders.LoremIpsum());
+                                x.Item().Image(Placeholders.Image(200, 100));
+                            });
+                        
+                        page.Footer()
+                            .AlignCenter()
+                            .Text(x =>
+                            {
+                                x.Span("Page ");
+                                x.CurrentPageNumber();
+                            });
+                    });
+                });
+        }
+    }
+}

+ 2 - 1
QuestPDF.Examples/Padding.cs

@@ -50,7 +50,8 @@ namespace QuestPDF.Examples
                         .Background("FFF")
                         .Background("FFF")
                         .Padding(5)
                         .Padding(5)
                         .AlignCenter()
                         .AlignCenter()
-                        .Text("Sample text", TextStyle.Default.FontType("Segoe UI emoji"));
+                        .Text("Sample text")
+                        .FontFamily("Segoe UI emoji");
                 });
                 });
         }
         }
         
         

+ 49 - 0
QuestPDF.Examples/PageBackgroundForeground.cs

@@ -0,0 +1,49 @@
+using System;
+using System.Linq;
+using NUnit.Framework;
+using QuestPDF.Examples.Engine;
+using QuestPDF.Fluent;
+using QuestPDF.Helpers;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Examples
+{
+    public class PageBackgroundForeground
+    {
+        [Test]
+        public void Test()
+        {
+            RenderingTest
+                .Create()
+                .PageSize(550, 400)
+                .ProducePdf()
+                .ShowResults()
+                .RenderDocument(document =>
+                {
+                    document.Page(page =>
+                    {
+                        page.Size(PageSizes.A4);
+                        page.Margin(1, Unit.Inch);
+                        page.DefaultTextStyle(TextStyle.Default.FontSize(16));
+
+                        page.Foreground()
+                            .AlignMiddle()
+                            .AlignCenter()
+                            .Text("Watermark")
+                            .FontSize(64)
+                            .FontColor(Colors.Blue.Lighten3);
+                        
+                        page.Header().Text("Background and foreground").Bold().FontColor(Colors.Blue.Medium).FontSize(24);
+                        
+                        page.Content().PaddingVertical(25).Column(column =>
+                        {
+                            column.Spacing(25);
+
+                            foreach (var i in Enumerable.Range(0, 100))
+                                column.Item().Background(Colors.Grey.Lighten2).Height(75);
+                        });
+                    });
+                });
+        }
+    }
+}

+ 1 - 1
QuestPDF.Examples/ShowOnceExample.cs

@@ -23,7 +23,7 @@ namespace QuestPDF.Examples
                         page.Size(PageSizes.A7.Landscape());
                         page.Size(PageSizes.A7.Landscape());
                         page.Background(Colors.White);
                         page.Background(Colors.White);
 
 
-                        page.Header().Text("With show once", TextStyle.Default.SemiBold());
+                        page.Header().Text("With show once").SemiBold();
                         
                         
                         page.Content().PaddingVertical(5).Row(row =>
                         page.Content().PaddingVertical(5).Row(row =>
                         {
                         {

+ 2 - 1
QuestPDF.Examples/SkipOnceExample.cs

@@ -31,7 +31,8 @@ namespace QuestPDF.Examples
                         
                         
                         page.Content()
                         page.Content()
                             .PaddingVertical(10)
                             .PaddingVertical(10)
-                            .Text(Placeholders.Paragraphs(), TextStyle.Default.Color(Colors.Grey.Medium));
+                            .Text(Placeholders.Paragraphs())
+                            .FontColor(Colors.Grey.Medium);
                         
                         
                         page.Footer().Text(text =>
                         page.Footer().Text(text =>
                         {
                         {

+ 7 - 7
QuestPDF.Examples/TextBenchmark.cs

@@ -134,8 +134,8 @@ namespace QuestPDF.Examples
                     .AlignBottom()
                     .AlignBottom()
                     .Column(column =>
                     .Column(column =>
                     {
                     {
-                        column.Item().Text("Quo Vadis", TextStyle.Default.Size(72).Bold().Color(Colors.Blue.Darken2));
-                        column.Item().Text("Henryk Sienkiewicz", TextStyle.Default.Size(24).Color(Colors.Grey.Darken2));
+                        column.Item().Text("Quo Vadis").FontSize(72).Bold().FontColor(Colors.Blue.Darken2);
+                        column.Item().Text("Henryk Sienkiewicz").FontSize(24).FontColor(Colors.Grey.Darken2);
                     });
                     });
             }
             }
 
 
@@ -149,8 +149,8 @@ namespace QuestPDF.Examples
                     {
                     {
                         column.Item().InternalLink(chapter.Title).Row(row =>
                         column.Item().InternalLink(chapter.Title).Row(row =>
                         {
                         {
-                            row.RelativeItem().Text(chapter.Title, normalStyle);
-                            row.ConstantItem(100).AlignRight().Text(text => text.PageNumberOfLocation(chapter.Title, normalStyle));
+                            row.RelativeItem().Text(chapter.Title).Style(normalStyle);
+                            row.ConstantItem(100).AlignRight().Text(text => text.BeginPageNumberOfSection(chapter.Title).Style(normalStyle));
                         });
                         });
                     }
                     }
                 });
                 });
@@ -173,7 +173,7 @@ namespace QuestPDF.Examples
                     column.Item().Text(text =>
                     column.Item().Text(text =>
                     {
                     {
                         text.ParagraphSpacing(5);
                         text.ParagraphSpacing(5);
-                        text.Span(content, normalStyle);
+                        text.Span(content).Style(normalStyle);
                     });
                     });
                     
                     
                     column.Item().PageBreak();
                     column.Item().PageBreak();
@@ -191,7 +191,7 @@ namespace QuestPDF.Examples
                         text.DefaultTextStyle(normalStyle);
                         text.DefaultTextStyle(normalStyle);
                         
                         
                         text.Span("Ten dokument został wygenerowany na podstawie książki w formacie TXT opublikowanej w serwisie ");
                         text.Span("Ten dokument został wygenerowany na podstawie książki w formacie TXT opublikowanej w serwisie ");
-                        text.ExternalLocation("wolnelektury.pl", "https://wolnelektury.pl/", normalStyle.Color(Colors.Blue.Medium).Underline());
+                        text.Hyperlink("wolnelektury.pl", "https://wolnelektury.pl/").FontColor(Colors.Blue.Medium).Underline();
                         text.Span(". Dziękuję za wspieranie polskiego czytelnictwa!");
                         text.Span(". Dziękuję za wspieranie polskiego czytelnictwa!");
                     });
                     });
                 });
                 });
@@ -199,7 +199,7 @@ namespace QuestPDF.Examples
 
 
             void SectionTitle(ColumnDescriptor column, string text)
             void SectionTitle(ColumnDescriptor column, string text)
             {
             {
-                column.Item().Location(text).Text(text, subtitleStyle);
+                column.Item().Location(text).Text(text).Style(subtitleStyle);
                 column.Item().PaddingTop(10).PaddingBottom(50).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).ExtendHorizontal();
                 column.Item().PaddingTop(10).PaddingBottom(50).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).ExtendHorizontal();
             }
             }
             
             

+ 15 - 13
QuestPDF.Examples/TextExamples.cs

@@ -29,9 +29,9 @@ namespace QuestPDF.Examples
                         .Padding(10)
                         .Padding(10)
                         .Text(text =>
                         .Text(text =>
                         {
                         {
-                            text.DefaultTextStyle(TextStyle.Default.Size(20));
+                            text.DefaultTextStyle(TextStyle.Default.FontSize(20));
                             text.Span("This is a normal text, followed by an ");
                             text.Span("This is a normal text, followed by an ");
-                            text.Span("underlined red text", TextStyle.Default.Color(Colors.Red.Medium).Underline());
+                            text.Span("underlined red text").FontColor(Colors.Red.Medium).Underline();
                             text.Span(".");
                             text.Span(".");
                         });
                         });
                 });
                 });
@@ -58,7 +58,7 @@ namespace QuestPDF.Examples
     
     
                             foreach (var i in Enumerable.Range(1, 3))
                             foreach (var i in Enumerable.Range(1, 3))
                             {
                             {
-                                text.Span($"Paragraph {i}: ", TextStyle.Default.SemiBold());
+                                text.Span($"Paragraph {i}: ").SemiBold();
                                 text.Line(Placeholders.Paragraph());
                                 text.Line(Placeholders.Paragraph());
                             }
                             }
                         });
                         });
@@ -121,7 +121,7 @@ namespace QuestPDF.Examples
 
 
                             text.Line(Placeholders.LoremIpsum());
                             text.Line(Placeholders.LoremIpsum());
 
 
-                            text.Span($"This is target text that should show up. {DateTime.UtcNow:T} > This is a short sentence that will be wrapped into second line hopefully, right? <", TextStyle.Default.Underline());
+                            text.Span($"This is target text that should show up. {DateTime.UtcNow:T} > This is a short sentence that will be wrapped into second line hopefully, right? <").Underline();
                         });
                         });
                 });
                 });
         }
         }
@@ -174,6 +174,8 @@ namespace QuestPDF.Examples
                         .Padding(10)
                         .Padding(10)
                         .Text(text =>
                         .Text(text =>
                         {
                         {
+                            text.DefaultTextStyle(x => x.Bold());
+                            
                             text.DefaultTextStyle(TextStyle.Default);
                             text.DefaultTextStyle(TextStyle.Default);
                             text.AlignLeft();
                             text.AlignLeft();
                             text.ParagraphSpacing(10);
                             text.ParagraphSpacing(10);
@@ -183,9 +185,9 @@ namespace QuestPDF.Examples
                             text.EmptyLine();
                             text.EmptyLine();
 
 
                             text.Span("This text is a normal text, ");
                             text.Span("This text is a normal text, ");
-                            text.Span("this is a bold text, ", TextStyle.Default.Bold());
-                            text.Span("this is a red and underlined text, ", TextStyle.Default.Color(Colors.Red.Medium).Underline());
-                            text.Span("and this is slightly bigger text.", TextStyle.Default.Size(16));
+                            text.Span("this is a bold text, ").Bold();
+                            text.Span("this is a red and underlined text, ").FontColor(Colors.Red.Medium).Underline();
+                            text.Span("and this is slightly bigger text.").FontSize(16);
 
 
                             text.EmptyLine();
                             text.EmptyLine();
 
 
@@ -211,7 +213,7 @@ namespace QuestPDF.Examples
                             
                             
                             text.EmptyLine();
                             text.EmptyLine();
 
 
-                            text.Span(Placeholders.Paragraphs(), TextStyle.Default.Italic());
+                            text.Span(Placeholders.Paragraphs()).Italic();
                             
                             
                             text.Line("This is target text that does not show up. " + Placeholders.Paragraph());
                             text.Line("This is target text that does not show up. " + Placeholders.Paragraph());
                         });
                         });
@@ -242,9 +244,9 @@ namespace QuestPDF.Examples
                             text.ParagraphSpacing(10);
                             text.ParagraphSpacing(10);
 
 
                             text.Span("This text is a normal text, ");
                             text.Span("This text is a normal text, ");
-                            text.Span("this is a bold text, ", TextStyle.Default.Bold());
-                            text.Span("this is a red and underlined text, ", TextStyle.Default.Color(Colors.Red.Medium).Underline());
-                            text.Span("and this is slightly bigger text.", TextStyle.Default.Size(16));
+                            text.Span("this is a bold text, ").Bold();
+                            text.Span("this is a red and underlined text, ").FontColor(Colors.Red.Medium).Underline();
+                            text.Span("and this is slightly bigger text.").FontSize(16);
                             
                             
                             text.Span("The new text element also supports injecting custom content between words: ");
                             text.Span("The new text element also supports injecting custom content between words: ");
                             text.Element().PaddingBottom(-4).Height(16).Width(32).Image(Placeholders.Image);
                             text.Element().PaddingBottom(-4).Height(16).Width(32).Image(Placeholders.Image);
@@ -314,7 +316,7 @@ namespace QuestPDF.Examples
 
 
                         page.Content().Text( 
                         page.Content().Text( 
                             "         ",
                             "         ",
-                            TextStyle.Default.Size(11).BackgroundColor(Colors.Red.Lighten3));
+                            TextStyle.Default.FontSize(11).BackgroundColor(Colors.Red.Lighten3));
                     });
                     });
                 });
                 });
         }
         }
@@ -339,7 +341,7 @@ namespace QuestPDF.Examples
 
 
                         page.Content().Text( 
                         page.Content().Text( 
                             "     x     ",
                             "     x     ",
-                            TextStyle.Default.Size(11).BackgroundColor(Colors.Red.Lighten3));
+                            TextStyle.Default.FontSize(11).BackgroundColor(Colors.Red.Lighten3));
                     });
                     });
                 });
                 });
         }
         }

+ 28 - 0
QuestPDF.ReportSample/Helpers.cs

@@ -1,4 +1,5 @@
 using System;
 using System;
+using System.Collections.Concurrent;
 using System.IO;
 using System.IO;
 using SkiaSharp;
 using SkiaSharp;
 
 
@@ -24,5 +25,32 @@ namespace QuestPDF.ReportSample
                 Latitude = Helpers.Random.NextDouble() * 180f - 90f
                 Latitude = Helpers.Random.NextDouble() * 180f - 90f
             };
             };
         }
         }
+
+        private static readonly ConcurrentDictionary<int, string> RomanNumeralCache = new ConcurrentDictionary<int, string>();
+
+        public static string FormatAsRomanNumeral(this int number)
+        {
+            if (number < 0 || number > 3999) 
+                throw new ArgumentOutOfRangeException("Number should be in range from 1 to 3999");
+            
+            return RomanNumeralCache.GetOrAdd(number, x =>
+            {
+                if (x >= 1000) return "M" + FormatAsRomanNumeral(x - 1000);
+                if (x >= 900) return "CM" + FormatAsRomanNumeral(x - 900); 
+                if (x >= 500) return "D" + FormatAsRomanNumeral(x - 500);
+                if (x >= 400) return "CD" + FormatAsRomanNumeral(x - 400);
+                if (x >= 100) return "C" + FormatAsRomanNumeral(x - 100);            
+                if (x >= 90) return "XC" + FormatAsRomanNumeral(x - 90);
+                if (x >= 50) return "L" + FormatAsRomanNumeral(x - 50);
+                if (x >= 40) return "XL" + FormatAsRomanNumeral(x - 40);
+                if (x >= 10) return "X" + FormatAsRomanNumeral(x - 10);
+                if (x >= 9) return "IX" + FormatAsRomanNumeral(x - 9);
+                if (x >= 5) return "V" + FormatAsRomanNumeral(x - 5);
+                if (x >= 4) return "IV" + FormatAsRomanNumeral(x - 4);
+                if (x >= 1) return "I" + FormatAsRomanNumeral(x - 1);
+                
+                return string.Empty;  
+            });
+        }
     }
     }
 }
 }

+ 2 - 2
QuestPDF.ReportSample/Layouts/DifferentHeadersTemplate.cs

@@ -30,12 +30,12 @@ namespace QuestPDF.ReportSample.Layouts
             {
             {
                 column.Item().ShowOnce().Padding(5).AlignMiddle().Row(row =>
                 column.Item().ShowOnce().Padding(5).AlignMiddle().Row(row =>
                 {
                 {
-                    row.RelativeItem(2).AlignMiddle().Text("PRIMARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
+                    row.RelativeItem(2).AlignMiddle().Text("PRIMARY HEADER").FontColor(Colors.Grey.Darken3).FontSize(30).Bold();
                     row.RelativeItem(1).AlignRight().MinimalBox().AlignMiddle().Background(Colors.Blue.Darken2).Padding(30);
                     row.RelativeItem(1).AlignRight().MinimalBox().AlignMiddle().Background(Colors.Blue.Darken2).Padding(30);
                 });
                 });
                 column.Item().SkipOnce().Padding(5).Row(row =>
                 column.Item().SkipOnce().Padding(5).Row(row =>
                 {
                 {
-                    row.RelativeItem(2).Text("SECONDARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
+                    row.RelativeItem(2).Text("SECONDARY HEADER").FontColor(Colors.Grey.Darken3).FontSize(30).Bold();
                     row.RelativeItem(1).AlignRight().MinimalBox().Background(Colors.Blue.Lighten4).Padding(15);
                     row.RelativeItem(1).AlignRight().MinimalBox().Background(Colors.Blue.Lighten4).Padding(15);
                 });
                 });
             });
             });

+ 3 - 2
QuestPDF.ReportSample/Layouts/SectionTemplate.cs

@@ -23,7 +23,8 @@ namespace QuestPDF.ReportSample.Layouts
                     decoration
                     decoration
                         .Before()
                         .Before()
                         .PaddingBottom(5)
                         .PaddingBottom(5)
-                        .Text(Model.Title, Typography.Headline);
+                        .Text(Model.Title)
+                        .Style(Typography.Headline);
 
 
                     decoration.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Column(column =>
                     decoration.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Column(column =>
                     {
                     {
@@ -69,7 +70,7 @@ namespace QuestPDF.ReportSample.Layouts
         {
         {
             if (model.PhotoCount == 0)
             if (model.PhotoCount == 0)
             {
             {
-                container.Text("No photos", Typography.Normal);
+                container.Text("No photos").Style(Typography.Normal);
                 return;
                 return;
             }
             }
 
 

+ 4 - 4
QuestPDF.ReportSample/Layouts/StandardReport.cs

@@ -39,9 +39,9 @@ namespace QuestPDF.ReportSample.Layouts
                     
                     
                     page.Footer().AlignCenter().Text(text =>
                     page.Footer().AlignCenter().Text(text =>
                     {
                     {
-                        text.CurrentPageNumber();
+                        text.CurrentPageNumber().Format(x => x?.FormatAsRomanNumeral() ?? "-----");
                         text.Span(" / ");
                         text.Span(" / ");
-                        text.TotalPages();
+                        text.TotalPages().Format(x => x?.FormatAsRomanNumeral() ?? "-----");
                     });
                     });
                 });
                 });
         }
         }
@@ -54,7 +54,7 @@ namespace QuestPDF.ReportSample.Layouts
                 {
                 {
                     row.Spacing(50);
                     row.Spacing(50);
                     
                     
-                    row.RelativeItem().PaddingTop(-10).Text(Model.Title, Typography.Title);
+                    row.RelativeItem().PaddingTop(-10).Text(Model.Title).Style(Typography.Title);
                     row.ConstantItem(90).ExternalLink("https://www.questpdf.com").MaxHeight(30).Component<ImagePlaceholder>();
                     row.ConstantItem(90).ExternalLink("https://www.questpdf.com").MaxHeight(30).Component<ImagePlaceholder>();
                 });
                 });
 
 
@@ -69,7 +69,7 @@ namespace QuestPDF.ReportSample.Layouts
                     {
                     {
                         grid.Item().Text(text =>
                         grid.Item().Text(text =>
                         {
                         {
-                            text.Span($"{field.Label}: ", TextStyle.Default.SemiBold());
+                            text.Span($"{field.Label}: ").SemiBold();
                             text.Span(field.Value);
                             text.Span(field.Value);
                         });
                         });
                     }
                     }

+ 16 - 2
QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs

@@ -1,5 +1,7 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Drawing;
 using QuestPDF.Fluent;
 using QuestPDF.Fluent;
+using QuestPDF.Helpers;
 using QuestPDF.Infrastructure;
 using QuestPDF.Infrastructure;
 
 
 namespace QuestPDF.ReportSample.Layouts
 namespace QuestPDF.ReportSample.Layouts
@@ -21,7 +23,8 @@ namespace QuestPDF.ReportSample.Layouts
                     decoration
                     decoration
                         .Before()
                         .Before()
                         .PaddingBottom(5)
                         .PaddingBottom(5)
-                        .Text("Table of contents", Typography.Headline);
+                        .Text("Table of contents")
+                        .Style(Typography.Headline);
 
 
                     decoration.Content().Column(column =>
                     decoration.Content().Column(column =>
                     {
                     {
@@ -43,7 +46,18 @@ namespace QuestPDF.ReportSample.Layouts
                 {
                 {
                     row.ConstantItem(25).Text($"{number}.");
                     row.ConstantItem(25).Text($"{number}.");
                     row.RelativeItem().Text(locationName);
                     row.RelativeItem().Text(locationName);
-                    row.ConstantItem(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName));
+                    row.ConstantItem(150).AlignRight().Text(text =>
+                    {
+                        text.BeginPageNumberOfSection(locationName);
+                        text.Span(" - ");
+                        text.EndPageNumberOfSection(locationName);
+
+                        var lengthStyle = TextStyle.Default.Color(Colors.Grey.Medium);
+                        
+                        text.Span(" (").Style(lengthStyle);
+                        text.TotalPagesWithinSection(locationName).Style(lengthStyle).Format(x => x == 1 ? "1 page long" : $"{x} pages long");
+                        text.Span(")").Style(lengthStyle);
+                    });
                 });
                 });
         }
         }
     }
     }

+ 1 - 1
QuestPDF.UnitTests/ExternalLinkTests.cs

@@ -8,7 +8,7 @@ namespace QuestPDF.UnitTests
     public class ExternalLinkTests
     public class ExternalLinkTests
     {
     {
         [Test]
         [Test]
-        public void Measure() => SimpleContainerTests.Measure<ExternalLink>();
+        public void Measure() => SimpleContainerTests.Measure<Hyperlink>();
         
         
         // TODO: consider tests for the Draw method
         // TODO: consider tests for the Draw method
     }
     }

+ 146 - 0
QuestPDF.UnitTests/FontStyleSetTests.cs

@@ -0,0 +1,146 @@
+using FluentAssertions;
+using NUnit.Framework;
+using QuestPDF.Drawing;
+using SkiaSharp;
+using static SkiaSharp.SKFontStyleSlant;
+
+namespace QuestPDF.UnitTests
+{
+    [TestFixture]
+    public class FontStyleSetTests
+    {
+        private void ExpectComparisonOrder(SKFontStyle target, SKFontStyle[] styles)
+        {
+            for (var i = 0; i < styles.Length - 1; i++)
+            {
+                var currentStyle = styles[i];
+                var nextStyle = styles[i + 1];
+                
+                FontStyleSet.IsBetterMatch(target, currentStyle, nextStyle).Should().BeTrue();
+                FontStyleSet.IsBetterMatch(target, nextStyle, currentStyle).Should().BeFalse();
+            }
+        }
+
+        [Test]
+        public void FontStyleSet_IsBetterMatch_CondensedWidth()
+        {
+            var styles = new[]
+            {
+                new SKFontStyle(500, 5, Upright),
+                new SKFontStyle(500, 4, Upright),
+                new SKFontStyle(500, 3, Upright),
+                new SKFontStyle(500, 6, Upright)
+            };
+            
+            ExpectComparisonOrder(new SKFontStyle(500, 5, Upright), styles);
+        }
+
+        [Test]
+        public void FontStyleSet_IsBetterMatch_ExpandedWidth()
+        {
+            var styles = new[]
+            {
+                new SKFontStyle(500, 6, Upright),
+                new SKFontStyle(500, 7, Upright),
+                new SKFontStyle(500, 8, Upright),
+                new SKFontStyle(500, 5, Upright)
+            };
+            
+            ExpectComparisonOrder(new SKFontStyle(500, 6, Upright), styles);
+        }
+
+        [Test]
+        public void FontStyleSet_IsBetterMatch_ItalicSlant()
+        {
+            var styles = new[]
+            {
+                new SKFontStyle(500, 5, Italic),
+                new SKFontStyle(500, 5, Oblique),
+                new SKFontStyle(500, 5, Upright)
+            };
+        
+            ExpectComparisonOrder(new SKFontStyle(500, 5, Italic), styles);
+        }
+
+        [Test]
+        public void FontStyleSet_IsBetterMatch_ObliqueSlant()
+        {
+            var styles = new[]
+            {
+                new SKFontStyle(500, 5, Oblique),
+                new SKFontStyle(500, 5, Italic),
+                new SKFontStyle(500, 5, Upright)
+            };
+        
+            ExpectComparisonOrder(new SKFontStyle(500, 5, Oblique), styles);
+        }
+
+        [Test]
+        public void FontStyleSet_IsBetterMatch_UprightSlant()
+        {
+            var styles = new[]
+            {
+                new SKFontStyle(500, 5, Upright),
+                new SKFontStyle(500, 5, Oblique),
+                new SKFontStyle(500, 5, Italic)
+            };
+        
+            ExpectComparisonOrder(new SKFontStyle(500, 5, Upright), styles);
+        }
+
+        [Test]
+        public void FontStyleSet_IsBetterMatch_ThinWeight()
+        {
+            var styles = new[]
+            {
+                new SKFontStyle(300, 5, Upright),
+                new SKFontStyle(200, 5, Upright),
+                new SKFontStyle(100, 5, Upright),
+                new SKFontStyle(400, 5, Upright)
+            };
+        
+            ExpectComparisonOrder(new SKFontStyle(300, 5, Upright), styles);
+        }
+
+        [Test]
+        public void FontStyleSet_IsBetterMatch_RegularWeight()
+        {
+            var styles = new[]
+            {
+                new SKFontStyle(500, 5, Upright),
+                new SKFontStyle(300, 5, Upright),
+                new SKFontStyle(100, 5, Upright),
+                new SKFontStyle(600, 5, Upright)
+            };
+        
+            ExpectComparisonOrder(new SKFontStyle(400, 5, Upright), styles);
+        }
+
+        [Test]
+        public void FontStyleSet_IsBetterMatch_BoldWeight()
+        {
+            var styles = new[]
+            {
+                new SKFontStyle(600, 5, Upright),
+                new SKFontStyle(700, 5, Upright),
+                new SKFontStyle(800, 5, Upright),
+                new SKFontStyle(500, 5, Upright)
+            };
+        
+            ExpectComparisonOrder(new SKFontStyle(600, 5, Upright), styles);
+        }
+
+        [Test]
+        public void FontStyleSet_RespectsPriority()
+        {
+            var styles = new[]
+            {
+                new SKFontStyle(600, 5, Italic),
+                new SKFontStyle(600, 6, Upright),
+                new SKFontStyle(500, 6, Italic)
+            };
+        
+            ExpectComparisonOrder(new SKFontStyle(500, 5, Upright), styles);
+        }
+    }
+}

+ 1 - 1
QuestPDF.UnitTests/InternalLinkTests.cs

@@ -8,7 +8,7 @@ namespace QuestPDF.UnitTests
     public class InternalLinkTests
     public class InternalLinkTests
     {
     {
         [Test]
         [Test]
-        public void Measure() => SimpleContainerTests.Measure<InternalLink>();
+        public void Measure() => SimpleContainerTests.Measure<SectionLink>();
         
         
         // TODO: consider tests for the Draw method
         // TODO: consider tests for the Draw method
     }
     }

+ 1 - 1
QuestPDF.UnitTests/InternalLocationTests.cs

@@ -8,7 +8,7 @@ namespace QuestPDF.UnitTests
     public class InternalLocationTests
     public class InternalLocationTests
     {
     {
         [Test]
         [Test]
-        public void Measure() => SimpleContainerTests.Measure<InternalLink>();
+        public void Measure() => SimpleContainerTests.Measure<SectionLink>();
         
         
         // TODO: consider tests for the Draw method
         // TODO: consider tests for the Draw method
     }
     }

+ 4 - 4
QuestPDF.UnitTests/TestEngine/MockCanvas.cs

@@ -20,9 +20,9 @@ namespace QuestPDF.UnitTests.TestEngine
         public void DrawRectangle(Position vector, Size size, string color) => DrawRectFunc(vector, size, color);
         public void DrawRectangle(Position vector, Size size, string color) => DrawRectFunc(vector, size, color);
         public void DrawText(string text, Position position, TextStyle style) => DrawTextFunc(text, position, style);
         public void DrawText(string text, Position position, TextStyle style) => DrawTextFunc(text, position, style);
         public void DrawImage(SKImage image, Position position, Size size) => DrawImageFunc(image, position, size);
         public void DrawImage(SKImage image, Position position, Size size) => DrawImageFunc(image, position, size);
-        
-        public void DrawExternalLink(string url, Size size) => throw new NotImplementedException();
-        public void DrawLocationLink(string locationName, Size size) => throw new NotImplementedException();
-        public void DrawLocation(string locationName) => throw new NotImplementedException();
+
+        public void DrawHyperlink(string url, Size size) => throw new NotImplementedException();
+        public void DrawSectionLink(string sectionName, Size size) => throw new NotImplementedException();
+        public void DrawSection(string sectionName) => throw new NotImplementedException();
     }
     }
 }
 }

+ 3 - 3
QuestPDF.UnitTests/TestEngine/OperationRecordingCanvas.cs

@@ -18,8 +18,8 @@ namespace QuestPDF.UnitTests.TestEngine
         public void DrawText(string text, Position position, TextStyle style) => Operations.Add(new CanvasDrawTextOperation(text, position, style));
         public void DrawText(string text, Position position, TextStyle style) => Operations.Add(new CanvasDrawTextOperation(text, position, style));
         public void DrawImage(SKImage image, Position position, Size size) => Operations.Add(new CanvasDrawImageOperation(position, size));
         public void DrawImage(SKImage image, Position position, Size size) => Operations.Add(new CanvasDrawImageOperation(position, size));
         
         
-        public void DrawExternalLink(string url, Size size) => throw new NotImplementedException();
-        public void DrawLocationLink(string locationName, Size size) => throw new NotImplementedException();
-        public void DrawLocation(string locationName) => throw new NotImplementedException();
+        public void DrawHyperlink(string url, Size size) => throw new NotImplementedException();
+        public void DrawSectionLink(string sectionName, Size size) => throw new NotImplementedException();
+        public void DrawSection(string sectionName) => throw new NotImplementedException();
     }
     }
 }
 }

+ 1 - 1
QuestPDF.UnitTests/TestEngine/TestPlan.cs

@@ -92,7 +92,7 @@ namespace QuestPDF.UnitTests.TestEngine
                     Assert.AreEqual(expected.Position.Y, position.Y, "Draw text: Y");
                     Assert.AreEqual(expected.Position.Y, position.Y, "Draw text: Y");
                     
                     
                     Assert.AreEqual(expected.Style.Color, style.Color, "Draw text: color");
                     Assert.AreEqual(expected.Style.Color, style.Color, "Draw text: color");
-                    Assert.AreEqual(expected.Style.FontType, style.FontType, "Draw text: font");
+                    Assert.AreEqual(expected.Style.FontFamily, style.FontFamily, "Draw text: font");
                     Assert.AreEqual(expected.Style.Size, style.Size, "Draw text: size");
                     Assert.AreEqual(expected.Style.Size, style.Size, "Draw text: size");
                 },
                 },
                 DrawImageFunc = (image, position, size) =>
                 DrawImageFunc = (image, position, size) =>

+ 53 - 17
QuestPDF/Drawing/FontManager.cs

@@ -1,6 +1,7 @@
 using System;
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Concurrent;
 using System.IO;
 using System.IO;
+using System.Linq;
 using QuestPDF.Infrastructure;
 using QuestPDF.Infrastructure;
 using SkiaSharp;
 using SkiaSharp;
 
 
@@ -8,16 +9,41 @@ namespace QuestPDF.Drawing
 {
 {
     public static class FontManager
     public static class FontManager
     {
     {
-        private static ConcurrentDictionary<string, SKTypeface> Typefaces = new ConcurrentDictionary<string, SKTypeface>();
-        private static ConcurrentDictionary<string, SKFontMetrics> FontMetrics = new ConcurrentDictionary<string, SKFontMetrics>();
-        private static ConcurrentDictionary<string, SKPaint> Paints = new ConcurrentDictionary<string, SKPaint>();
-        private static ConcurrentDictionary<string, SKPaint> ColorPaint = new ConcurrentDictionary<string, SKPaint>();
+        private static ConcurrentDictionary<string, FontStyleSet> StyleSets = new();
+        private static ConcurrentDictionary<object, SKFontMetrics> FontMetrics = new();
+        private static ConcurrentDictionary<object, SKPaint> Paints = new();
+        private static ConcurrentDictionary<string, SKPaint> ColorPaint = new();
 
 
+        private static void RegisterFontType(SKData fontData, string? customName = null)
+        {
+            foreach (var index in Enumerable.Range(0, 256))
+            {
+                var typeface = SKTypeface.FromData(fontData, index);
+                
+                if (typeface == null)
+                    break;
+                
+                var typefaceName = customName ?? typeface.FamilyName;
+
+                var fontStyleSet = StyleSets.GetOrAdd(typefaceName, _ => new FontStyleSet());
+                fontStyleSet.Add(typeface);
+            }
+        }
+
+        [Obsolete("Since version 2022.3, the FontManager class offers better font type matching support. Please use the RegisterFont(Stream stream) method.")]
         public static void RegisterFontType(string fontName, Stream stream)
         public static void RegisterFontType(string fontName, Stream stream)
         {
         {
-            Typefaces.TryAdd(fontName, SKTypeface.FromStream(stream));
+            using var fontData = SKData.Create(stream);
+            RegisterFontType(fontData);
+            RegisterFontType(fontData, customName: fontName);
         }
         }
-        
+
+        public static void RegisterFont(Stream stream)
+        {
+            using var fontData = SKData.Create(stream);
+            RegisterFontType(fontData);
+        }
+
         internal static SKPaint ColorToPaint(this string color)
         internal static SKPaint ColorToPaint(this string color)
         {
         {
             return ColorPaint.GetOrAdd(color, Convert);
             return ColorPaint.GetOrAdd(color, Convert);
@@ -30,37 +56,47 @@ namespace QuestPDF.Drawing
                 };
                 };
             }
             }
         }
         }
-        
+
         internal static SKPaint ToPaint(this TextStyle style)
         internal static SKPaint ToPaint(this TextStyle style)
         {
         {
-            return Paints.GetOrAdd(style.Key, key => Convert(style));
-            
+            return Paints.GetOrAdd(style.PaintKey, key => Convert(style));
+
             static SKPaint Convert(TextStyle style)
             static SKPaint Convert(TextStyle style)
             {
             {
                 return new SKPaint
                 return new SKPaint
                 {
                 {
                     Color = SKColor.Parse(style.Color),
                     Color = SKColor.Parse(style.Color),
                     Typeface = GetTypeface(style),
                     Typeface = GetTypeface(style),
-                    TextSize = (style.Size ?? 12),
-                    TextEncoding = SKTextEncoding.Utf32
+                    TextSize = style.Size ?? 12
                 };
                 };
             }
             }
 
 
             static SKTypeface GetTypeface(TextStyle style)
             static SKTypeface GetTypeface(TextStyle style)
             {
             {
-                if (Typefaces.TryGetValue(style.FontType, out var result))
-                    return result;
-                
+                var weight = (SKFontStyleWeight)(style.FontWeight ?? FontWeight.Normal);
                 var slant = (style.IsItalic ?? false) ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
                 var slant = (style.IsItalic ?? false) ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
+
+                var fontStyle = new SKFontStyle(weight, SKFontStyleWidth.Normal, slant);
+
+                if (StyleSets.TryGetValue(style.FontFamily, out var fontStyleSet))
+                    return fontStyleSet.Match(fontStyle);
+
+                var fontFromDefaultSource = SKFontManager.Default.MatchFamily(style.FontFamily, fontStyle);
+                
+                if (fontFromDefaultSource != null)
+                    return fontFromDefaultSource;
                 
                 
-                return SKTypeface.FromFamilyName(style.FontType, (int)(style.FontWeight ?? FontWeight.Normal), (int)SKFontStyleWidth.Normal, slant) 
-                       ?? throw new ArgumentException($"The typeface {style.FontType} could not be found.");
+                throw new ArgumentException(
+                    $"The typeface '{style.FontFamily}' could not be found. " +
+                    $"Please consider the following options: " +
+                    $"1) install the font on your operating system or execution environment. " +
+                    $"2) load a font file specifically for QuestPDF usage via the QuestPDF.Drawing.FontManager.RegisterFontType(Stream fileContentStream) static method.");
             }
             }
         }
         }
 
 
         internal static SKFontMetrics ToFontMetrics(this TextStyle style)
         internal static SKFontMetrics ToFontMetrics(this TextStyle style)
         {
         {
-            return FontMetrics.GetOrAdd(style.Key, key => style.ToPaint().FontMetrics);
+            return FontMetrics.GetOrAdd(style.FontMetricsKey, key => style.ToPaint().FontMetrics);
         }
         }
     }
     }
 }
 }

+ 132 - 0
QuestPDF/Drawing/FontStyleSet.cs

@@ -0,0 +1,132 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Concurrent;
+using SkiaSharp;
+
+namespace QuestPDF.Drawing
+{
+    internal class FontStyleSet
+    {
+        private ConcurrentDictionary<SKFontStyle, SKTypeface> Styles { get; } = new();
+
+        public void Add(SKTypeface typeface)
+        {
+            var style = typeface.FontStyle;
+            Styles.AddOrUpdate(style, _ => typeface, (_, _) => typeface);
+        }
+
+        public SKTypeface? Match(SKFontStyle target)
+        {
+            SKFontStyle? bestStyle = null;
+            SKTypeface? bestTypeface = null;
+
+            foreach (var entry in Styles)
+            {
+                if (IsBetterMatch(target, entry.Key, bestStyle))
+                {
+                    bestStyle = entry.Key;
+                    bestTypeface = entry.Value;
+                }
+            }
+
+            return bestTypeface;
+        }
+
+        private static Dictionary<SKFontStyleSlant, List<SKFontStyleSlant>> SlantFallbacks = new()
+        {
+            { SKFontStyleSlant.Italic, new() { SKFontStyleSlant.Italic, SKFontStyleSlant.Oblique, SKFontStyleSlant.Upright } },
+            { SKFontStyleSlant.Oblique, new() { SKFontStyleSlant.Oblique, SKFontStyleSlant.Italic, SKFontStyleSlant.Upright } },
+            { SKFontStyleSlant.Upright, new() { SKFontStyleSlant.Upright, SKFontStyleSlant.Oblique, SKFontStyleSlant.Italic } },
+        };
+
+        // Checks whether style a is a better match for the target then style b. Uses the CSS font style matching algorithm
+        internal static bool IsBetterMatch(SKFontStyle? target, SKFontStyle? a, SKFontStyle? b)
+        {
+            // A font is better than no font
+            if (b == null) 
+                return true;
+            
+            if (a == null) 
+                return false;
+
+            // First check font width
+            // For normal and condensed widths prefer smaller widths
+            // For expanded widths prefer larger widths
+            if (target.Width <= (int)SKFontStyleWidth.Normal)
+            {
+                if (a.Width <= target.Width && b.Width > target.Width) 
+                    return true;
+                
+                if (a.Width > target.Width && b.Width <= target.Width) 
+                    return false;
+            }
+            else
+            {
+                if (a.Width >= target.Width && b.Width < target.Width) 
+                    return true;
+                
+                if (a.Width < target.Width && b.Width >= target.Width) 
+                    return false;
+            }
+
+            // Prefer closest match
+            var widthDifferenceA = Math.Abs(a.Width - target.Width);
+            var widthDifferenceB = Math.Abs(b.Width - target.Width);
+
+            if (widthDifferenceA < widthDifferenceB) 
+                return true;
+            
+            if (widthDifferenceB < widthDifferenceA) 
+                return false;
+
+            // Prefer closest slant based on provided fallback list
+            var slantFallback = SlantFallbacks[target.Slant];
+            var slantIndexA = slantFallback.IndexOf(a.Slant);
+            var slantIndexB = slantFallback.IndexOf(b.Slant);
+
+            if (slantIndexA < slantIndexB) 
+                return true;
+            
+            if (slantIndexB < slantIndexA) 
+                return false;
+
+            // Check weight last
+            // For thin (<400) weights, prefer thinner weights
+            // For regular (400-500) weights, prefer other regular weights, then use rule for thin or bold
+            // For bold (>500) weights, prefer thicker weights
+            // Behavior for values other than multiples of 100 is not given in the specification
+
+            if (target.Weight >= 400 && target.Weight <= 500)
+            {
+                if ((a.Weight >= 400 && a.Weight <= 500) && !(b.Weight >= 400 && b.Weight <= 500)) 
+                    return true;
+                
+                if (!(a.Weight >= 400 && a.Weight <= 500) && (b.Weight >= 400 && b.Weight <= 500)) 
+                    return false;
+            }
+
+            if (target.Weight < 450)
+            {
+                if (a.Weight <= target.Weight && b.Weight > target.Weight) 
+                    return true;
+                
+                if (a.Weight > target.Weight && b.Weight <= target.Weight)
+                    return false;
+            }
+            else
+            {
+                if (a.Weight >= target.Weight && b.Weight < target.Weight) 
+                    return true;
+                
+                if (a.Weight < target.Weight && b.Weight >= target.Weight) 
+                    return false;
+            }
+
+            // Prefer closest weight
+            var weightDifferenceA = Math.Abs(a.Weight - target.Weight);
+            var weightDifferenceB = Math.Abs(b.Weight - target.Weight);
+
+            return weightDifferenceA < weightDifferenceB;
+        }
+    }
+}

+ 3 - 3
QuestPDF/Drawing/FreeCanvas.cs

@@ -51,17 +51,17 @@ namespace QuestPDF.Drawing
             
             
         }
         }
 
 
-        public void DrawExternalLink(string url, Size size)
+        public void DrawHyperlink(string url, Size size)
         {
         {
            
            
         }
         }
 
 
-        public void DrawLocationLink(string locationName, Size size)
+        public void DrawSectionLink(string sectionName, Size size)
         {
         {
             
             
         }
         }
 
 
-        public void DrawLocation(string locationName)
+        public void DrawSection(string sectionName)
         {
         {
             
             
         }
         }

+ 5 - 5
QuestPDF/Drawing/SkiaCanvasBase.cs

@@ -37,19 +37,19 @@ namespace QuestPDF.Drawing
             Canvas.DrawImage(image, new SKRect(vector.X, vector.Y, size.Width, size.Height));
             Canvas.DrawImage(image, new SKRect(vector.X, vector.Y, size.Width, size.Height));
         }
         }
 
 
-        public void DrawExternalLink(string url, Size size)
+        public void DrawHyperlink(string url, Size size)
         {
         {
             Canvas.DrawUrlAnnotation(new SKRect(0, 0, size.Width, size.Height), url);
             Canvas.DrawUrlAnnotation(new SKRect(0, 0, size.Width, size.Height), url);
         }
         }
         
         
-        public void DrawLocationLink(string locationName, Size size)
+        public void DrawSectionLink(string sectionName, Size size)
         {
         {
-            Canvas.DrawLinkDestinationAnnotation(new SKRect(0, 0, size.Width, size.Height), locationName);
+            Canvas.DrawLinkDestinationAnnotation(new SKRect(0, 0, size.Width, size.Height), sectionName);
         }
         }
 
 
-        public void DrawLocation(string locationName)
+        public void DrawSection(string sectionName)
         {
         {
-            Canvas.DrawNamedDestinationAnnotation(new SKPoint(0, 0), locationName);
+            Canvas.DrawNamedDestinationAnnotation(new SKPoint(0, 0), sectionName);
         }
         }
 
 
         public void Rotate(float angle)
         public void Rotate(float angle)

+ 4 - 1
QuestPDF/Elements/DebugArea.cs

@@ -30,7 +30,10 @@ namespace QuestPDF.Elements
                         .MinimalBox()
                         .MinimalBox()
                         .Background(Colors.White)
                         .Background(Colors.White)
                         .Padding(2)
                         .Padding(2)
-                        .Text(Text, TextStyle.Default.Color(Color).FontType(Fonts.Consolas).Size(8));
+                        .Text(Text)
+                        .FontColor(Color)
+                        .FontFamily(Fonts.Consolas)
+                        .FontSize(8);
                 });
                 });
         }
         }
     }
     }

+ 2 - 2
QuestPDF/Elements/ExternalLink.cs → QuestPDF/Elements/Hyperlink.cs

@@ -3,7 +3,7 @@ using QuestPDF.Infrastructure;
 
 
 namespace QuestPDF.Elements
 namespace QuestPDF.Elements
 {
 {
-    internal class ExternalLink : ContainerElement
+    internal class Hyperlink : ContainerElement
     {
     {
         public string Url { get; set; } = "https://www.questpdf.com";
         public string Url { get; set; } = "https://www.questpdf.com";
         
         
@@ -14,7 +14,7 @@ namespace QuestPDF.Elements
             if (targetSize.Type == SpacePlanType.Wrap)
             if (targetSize.Type == SpacePlanType.Wrap)
                 return;
                 return;
 
 
-            Canvas.DrawExternalLink(Url, targetSize);
+            Canvas.DrawHyperlink(Url, targetSize);
             base.Draw(availableSpace);
             base.Draw(availableSpace);
         }
         }
     }
     }

+ 45 - 27
QuestPDF/Elements/Page.cs

@@ -20,6 +20,9 @@ namespace QuestPDF.Elements
 
 
         public string BackgroundColor { get; set; } = Colors.Transparent;
         public string BackgroundColor { get; set; } = Colors.Transparent;
         
         
+        public Element Background { get; set; } = Empty.Instance;
+        public Element Foreground { get; set; } = Empty.Instance;
+        
         public Element Header { get; set; } = Empty.Instance;
         public Element Header { get; set; } = Empty.Instance;
         public Element Content { get; set; } = Empty.Instance;
         public Element Content { get; set; } = Empty.Instance;
         public Element Footer { get; set; } = Empty.Instance;
         public Element Footer { get; set; } = Empty.Instance;
@@ -27,39 +30,54 @@ namespace QuestPDF.Elements
         public void Compose(IContainer container)
         public void Compose(IContainer container)
         {
         {
             container
             container
-                .MinWidth(MinSize.Width)
-                .MinHeight(MinSize.Height)
+                .Layers(layers =>
+                {
+                    layers
+                        .Layer()
+                        .DebugPointer("Page background layer")
+                        .Element(Background);
+                    
+                    layers
+                        .PrimaryLayer()
+                        .MinWidth(MinSize.Width)
+                        .MinHeight(MinSize.Height)
                 
                 
-                .MaxWidth(MaxSize.Width)
-                .MaxHeight(MaxSize.Height)
+                        .MaxWidth(MaxSize.Width)
+                        .MaxHeight(MaxSize.Height)
                 
                 
-                .Background(BackgroundColor)
+                        .Background(BackgroundColor)
      
      
-                .PaddingLeft(MarginLeft)
-                .PaddingRight(MarginRight)
-                .PaddingTop(MarginTop)
-                .PaddingBottom(MarginBottom)
+                        .PaddingLeft(MarginLeft)
+                        .PaddingRight(MarginRight)
+                        .PaddingTop(MarginTop)
+                        .PaddingBottom(MarginBottom)
                 
                 
-                .DefaultTextStyle(DefaultTextStyle)
+                        .DefaultTextStyle(DefaultTextStyle)
                 
                 
-                .Decoration(decoration =>
-                {
-                    decoration
-                        .Before()
-                        .DebugPointer("Page header")
-                        .Element(Header);
-                    
-                    decoration
-                        .Content()
-                        .Element(x => IsClose(MinSize.Width, MaxSize.Width) ? x.ExtendHorizontal() : x)
-                        .Element(x => IsClose(MinSize.Height, MaxSize.Height) ? x.ExtendVertical() : x)
-                        .DebugPointer("Page content")
-                        .Element(Content);
+                        .Decoration(decoration =>
+                        {
+                            decoration
+                                .Before()
+                                .DebugPointer("Page header")
+                                .Element(Header);
+
+                            decoration
+                                .Content()
+                                .Element(x => IsClose(MinSize.Width, MaxSize.Width) ? x.ExtendHorizontal() : x)
+                                .Element(x => IsClose(MinSize.Height, MaxSize.Height) ? x.ExtendVertical() : x)
+                                .DebugPointer("Page content")
+                                .Element(Content);
+
+                            decoration
+                                .After()
+                                .DebugPointer("Page footer")
+                                .Element(Footer);
+                        });
                     
                     
-                    decoration
-                        .After()
-                        .DebugPointer("Page footer")
-                        .Element(Footer);
+                    layers
+                        .Layer()
+                        .DebugPointer("Page foreground layer")
+                        .Element(Foreground);
                 });
                 });
 
 
             bool IsClose(float x, float y)
             bool IsClose(float x, float y)

+ 1 - 1
QuestPDF/Elements/Placeholder.cs

@@ -26,7 +26,7 @@ namespace QuestPDF.Elements
                     if (string.IsNullOrWhiteSpace(Text))
                     if (string.IsNullOrWhiteSpace(Text))
                         x.MaxHeight(32).Image(ImageData, ImageScaling.FitArea);
                         x.MaxHeight(32).Image(ImageData, ImageScaling.FitArea);
                     else
                     else
-                        x.Text(Text, TextStyle.Default.Size(14));
+                        x.Text(Text).FontSize(14);
                 });
                 });
         }
         }
     }
     }

+ 3 - 4
QuestPDF/Elements/InternalLocation.cs → QuestPDF/Elements/Section.cs

@@ -2,7 +2,7 @@
 
 
 namespace QuestPDF.Elements
 namespace QuestPDF.Elements
 {
 {
-    internal class InternalLocation : ContainerElement, IStateResettable
+    internal class Section : ContainerElement, IStateResettable
     {
     {
         public string LocationName { get; set; }
         public string LocationName { get; set; }
         private bool IsRendered { get; set; }
         private bool IsRendered { get; set; }
@@ -16,12 +16,11 @@ namespace QuestPDF.Elements
         {
         {
             if (!IsRendered)
             if (!IsRendered)
             {
             {
-                PageContext.SetLocationPage(LocationName);
-                
-                Canvas.DrawLocation(LocationName);
+                Canvas.DrawSection(LocationName);
                 IsRendered = true;
                 IsRendered = true;
             }
             }
             
             
+            PageContext.SetSectionPage(LocationName);
             base.Draw(availableSpace);
             base.Draw(availableSpace);
         }
         }
     }
     }

+ 3 - 3
QuestPDF/Elements/InternalLink.cs → QuestPDF/Elements/SectionLink.cs

@@ -3,9 +3,9 @@ using QuestPDF.Infrastructure;
 
 
 namespace QuestPDF.Elements
 namespace QuestPDF.Elements
 {
 {
-    internal class InternalLink : ContainerElement
+    internal class SectionLink : ContainerElement
     {
     {
-        public string LocationName { get; set; }
+        public string SectionName { get; set; }
         
         
         internal override void Draw(Size availableSpace)
         internal override void Draw(Size availableSpace)
         {
         {
@@ -14,7 +14,7 @@ namespace QuestPDF.Elements
             if (targetSize.Type == SpacePlanType.Wrap)
             if (targetSize.Type == SpacePlanType.Wrap)
                 return;
                 return;
 
 
-            Canvas.DrawLocationLink(LocationName, targetSize);
+            Canvas.DrawSectionLink(SectionName, targetSize);
             base.Draw(availableSpace);
             base.Draw(availableSpace);
         }
         }
     }
     }

+ 2 - 2
QuestPDF/Elements/Text/Items/TextBlockExternalLink.cs → QuestPDF/Elements/Text/Items/TextBlockHyperlink.cs

@@ -3,7 +3,7 @@ using QuestPDF.Infrastructure;
 
 
 namespace QuestPDF.Elements.Text.Items
 namespace QuestPDF.Elements.Text.Items
 {
 {
-    internal class TextBlockExternalLink : TextBlockSpan
+    internal class TextBlockHyperlink : TextBlockSpan
     {
     {
         public string Url { get; set; }
         public string Url { get; set; }
         
         
@@ -15,7 +15,7 @@ namespace QuestPDF.Elements.Text.Items
         public override void Draw(TextDrawingRequest request)
         public override void Draw(TextDrawingRequest request)
         {
         {
             request.Canvas.Translate(new Position(0, request.TotalAscent));
             request.Canvas.Translate(new Position(0, request.TotalAscent));
-            request.Canvas.DrawExternalLink(Url, new Size(request.TextSize.Width, request.TextSize.Height));
+            request.Canvas.DrawHyperlink(Url, new Size(request.TextSize.Width, request.TextSize.Height));
             request.Canvas.Translate(new Position(0, -request.TotalAscent));
             request.Canvas.Translate(new Position(0, -request.TotalAscent));
             
             
             base.Draw(request);
             base.Draw(request);

+ 5 - 9
QuestPDF/Elements/Text/Items/TextBlockPageNumber.cs

@@ -1,11 +1,13 @@
-using QuestPDF.Elements.Text.Calculation;
+using System;
+using QuestPDF.Elements.Text.Calculation;
 using QuestPDF.Infrastructure;
 using QuestPDF.Infrastructure;
 
 
 namespace QuestPDF.Elements.Text.Items
 namespace QuestPDF.Elements.Text.Items
 {
 {
     internal class TextBlockPageNumber : TextBlockSpan
     internal class TextBlockPageNumber : TextBlockSpan
     {
     {
-        public string SlotName { get; set; }
+        public const string PageNumberPlaceholder = "123";
+        public Func<IPageContext, string> Source { get; set; } = _ => PageNumberPlaceholder;
         
         
         public override TextMeasurementResult? Measure(TextMeasurementRequest request)
         public override TextMeasurementResult? Measure(TextMeasurementRequest request)
         {
         {
@@ -21,13 +23,7 @@ namespace QuestPDF.Elements.Text.Items
 
 
         private void SetPageNumber(IPageContext context)
         private void SetPageNumber(IPageContext context)
         {
         {
-            var pageNumberPlaceholder = 123;
-            
-            var pageNumber = context.GetRegisteredLocations().Contains(SlotName)
-                ? context.GetLocationPage(SlotName)
-                : pageNumberPlaceholder;
-
-            Text = pageNumber.ToString();
+            Text = Source(context) ?? PageNumberPlaceholder;
         }
         }
     }
     }
 }
 }

+ 3 - 3
QuestPDF/Elements/Text/Items/TextBlockInternalLink.cs → QuestPDF/Elements/Text/Items/TextBlockSectionlLink.cs

@@ -3,9 +3,9 @@ using QuestPDF.Infrastructure;
 
 
 namespace QuestPDF.Elements.Text.Items
 namespace QuestPDF.Elements.Text.Items
 {
 {
-    internal class TextBlockInternalLink : TextBlockSpan
+    internal class TextBlockSectionlLink : TextBlockSpan
     {
     {
-        public string LocationName { get; set; }
+        public string SectionName { get; set; }
         
         
         public override TextMeasurementResult? Measure(TextMeasurementRequest request)
         public override TextMeasurementResult? Measure(TextMeasurementRequest request)
         {
         {
@@ -15,7 +15,7 @@ namespace QuestPDF.Elements.Text.Items
         public override void Draw(TextDrawingRequest request)
         public override void Draw(TextDrawingRequest request)
         {
         {
             request.Canvas.Translate(new Position(0, request.TotalAscent));
             request.Canvas.Translate(new Position(0, request.TotalAscent));
-            request.Canvas.DrawLocationLink(LocationName, new Size(request.TextSize.Width, request.TextSize.Height));
+            request.Canvas.DrawSectionLink(SectionName, new Size(request.TextSize.Width, request.TextSize.Height));
             request.Canvas.Translate(new Position(0, -request.TotalAscent));
             request.Canvas.Translate(new Position(0, -request.TotalAscent));
             
             
             base.Draw(request);
             base.Draw(request);

+ 33 - 7
QuestPDF/Fluent/ElementExtensions.cs

@@ -60,7 +60,7 @@ namespace QuestPDF.Fluent
 
 
         public static void Placeholder(this IContainer element, string? text = null)
         public static void Placeholder(this IContainer element, string? text = null)
         {
         {
-            element.Component(new Elements.Placeholder
+            element.Component(new Placeholder
             {
             {
                 Text = text ?? string.Empty
                 Text = text ?? string.Empty
             });
             });
@@ -99,27 +99,45 @@ namespace QuestPDF.Fluent
             return element.Element(new Container());
             return element.Element(new Container());
         }
         }
         
         
+        [Obsolete("This element has been renamed since version 2022.3. Please use the Hyperlink method.")]
         public static IContainer ExternalLink(this IContainer element, string url)
         public static IContainer ExternalLink(this IContainer element, string url)
         {
         {
-            return element.Element(new ExternalLink
+            return element.Hyperlink(url);
+        }
+        
+        public static IContainer Hyperlink(this IContainer element, string url)
+        {
+            return element.Element(new Hyperlink
             {
             {
                 Url = url
                 Url = url
             });
             });
         }
         }
         
         
+        [Obsolete("This element has been renamed since version 2022.3. Please use the Section method.")]
         public static IContainer Location(this IContainer element, string locationName)
         public static IContainer Location(this IContainer element, string locationName)
         {
         {
-            return element.Element(new InternalLocation
+            return element.Section(locationName);
+        }
+        
+        public static IContainer Section(this IContainer element, string sectionName)
+        {
+            return element.Element(new Section
             {
             {
-                LocationName = locationName
+                LocationName = sectionName
             });
             });
         }
         }
         
         
+        [Obsolete("This element has been renamed since version 2022.3. Please use the SectionLink method.")]
         public static IContainer InternalLink(this IContainer element, string locationName)
         public static IContainer InternalLink(this IContainer element, string locationName)
         {
         {
-            return element.Element(new InternalLink
+            return element.SectionLink(locationName);
+        }
+        
+        public static IContainer SectionLink(this IContainer element, string sectionName)
+        {
+            return element.Element(new SectionLink
             {
             {
-                LocationName = locationName
+                SectionName = sectionName
             });
             });
         }
         }
         
         
@@ -159,7 +177,15 @@ namespace QuestPDF.Fluent
                 TextStyle = textStyle
                 TextStyle = textStyle
             });
             });
         }
         }
-
+        
+        public static IContainer DefaultTextStyle(this IContainer element, Func<TextStyle, TextStyle> handler)
+        {
+            return element.Element(new DefaultTextStyle
+            {
+                TextStyle = handler(TextStyle.Default)
+            });
+        }
+        
         public static IContainer StopPaging(this IContainer element)
         public static IContainer StopPaging(this IContainer element)
         {
         {
             return element.Element(new StopPaging());
             return element.Element(new StopPaging());

+ 35 - 0
QuestPDF/Fluent/MinimalApi.cs

@@ -0,0 +1,35 @@
+using System;
+using QuestPDF.Drawing;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Fluent
+{
+    public class Document : IDocument
+    {
+        private Action<IDocumentContainer> ContentSource { get; }
+        private DocumentMetadata Metadata { get; set; } = DocumentMetadata.Default;
+
+        private Document(Action<IDocumentContainer> contentSource)
+        {
+            ContentSource = contentSource;
+        }
+        
+        public static Document Create(Action<IDocumentContainer> handler)
+        {
+            return new Document(handler);
+        }
+
+        public Document WithMetadata(DocumentMetadata metadata)
+        {
+            Metadata = metadata ?? Metadata;
+            return this;
+        }
+        
+        #region IDocument
+
+        public DocumentMetadata GetMetadata() => Metadata;
+        public void Compose(IDocumentContainer container) => ContentSource(container);
+
+        #endregion
+    }
+}

+ 19 - 0
QuestPDF/Fluent/PageExtensions.cs

@@ -83,11 +83,30 @@ namespace QuestPDF.Fluent
             Page.DefaultTextStyle = textStyle;
             Page.DefaultTextStyle = textStyle;
         }
         }
         
         
+        public void DefaultTextStyle(Func<TextStyle, TextStyle> handler)
+        {
+            DefaultTextStyle(handler(TextStyle.Default));
+        }
+        
         public void Background(string color)
         public void Background(string color)
         {
         {
             Page.BackgroundColor = color;
             Page.BackgroundColor = color;
         }
         }
         
         
+        public IContainer Background()
+        {
+            var container = new Container();
+            Page.Background = container;
+            return container;
+        }
+        
+        public IContainer Foreground()
+        {
+            var container = new Container();
+            Page.Foreground = container;
+            return container;
+        }
+        
         public IContainer Header()
         public IContainer Header()
         {
         {
             var container = new Container();
             var container = new Container();

+ 127 - 45
QuestPDF/Fluent/TextExtensions.cs

@@ -10,6 +10,34 @@ using static System.String;
 
 
 namespace QuestPDF.Fluent
 namespace QuestPDF.Fluent
 {
 {
+    public class TextSpanDescriptor
+    {
+        internal TextStyle TextStyle { get; }
+
+        internal TextSpanDescriptor(TextStyle textStyle)
+        {
+            TextStyle = textStyle;
+        }
+    }
+
+    public delegate string PageNumberFormatter(int? pageNumber);
+    
+    public class TextPageNumberDescriptor : TextSpanDescriptor
+    {
+        internal PageNumberFormatter FormatFunction { get; private set; } = x => (x ?? 123).ToString();
+
+        internal TextPageNumberDescriptor(TextStyle textStyle) : base(textStyle)
+        {
+            
+        }
+
+        public TextPageNumberDescriptor Format(PageNumberFormatter formatter)
+        {
+            FormatFunction = formatter ?? FormatFunction;
+            return this;
+        }
+    }
+    
     public class TextDescriptor
     public class TextDescriptor
     {
     {
         private ICollection<TextBlock> TextBlocks { get; } = new List<TextBlock>();
         private ICollection<TextBlock> TextBlocks { get; } = new List<TextBlock>();
@@ -22,6 +50,11 @@ namespace QuestPDF.Fluent
             DefaultStyle = style;
             DefaultStyle = style;
         }
         }
         
         
+        public void DefaultTextStyle(Func<TextStyle, TextStyle> style)
+        {
+            DefaultStyle = style(TextStyle.Default);
+        }
+        
         public void AlignLeft()
         public void AlignLeft()
         {
         {
             Alignment = HorizontalAlignment.Left;
             Alignment = HorizontalAlignment.Left;
@@ -50,12 +83,15 @@ namespace QuestPDF.Fluent
             TextBlocks.Last().Items.Add(item);
             TextBlocks.Last().Items.Add(item);
         }
         }
         
         
-        public void Span(string? text, TextStyle? style = null)
+        [Obsolete("This element has been renamed since version 2022.3. Please use the overload that returns a TextSpanDescriptor object which allows to specify text style.")]
+        public void Span(string? text, TextStyle style)
         {
         {
-            if (IsNullOrEmpty(text))
-                return;
-            
-            style ??= TextStyle.Default;
+            Span(text).Style(style);
+        }
+        
+        public TextSpanDescriptor Span(string? text)
+        {
+            var style = DefaultStyle.Clone();
  
  
             var items = text
             var items = text
                 .Replace("\r", string.Empty)
                 .Replace("\r", string.Empty)
@@ -77,85 +113,123 @@ namespace QuestPDF.Fluent
                 })
                 })
                 .ToList()
                 .ToList()
                 .ForEach(TextBlocks.Add);
                 .ForEach(TextBlocks.Add);
+
+            return new TextSpanDescriptor(style);
         }
         }
 
 
-        public void Line(string? text, TextStyle? style = null)
+        public TextSpanDescriptor Line(string? text)
         {
         {
             text ??= string.Empty;
             text ??= string.Empty;
-            Span(text + Environment.NewLine, style);
+            return Span(text + Environment.NewLine);
         }
         }
 
 
-        public void EmptyLine()
+        public TextSpanDescriptor EmptyLine()
         {
         {
-            Span(Environment.NewLine);
+            return Span(Environment.NewLine);
         }
         }
-
-        private void PageNumber(string slotName, TextStyle? style = null)
+        
+        private TextPageNumberDescriptor PageNumber(Func<IPageContext, int?> pageNumber)
         {
         {
-            if (IsNullOrEmpty(slotName))
-                throw new ArgumentException(nameof(slotName));
-            
-            style ??= TextStyle.Default;
+            var style = DefaultStyle.Clone();
+            var descriptor = new TextPageNumberDescriptor(DefaultStyle);
             
             
-            AddItemToLastTextBlock(new TextBlockPageNumber()
+            AddItemToLastTextBlock(new TextBlockPageNumber
             {
             {
-                Style = style,
-                SlotName = slotName
+                Source = context => descriptor.FormatFunction(pageNumber(context)),
+                Style = style
             });
             });
+            
+            return descriptor;
+        }
+
+        public TextPageNumberDescriptor CurrentPageNumber()
+        {
+            return PageNumber(x => x.CurrentPage);
         }
         }
         
         
-        public void CurrentPageNumber(TextStyle? style = null)
+        public TextPageNumberDescriptor TotalPages()
         {
         {
-            PageNumber(PageContext.CurrentPageSlot, style);
+            return PageNumber(x => x.GetLocation(PageContext.DocumentLocation)?.Length);
+        }
+
+        [Obsolete("This element has been renamed since version 2022.3. Please use the BeginPageNumberOfSection method.")]
+        public void PageNumberOfLocation(string locationName, TextStyle? style = null)
+        {
+            BeginPageNumberOfSection(locationName).Style(style);
         }
         }
         
         
-        public void TotalPages(TextStyle? style = null)
+        public TextPageNumberDescriptor BeginPageNumberOfSection(string locationName)
         {
         {
-            PageNumber(PageContext.TotalPagesSlot, style);
+            return PageNumber(x => x.GetLocation(locationName)?.PageStart);
         }
         }
         
         
-        public void PageNumberOfLocation(string locationName, TextStyle? style = null)
+        public TextPageNumberDescriptor EndPageNumberOfSection(string locationName)
         {
         {
-            if (IsNullOrEmpty(locationName))
-                throw new ArgumentException(nameof(locationName));
-            
-            PageNumber(locationName, style);
+            return PageNumber(x => x.GetLocation(locationName)?.PageEnd);
         }
         }
         
         
-        public void InternalLocation(string? text, string locationName, TextStyle? style = null)
+        public TextPageNumberDescriptor PageNumberWithinSection(string locationName)
         {
         {
-            if (IsNullOrEmpty(text))
-                return;
-            
-            if (IsNullOrEmpty(locationName))
-                throw new ArgumentException(nameof(locationName));
+            return PageNumber(x => x.CurrentPage + 1 - x.GetLocation(locationName)?.PageEnd);
+        }
+        
+        public TextPageNumberDescriptor TotalPagesWithinSection(string locationName)
+        {
+            return PageNumber(x => x.GetLocation(locationName)?.Length);
+        }
+        
+        public TextSpanDescriptor SectionLink(string? text, string sectionName)
+        {
+            if (IsNullOrEmpty(sectionName))
+                throw new ArgumentException(nameof(sectionName));
 
 
-            style ??= TextStyle.Default;
+            var style = DefaultStyle.Clone();
+            var descriptor = new TextSpanDescriptor(style);
             
             
-            AddItemToLastTextBlock(new TextBlockInternalLink
+            if (IsNullOrEmpty(text))
+                return descriptor;
+            
+            AddItemToLastTextBlock(new TextBlockSectionlLink
             {
             {
                 Style = style,
                 Style = style,
                 Text = text,
                 Text = text,
-                LocationName = locationName
+                SectionName = sectionName
             });
             });
+
+            return descriptor;
         }
         }
         
         
-        public void ExternalLocation(string? text, string url, TextStyle? style = null)
+        [Obsolete("This element has been renamed since version 2022.3. Please use the SectionLink method.")]
+        public void InternalLocation(string? text, string locationName, TextStyle? style = null)
+        {
+            SectionLink(text, locationName).Style(style);
+        }
+        
+        public TextSpanDescriptor Hyperlink(string? text, string url)
         {
         {
-            if (IsNullOrEmpty(text))
-                return;
-            
             if (IsNullOrEmpty(url))
             if (IsNullOrEmpty(url))
                 throw new ArgumentException(nameof(url));
                 throw new ArgumentException(nameof(url));
+
+            var style = DefaultStyle.Clone();
+            var descriptor = new TextSpanDescriptor(style);
+
+            if (IsNullOrEmpty(text))
+                return descriptor;
             
             
-            style ??= TextStyle.Default;
-            
-            AddItemToLastTextBlock(new TextBlockExternalLink
+            AddItemToLastTextBlock(new TextBlockHyperlink
             {
             {
                 Style = style,
                 Style = style,
                 Text = text,
                 Text = text,
                 Url = url
                 Url = url
             });
             });
+
+            return descriptor;
+        }
+        
+        [Obsolete("This element has been renamed since version 2022.3. Please use the Hyperlink method.")]
+        public void ExternalLocation(string? text, string url, TextStyle? style = null)
+        {
+            Hyperlink(text, url).Style(style);
         }
         }
         
         
         public IContainer Element()
         public IContainer Element()
@@ -197,9 +271,17 @@ namespace QuestPDF.Fluent
             descriptor.Compose(element);
             descriptor.Compose(element);
         }
         }
         
         
-        public static void Text(this IContainer element, object? text, TextStyle? style = null)
+        [Obsolete("This element has been renamed since version 2022.3. Please use the overload that returns a TextSpanDescriptor object which allows to specify text style.")]
+        public static void Text(this IContainer element, object? text, TextStyle style)
+        {
+            element.Text(text).Style(style);
+        }
+        
+        public static TextSpanDescriptor Text(this IContainer element, object? text)
         {
         {
-            element.Text(x => x.Span(text?.ToString(), style));
+            var descriptor = (TextSpanDescriptor) null;
+            element.Text(x => descriptor = x.Span(text?.ToString()));
+            return descriptor;
         }
         }
     }
     }
 }
 }

+ 126 - 0
QuestPDF/Fluent/TextSpanDescriptorExtensions.cs

@@ -0,0 +1,126 @@
+using System;
+using System.Runtime.CompilerServices;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Fluent
+{
+    public static class TextSpanDescriptorExtensions
+    {
+        public static T Style<T>(this T descriptor, TextStyle style) where T : TextSpanDescriptor
+        {
+            if (style == null)
+                return descriptor;
+            
+            descriptor.TextStyle.OverrideStyle(style);
+            return descriptor;
+        }
+        
+        public static T FontColor<T>(this T descriptor, string value) where T : TextSpanDescriptor
+        {
+            descriptor.TextStyle.Color = value;
+            return descriptor;
+        }
+        
+        public static T BackgroundColor<T>(this T descriptor, string value) where T : TextSpanDescriptor
+        {
+            descriptor.TextStyle.BackgroundColor = value;
+            return descriptor;
+        }
+        
+        public static T FontFamily<T>(this T descriptor, string value) where T : TextSpanDescriptor
+        {
+            descriptor.TextStyle.FontFamily = value;
+            return descriptor;
+        }
+        
+        public static T FontSize<T>(this T descriptor, float value) where T : TextSpanDescriptor
+        {
+            descriptor.TextStyle.Size = value;
+            return descriptor;
+        }
+        
+        public static T LineHeight<T>(this T descriptor, float value) where T : TextSpanDescriptor
+        {
+            descriptor.TextStyle.LineHeight = value;
+            return descriptor;
+        }
+        
+        public static T Italic<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
+        {
+            descriptor.TextStyle.IsItalic = value;
+            return descriptor;
+        }
+        
+        public static T Strikethrough<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
+        {
+            descriptor.TextStyle.HasStrikethrough = value;
+            return descriptor;
+        }
+        
+        public static T Underline<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
+        {
+            descriptor.TextStyle.HasUnderline = value;
+            return descriptor;
+        }
+
+        #region Weight
+        
+        public static T Weight<T>(this T descriptor, FontWeight weight) where T : TextSpanDescriptor
+        {
+            descriptor.TextStyle.FontWeight = weight;
+            return descriptor;
+        }
+        
+        public static T Thin<T>(this T descriptor) where T : TextSpanDescriptor
+        {
+            return descriptor.Weight(FontWeight.Thin);
+        }
+        
+        public static T ExtraLight<T>(this T descriptor) where T : TextSpanDescriptor
+        {
+            return descriptor.Weight(FontWeight.ExtraLight);
+        }
+        
+        public static T Light<T>(this T descriptor) where T : TextSpanDescriptor
+        {
+            return descriptor.Weight(FontWeight.Light);
+        }
+        
+        public static T NormalWeight<T>(this T descriptor) where T : TextSpanDescriptor
+        {
+            return descriptor.Weight(FontWeight.Normal);
+        }
+        
+        public static T Medium<T>(this T descriptor) where T : TextSpanDescriptor
+        {
+            return descriptor.Weight(FontWeight.Medium);
+        }
+        
+        public static T SemiBold<T>(this T descriptor) where T : TextSpanDescriptor
+        {
+            return descriptor.Weight(FontWeight.SemiBold);
+        }
+        
+        public static T Bold<T>(this T descriptor) where T : TextSpanDescriptor
+        {
+            return descriptor.Weight(FontWeight.Bold);
+        }
+        
+        public static T ExtraBold<T>(this T descriptor) where T : TextSpanDescriptor
+        {
+            return descriptor.Weight(FontWeight.ExtraBold);
+        }
+        
+        public static T Black<T>(this T descriptor) where T : TextSpanDescriptor
+        {
+            return descriptor.Weight(FontWeight.Black);
+        }
+        
+        public static T ExtraBlack<T>(this T descriptor) where T : TextSpanDescriptor
+        {
+            return descriptor.Weight(FontWeight.ExtraBlack);
+        }
+        
+        #endregion
+    }
+}

+ 19 - 1
QuestPDF/Fluent/TextStyleExtensions.cs

@@ -13,7 +13,13 @@ namespace QuestPDF.Fluent
             return style;
             return style;
         }
         }
         
         
+        [Obsolete("This element has been renamed since version 2022.3. Please use the FontColor method.")]
         public static TextStyle Color(this TextStyle style, string value)
         public static TextStyle Color(this TextStyle style, string value)
+        {
+            return style.FontColor(value);
+        }
+        
+        public static TextStyle FontColor(this TextStyle style, string value)
         {
         {
             return style.Mutate(x => x.Color = value);
             return style.Mutate(x => x.Color = value);
         }
         }
@@ -23,12 +29,24 @@ namespace QuestPDF.Fluent
             return style.Mutate(x => x.BackgroundColor = value);
             return style.Mutate(x => x.BackgroundColor = value);
         }
         }
         
         
+        [Obsolete("This element has been renamed since version 2022.3. Please use the FontFamily method.")]
         public static TextStyle FontType(this TextStyle style, string value)
         public static TextStyle FontType(this TextStyle style, string value)
         {
         {
-            return style.Mutate(x => x.FontType = value);
+            return style.FontFamily(value);
         }
         }
         
         
+        public static TextStyle FontFamily(this TextStyle style, string value)
+        {
+            return style.Mutate(x => x.FontFamily = value);
+        }
+        
+        [Obsolete("This element has been renamed since version 2022.3. Please use the FontSize method.")]
         public static TextStyle Size(this TextStyle style, float value)
         public static TextStyle Size(this TextStyle style, float value)
+        {
+            return style.FontSize(value);
+        }
+        
+        public static TextStyle FontSize(this TextStyle style, float value)
         {
         {
             return style.Mutate(x => x.Size = value);
             return style.Mutate(x => x.Size = value);
         }
         }

+ 3 - 3
QuestPDF/Infrastructure/ICanvas.cs

@@ -10,9 +10,9 @@ namespace QuestPDF.Infrastructure
         void DrawText(string text, Position position, TextStyle style);
         void DrawText(string text, Position position, TextStyle style);
         void DrawImage(SKImage image, Position position, Size size);
         void DrawImage(SKImage image, Position position, Size size);
 
 
-        void DrawExternalLink(string url, Size size);
-        void DrawLocationLink(string locationName, Size size);
-        void DrawLocation(string locationName);
+        void DrawHyperlink(string url, Size size);
+        void DrawSectionLink(string sectionName, Size size);
+        void DrawSection(string sectionName);
         
         
         void Rotate(float angle);
         void Rotate(float angle);
         void Scale(float scaleX, float scaleY);
         void Scale(float scaleX, float scaleY);

+ 12 - 4
QuestPDF/Infrastructure/IPageContext.cs

@@ -2,10 +2,18 @@
 
 
 namespace QuestPDF.Infrastructure
 namespace QuestPDF.Infrastructure
 {
 {
-    public interface IPageContext
+    internal class DocumentLocation
     {
     {
-        void SetLocationPage(string key);
-        int GetLocationPage(string key);
-        ICollection<string> GetRegisteredLocations();
+        public string Name { get; set; }
+        public int PageStart { get; set; }
+        public int PageEnd { get; set; }
+        public int Length => PageEnd - PageStart + 1;
+    }
+    
+    internal interface IPageContext
+    {
+        int CurrentPage { get; }
+        void SetSectionPage(string name);
+        DocumentLocation? GetLocation(string name);
     }
     }
 }
 }

+ 25 - 24
QuestPDF/Infrastructure/PageContext.cs

@@ -1,44 +1,45 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Linq;
 
 
 namespace QuestPDF.Infrastructure
 namespace QuestPDF.Infrastructure
 {
 {
-    public class PageContext : IPageContext
+    internal class PageContext : IPageContext
     {
     {
-        public const string CurrentPageSlot = "currentPage";
-        public const string TotalPagesSlot = "totalPages";
+        public const string DocumentLocation = "document";
         
         
-        private Dictionary<string, int> Locations { get; } = new Dictionary<string, int>();
-        private int PageNumber { get; set; }
+        private List<DocumentLocation> Locations { get; } = new();
+        public int CurrentPage { get; private set; }
 
 
         internal void SetPageNumber(int number)
         internal void SetPageNumber(int number)
         {
         {
-            PageNumber = number;
-            Locations[CurrentPageSlot] = number;
-
-            if (!Locations.ContainsKey(TotalPagesSlot) || Locations[TotalPagesSlot] < number)
-                Locations[TotalPagesSlot] = number;
+            CurrentPage = number;
+            SetSectionPage(DocumentLocation);
         }
         }
         
         
-        public void SetLocationPage(string key)
+        public void SetSectionPage(string name)
         {
         {
-            if (Locations.ContainsKey(key))
-                return;
-            
-            Locations[key] = PageNumber;
-        }
+            var location = GetLocation(name);
 
 
-        public int GetLocationPage(string key)
-        {
-            if (!Locations.ContainsKey(key))
-                throw new ArgumentException($"The location '{key}' does not exists.");
-            
-            return Locations[key];
+            if (location == null)
+            {
+                location = new DocumentLocation
+                {
+                    Name = name,
+                    PageStart = CurrentPage,
+                    PageEnd = CurrentPage
+                };
+                
+                Locations.Add(location);
+            }
+
+            if (location.PageEnd < CurrentPage)
+                location.PageEnd = CurrentPage;
         }
         }
 
 
-        public ICollection<string> GetRegisteredLocations()
+        public DocumentLocation? GetLocation(string name)
         {
         {
-            return Locations.Keys;
+            return Locations.FirstOrDefault(x => x.Name == name);
         }
         }
     }
     }
 }
 }

+ 20 - 5
QuestPDF/Infrastructure/TextStyle.cs

@@ -9,7 +9,7 @@ namespace QuestPDF.Infrastructure
         
         
         internal string? Color { get; set; }
         internal string? Color { get; set; }
         internal string? BackgroundColor { get; set; }
         internal string? BackgroundColor { get; set; }
-        internal string? FontType { get; set; }
+        internal string? FontFamily { get; set; }
         internal float? Size { get; set; }
         internal float? Size { get; set; }
         internal float? LineHeight { get; set; }
         internal float? LineHeight { get; set; }
         internal FontWeight? FontWeight { get; set; }
         internal FontWeight? FontWeight { get; set; }
@@ -17,13 +17,14 @@ namespace QuestPDF.Infrastructure
         internal bool? HasStrikethrough { get; set; }
         internal bool? HasStrikethrough { get; set; }
         internal bool? HasUnderline { get; set; }
         internal bool? HasUnderline { get; set; }
 
 
-        internal string? Key { get; private set; }
+        internal object PaintKey { get; private set; }
+        internal object FontMetricsKey { get; private set; }
         
         
         internal static TextStyle LibraryDefault => new TextStyle
         internal static TextStyle LibraryDefault => new TextStyle
         {
         {
             Color = Colors.Black,
             Color = Colors.Black,
             BackgroundColor = Colors.Transparent,
             BackgroundColor = Colors.Transparent,
-            FontType = Fonts.Calibri,
+            FontFamily = Fonts.Calibri,
             Size = 12,
             Size = 12,
             LineHeight = 1.2f,
             LineHeight = 1.2f,
             FontWeight = Infrastructure.FontWeight.Normal,
             FontWeight = Infrastructure.FontWeight.Normal,
@@ -42,14 +43,15 @@ namespace QuestPDF.Infrastructure
             HasGlobalStyleApplied = true;
             HasGlobalStyleApplied = true;
 
 
             ApplyParentStyle(globalStyle);
             ApplyParentStyle(globalStyle);
-            Key ??= $"{Color}|{BackgroundColor}|{FontType}|{Size}|{LineHeight}|{FontWeight}|{IsItalic}|{HasStrikethrough}|{HasUnderline}";
+            PaintKey ??= (FontFamily, Size, FontWeight, IsItalic, Color);
+            FontMetricsKey ??= (FontFamily, Size, FontWeight, IsItalic);
         }
         }
         
         
         internal void ApplyParentStyle(TextStyle parentStyle)
         internal void ApplyParentStyle(TextStyle parentStyle)
         {
         {
             Color ??= parentStyle.Color;
             Color ??= parentStyle.Color;
             BackgroundColor ??= parentStyle.BackgroundColor;
             BackgroundColor ??= parentStyle.BackgroundColor;
-            FontType ??= parentStyle.FontType;
+            FontFamily ??= parentStyle.FontFamily;
             Size ??= parentStyle.Size;
             Size ??= parentStyle.Size;
             LineHeight ??= parentStyle.LineHeight;
             LineHeight ??= parentStyle.LineHeight;
             FontWeight ??= parentStyle.FontWeight;
             FontWeight ??= parentStyle.FontWeight;
@@ -58,6 +60,19 @@ namespace QuestPDF.Infrastructure
             HasUnderline ??= parentStyle.HasUnderline;
             HasUnderline ??= parentStyle.HasUnderline;
         }
         }
 
 
+        internal void OverrideStyle(TextStyle parentStyle)
+        {
+            Color = parentStyle.Color ?? Color;
+            BackgroundColor = parentStyle.BackgroundColor ?? BackgroundColor;
+            FontFamily = parentStyle.FontFamily ?? FontFamily;
+            Size = parentStyle.Size ?? Size;
+            LineHeight = parentStyle.LineHeight ?? LineHeight;
+            FontWeight = parentStyle.FontWeight ?? FontWeight;
+            IsItalic = parentStyle.IsItalic ?? IsItalic;
+            HasStrikethrough = parentStyle.HasStrikethrough ?? HasStrikethrough;
+            HasUnderline = parentStyle.HasUnderline ?? HasUnderline;
+        }
+        
         internal TextStyle Clone()
         internal TextStyle Clone()
         {
         {
             var clone = (TextStyle)MemberwiseClone();
             var clone = (TextStyle)MemberwiseClone();

+ 4 - 0
QuestPDF/Infrastructure/Unit.cs

@@ -13,6 +13,10 @@ namespace QuestPDF.Infrastructure
         
         
         Feet,
         Feet,
         Inch,
         Inch,
+        
+        /// <summary>
+        /// 1/1000th of inch
+        /// </summary>
         Mill
         Mill
     }
     }
 
 

+ 1 - 1
QuestPDF/QuestPDF.csproj

@@ -4,7 +4,7 @@
         <Authors>MarcinZiabek</Authors>
         <Authors>MarcinZiabek</Authors>
         <Company>CodeFlint</Company>
         <Company>CodeFlint</Company>
         <PackageId>QuestPDF</PackageId>
         <PackageId>QuestPDF</PackageId>
-        <Version>2022.2.7</Version>
+        <Version>2022.3.0</Version>
         <PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
         <PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
         <PackageReleaseNotes>$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt"))</PackageReleaseNotes>
         <PackageReleaseNotes>$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt"))</PackageReleaseNotes>
         <LangVersion>9</LangVersion>
         <LangVersion>9</LangVersion>

+ 50 - 181
readme.md

@@ -16,215 +16,84 @@ I have designed this layouting engine with full paging support in mind. The docu
 
 
 ## Support QuestPDF
 ## Support QuestPDF
 
 
-All great frameworks and libraries started from zero. Please help me make QuestPDF a commonly known library and an obvious choice in case of generating PDF documents. Please give it a start ⭐ and share with your colleagues 💬👨‍💻.
+All great frameworks and libraries started from zero. Please help me make QuestPDF a commonly known library and an obvious choice for generating PDF documents. 
+
+- ⭐ Give this repository a star,
+- 💬 Share it with your team members. 
 
 
 ## Installation
 ## Installation
 
 
 The library is available as a nuget package. You can install it as any other nuget package from your IDE, try to search by `QuestPDF`. You can find package details [on this webpage](https://www.nuget.org/packages/QuestPDF/).
 The library is available as a nuget package. You can install it as any other nuget package from your IDE, try to search by `QuestPDF`. You can find package details [on this webpage](https://www.nuget.org/packages/QuestPDF/).
 
 
-```
+```c#
+// Package Manager
 Install-Package QuestPDF
 Install-Package QuestPDF
-```
-
-## Documentation
-
-**[Release notes and roadmap](https://www.questpdf.com/documentation/releases.html)** - everything that is planned for future library iterations, description of new features and information about potential breaking changes.
-
-**[Getting started tutorial](https://www.questpdf.com/documentation/getting-started.html)** - a short and easy to follow tutorial showing how to design an invoice document under 200 lines of code.
 
 
-**[API Reference](https://www.questpdf.com/documentation/api-reference.html)** - a detailed description of behavior of all available components and how to use them with C# Fluent API.
+// .NET CLI
+dotnet add package QuestPDF
 
 
-**[Patterns and practices](https://www.questpdf.com/documentation/patterns-and-practices.html#document-metadata)** - everything that may help you designing great reports and reusable code that is easy to maintain.
+// Package reference in .csproj file
+<PackageReference Include="QuestPDF" Version="2022.2.5" />
+```
 
 
-## Example invoice
+## Documentation
 
 
-Do you believe that creating a complete invoice document can take less than 200 lines of code? We have prepared for you a step-by-step instruction that shows every detail of this implementation and describes the best patterns and practices.
+**[🚀 Getting started tutorial](https://www.questpdf.com/documentation/getting-started.html)** - a short and easy to follow tutorial showing how to design an invoice document under 200 lines of code.
 
 
-For tutorial, documentation and API reference, please visit [the QuestPDF documentation](https://www.questpdf.com/documentation/getting-started.html).
+**[📖 API Reference](https://www.questpdf.com/documentation/api-reference.html)** - a detailed description of behavior of all available components and how to use them with C# Fluent API.
 
 
-<a href="https://github.com/QuestPDF/example-invoice">
-  <img src="https://github.com/QuestPDF/example-invoice/raw/main/images/invoice.png" width="595px">
-</a>
+**[ℹ️ Patterns and practices](https://www.questpdf.com/documentation/patterns-and-practices.html#document-metadata)** - everything that may help you designing great reports and create reusable code that is easy to maintain.
 
 
-Here you can find an example code showing how easy is to write and understand the fluent API.
+## Simplicity is the key
 
 
-**General document structure** with header, content and footer:
+How easy it is to start and prototype with QuestPDF? Really easy thanks to its minimal API! Please analyse the code below:
 
 
 ```csharp
 ```csharp
-public void Compose(IDocumentContainer container)
-{
-    container
-        .Page(page =>
-        {
-            page.Margin(50);
-            
-            page.Header().Element(ComposeHeader);
-            page.Content().Element(ComposeContent);
-            
-            page.Footer().AlignCenter().Text(x =>
-            {
-                x.CurrentPageNumber();
-                x.Span(" / ");
-                x.TotalPages();
-            });
-        });
-}
-```
-
-**The header area** consists of basic invoice information along with a logo placeholder.
+using QuestPDF.Fluent;
+using QuestPDF.Helpers;
+using QuestPDF.Infrastructure;
 
 
-```csharp
-void ComposeHeader(IContainer container)
+// code in your main method
+Document.Create(container =>
 {
 {
-    var titleTextStyle = TextStyle.Default.Size(20).SemiBold().Color(Colors.Blue.Medium);
-    
-    container.Row(row =>
+    container.Page(page =>
     {
     {
-        row.RelativeColumn().Stack(stack =>
-        {
-            stack.Item().Text($"Invoice #{Model.InvoiceNumber}", titleStyle);
-
-            stack.Item().Text(text =>
+        page.Size(PageSizes.A4);
+        page.Margin(2, Unit.Centimetre);
+        page.Background(Colors.White);
+        page.DefaultTextStyle(TextStyle.Default.Size(20));
+        
+        page.Header()
+            .Text("Hello PDF!", TextStyle.Default.SemiBold().Size(36).Color(Colors.Blue.Medium));
+        
+        page.Content()
+            .PaddingVertical(1, Unit.Centimetre)
+            .Column(x =>
             {
             {
-                text.Span("Issue date: ", TextStyle.Default.SemiBold());
-                text.Span($"{Model.IssueDate:d}");
+                x.Spacing(20);
+                
+                x.Item().Text(Placeholders.LoremIpsum());
+                x.Item().Image(Placeholders.Image(200, 100));
             });
             });
-
-            stack.Item().Text(text =>
+        
+        page.Footer()
+            .AlignCenter()
+            .Text(x =>
             {
             {
-                text.Span("Due date: ", TextStyle.Default.SemiBold());
-                text.Span($"{Model.DueDate:d}");
+                x.Span("Page ");
+                x.CurrentPageNumber();
             });
             });
-        });
-        
-        row.ConstantColumn(100).Height(50).Placeholder();
     });
     });
-}
+})
+.GeneratePdf("hello.pdf");
 ```
 ```
 
 
-Implementation of **the content area** that contains seller and customer details, then listing of all bought products, then a comments section.
+And compare it to the produced PDF file:
 
 
-```csharp
-void ComposeContent(IContainer container)
-{
-    container.PaddingVertical(40).Stack(column => 
-    {
-        column.Spacing(20);
-        
-        column.Item().Row(row =>
-        {
-            row.RelativeColumn().Component(new AddressComponent("From", Model.SellerAddress));
-            row.ConstantColumn(50);
-            row.RelativeColumn().Component(new AddressComponent("For", Model.CustomerAddress));
-        });
-
-        column.Item().Element(ComposeTable);
-
-        var totalPrice = Model.Items.Sum(x => x.Price * x.Quantity);
-        
-        column
-            .Item()
-            .PaddingRight(5)
-            .AlignRight()
-            .Text($"Grand total: {totalPrice}$", TextStyle.Default.SemiBold());
-
-        if (!string.IsNullOrWhiteSpace(Model.Comments))
-            column.Item().PaddingTop(25).Element(ComposeComments);
-    });
-}
-```
-
-**The table and comments** codes are extracted into separate methods to increase clarity:
-
-```csharp
-void ComposeTable(IContainer container)
-{
-    var headerStyle = TextStyle.Default.SemiBold();
-    
-    container.Table(table =>
-    {
-        table.ColumnsDefinition(columns =>
-        {
-            columns.ConstantColumn(25);
-            columns.RelativeColumn(3);
-            columns.RelativeColumn();
-            columns.RelativeColumn();
-            columns.RelativeColumn();
-        });
-        
-        table.Header(header =>
-        {
-            header.Cell().Text("#", headerStyle);
-            header.Cell().Text("Product", headerStyle);
-            header.Cell().AlignRight().Text("Unit price", headerStyle);
-            header.Cell().AlignRight().Text("Quantity", headerStyle);
-            header.Cell().AlignRight().Text("Total", headerStyle);
-            
-            header.Cell().ColumnSpan(5)
-                  .PaddingVertical(5).BorderBottom(1).BorderColor(Colors.Black);
-        });
-        
-        foreach (var item in Model.Items)
-        {
-            table.Cell().Element(CellStyle).Text(Model.Items.IndexOf(item) + 1);
-            table.Cell().Element(CellStyle).Text(item.Name);
-            table.Cell().Element(CellStyle).AlignRight().Text($"{item.Price}$");
-            table.Cell().Element(CellStyle).AlignRight().Text(item.Quantity);
-            table.Cell().Element(CellStyle).AlignRight().Text($"{item.Price * item.Quantity}$");
-            
-            static IContainer CellStyle(IContainer container)
-            {
-                container.BorderBottom(1).BorderColor(Colors.Grey.Lighten2).PaddingVertical(5);
-            }
-        }
-    });
-}
-```
 
 
-```csharp
-void ComposeComments(IContainer container)
-{
-    container.ShowEntire().Background(Colors.Grey.Lighten3).Padding(10).Stack(message => 
-    {
-        message.Spacing(5);
-        message.Item().Text("Comments", TextStyle.Default.Size(14).SemiBold());
-        message.Item().Text(Model.Comments);
-    });
-}
-```
 
 
-**The address details section** is implemented using components. This way the code can be easily reused for both seller and customer:
+## Are you ready for more?
 
 
-```csharp
-public class AddressComponent : IComponent
-{
-    private string Title { get; }
-    private Address Address { get; }
+The Fluent API of QuestPDF scales really well. It is easy to create and maintain even most complex documents. Read [the Getting started tutorial](https://www.questpdf.com/documentation/getting-started.html) to learn QuestPDF basics and implement an invoice under 200 lines of code. You can also investigate and play with the code from [the example repository](https://github.com/QuestPDF/example-invoice).
 
 
-    public AddressComponent(string title, Address address)
-    {
-        Title = title;
-        Address = address;
-    }
-    
-    public void Compose(IContainer container)
-    {
-        container.ShowEntire().Stack(column =>
-        {
-            column.Spacing(5);
-
-            column
-                .Item()
-                .BorderBottom(1)
-                .PaddingBottom(5)
-                .Text(Title, TextStyle.Default.SemiBold());
-            
-            column.Item().Text(Address.CompanyName);
-            column.Item().Text(Address.Street);
-            column.Item().Text($"{Address.City}, {Address.State}");
-            column.Item().Text(Address.Email);
-            column.Item().Text(Address.Phone);
-        });
-    }
-}
-```
+<img src="https://github.com/QuestPDF/example-invoice/raw/main/images/invoice.png" width="595px">