diff --git a/QuestPDF.Previewer.Example/Program.cs b/QuestPDF.Previewer.Example/Program.cs
new file mode 100644
index 0000000..7271b1d
--- /dev/null
+++ b/QuestPDF.Previewer.Example/Program.cs
@@ -0,0 +1,80 @@
+using System.Diagnostics;
+using QuestPDF.Fluent;
+using QuestPDF.Helpers;
+using QuestPDF.Infrastructure;
+using QuestPDF.Previewer;
+
+Document
+ .Create(container =>
+ {
+ container.Page(page =>
+ {
+ page.Size(PageSizes.A4);
+ page.Margin(2, Unit.Centimetre);
+ page.PageColor(Colors.White);
+ page.DefaultTextStyle(x => x.FontSize(14));
+
+ page.Header()
+ .Text("Hello PDF!")
+ .SemiBold().FontSize(36).FontColor(Colors.Blue.Medium);
+
+ page.Content()
+ .PaddingVertical(1, Unit.Centimetre)
+ .Table(table =>
+ {
+ table.ColumnsDefinition(columns =>
+ {
+ columns.ConstantColumn(30);
+ columns.RelativeColumn();
+ columns.ConstantColumn(75);
+ columns.ConstantColumn(75);
+ columns.ConstantColumn(75);
+ });
+
+ table.Header(header =>
+ {
+ var textStyle = TextStyle.Default.SemiBold();
+
+ header.Cell().Element(DefaultCellStyle).Text("#").Style(textStyle);
+ header.Cell().Element(DefaultCellStyle).Text("Item name").Style(textStyle);
+ header.Cell().Element(DefaultCellStyle).AlignRight().Text("Price").Style(textStyle);
+ header.Cell().Element(DefaultCellStyle).AlignRight().Text("Count").Style(textStyle);
+ header.Cell().Element(DefaultCellStyle).AlignRight().Text("Total").Style(textStyle);
+
+ static IContainer DefaultCellStyle(IContainer container)
+ {
+ return container.PaddingBottom(5).BorderBottom(1).PaddingBottom(5);
+ }
+ });
+
+ foreach(var i in Enumerable.Range(1, 100))
+ {
+ var price = Placeholders.Random.Next(100, 999) / 100f;
+ var count = Placeholders.Random.Next(1, 10);
+ var total = price * count;
+
+ table.Cell().Element(DefaultCellStyle).Text(i);
+ table.Cell().Element(DefaultCellStyle).Text(Placeholders.Label());
+ table.Cell().Element(DefaultCellStyle).AlignRight().Text($"{price:N2} $");
+ table.Cell().Element(DefaultCellStyle).AlignRight().Text(count);
+ table.Cell().Element(DefaultCellStyle).AlignRight().Text($"{total:N2} $");
+
+ static IContainer DefaultCellStyle(IContainer container)
+ {
+ return container.PaddingBottom(5).BorderBottom(1).BorderColor(Colors.Grey.Lighten1).PaddingBottom(5);
+ }
+ }
+ });
+
+ page.Footer()
+ .AlignCenter()
+ .Text(x =>
+ {
+ x.Span("Page ");
+ x.CurrentPageNumber();
+ x.Span(" of ");
+ x.TotalPages();
+ });
+ });
+ })
+ .ShowInPreviewer();
\ No newline at end of file
diff --git a/QuestPDF.Previewer.Example/QuestPDF.Previewer.Example.csproj b/QuestPDF.Previewer.Example/QuestPDF.Previewer.Example.csproj
new file mode 100644
index 0000000..5631e65
--- /dev/null
+++ b/QuestPDF.Previewer.Example/QuestPDF.Previewer.Example.csproj
@@ -0,0 +1,18 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/QuestPDF.Previewer/DocumentPreviewerExtensions.cs b/QuestPDF.Previewer/DocumentPreviewerExtensions.cs
new file mode 100644
index 0000000..effdd57
--- /dev/null
+++ b/QuestPDF.Previewer/DocumentPreviewerExtensions.cs
@@ -0,0 +1,125 @@
+using System.Diagnostics;
+using System.Reflection;
+using Microsoft.AspNetCore.Builder;
+using QuestPDF.Fluent;
+using QuestPDF.Helpers;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Previewer;
+
+public static class DocumentPreviewerExtensions
+{
+ public static void ShowInPreviewer(this IDocument document)
+ {
+ ArgumentNullException.ThrowIfNull(document);
+
+ var builder = WebApplication.CreateBuilder();
+ var app = builder.Build();
+
+ var pdfDocumentCache = GeneratePdf(document);
+ var refreshFlag = false;
+
+ static byte[] GenerateDocumentAboutException(Exception exception)
+ {
+ return Document.Create(document =>
+ {
+ document.Page(page =>
+ {
+ page.Size(PageSizes.A4);
+ page.Margin(1, Unit.Inch);
+ page.PageColor(Colors.Red.Lighten4);
+ page.DefaultTextStyle(x => x.FontSize(16));
+
+ page.Header()
+ .BorderBottom(2)
+ .BorderColor(Colors.Red.Medium)
+ .PaddingBottom(5)
+ .Text("Ooops! Something went wrong...").FontSize(28).FontColor(Colors.Red.Medium).Bold();
+
+ page.Content().PaddingVertical(20).Column(column =>
+ {
+ var currentException = exception;
+
+ while (currentException != null)
+ {
+ column.Item().Text(exception.GetType().Name).FontSize(20).SemiBold();
+ column.Item().Text(exception.Message).FontSize(14);
+ column.Item().PaddingTop(10).Text(exception.StackTrace).FontSize(10).Light();
+
+ currentException = currentException.InnerException;
+
+ if (currentException != null)
+ column.Item().PaddingVertical(15).LineHorizontal(2).LineColor(Colors.Red.Medium);
+ }
+ });
+ });
+ }).GeneratePdf();
+ }
+
+ static byte[] GeneratePdf(IDocument document)
+ {
+ try
+ {
+ return document.GeneratePdf();
+ }
+ catch(Exception exception)
+ {
+ return GenerateDocumentAboutException(exception);
+ }
+ }
+
+ HotReloadManager.OnApplicationChanged += () =>
+ {
+ pdfDocumentCache = GeneratePdf(document);
+ refreshFlag = true;
+ };
+
+ app.MapGet("/", () =>
+ {
+ var assembly = Assembly.GetExecutingAssembly();
+ var resourceName = "QuestPDF.Previewer.index.html";
+
+ using var stream = assembly.GetManifestResourceStream(resourceName);
+ using var reader = new StreamReader(stream);
+ var result = reader.ReadToEnd();
+ return Results.Content(result, "text/html");
+ });
+
+ app.MapGet("/render", () =>
+ {
+ refreshFlag = false;
+ return Results.File(pdfDocumentCache, "application/pdf");
+ });
+
+ app.MapGet("/listen", async () =>
+ {
+ foreach (var i in Enumerable.Range(0, 1000))
+ {
+ await Task.Delay(TimeSpan.FromMilliseconds(100));
+
+ if (!refreshFlag)
+ continue;
+
+ return Results.Text("true");
+ }
+
+ return Results.Text("false");
+ });
+
+ app.Lifetime.ApplicationStarted.Register(() =>
+ {
+ var openBrowserProcess = new Process()
+ {
+ StartInfo = new()
+ {
+ UseShellExecute = true,
+ FileName = app.Urls.First()
+ }
+ };
+
+ openBrowserProcess.Start();
+ });
+
+ app.Run();
+ }
+}
\ No newline at end of file
diff --git a/QuestPDF.Previewer/HotReloadManager.cs b/QuestPDF.Previewer/HotReloadManager.cs
new file mode 100644
index 0000000..60928cc
--- /dev/null
+++ b/QuestPDF.Previewer/HotReloadManager.cs
@@ -0,0 +1,9 @@
+using System.Reflection.Metadata;
+
+[assembly: MetadataUpdateHandler(typeof(HotReloadManager))]
+
+internal static class HotReloadManager
+{
+ public static event Action? OnApplicationChanged;
+ public static void UpdateApplication(Type[]? _) => OnApplicationChanged?.Invoke();
+}
\ No newline at end of file
diff --git a/QuestPDF.Previewer/QuestPDF.Previewer.csproj b/QuestPDF.Previewer/QuestPDF.Previewer.csproj
new file mode 100644
index 0000000..46765f8
--- /dev/null
+++ b/QuestPDF.Previewer/QuestPDF.Previewer.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net6.0
+ enable
+ enable
+ Library
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/QuestPDF.Previewer/index.html b/QuestPDF.Previewer/index.html
new file mode 100644
index 0000000..1f53742
--- /dev/null
+++ b/QuestPDF.Previewer/index.html
@@ -0,0 +1,117 @@
+
+
+
+
+ QuestPDF Previewer
+
+
+
+
+
+
+
+
+ The recommended browser for this previewer is Microsoft Edge. It does preserve document scroll position when refreshing document, giving better overall experience.
+
Click to close
+
+
+
+ Cannot connect to QuestPDF previewer host. Possibly your debugging session has ended. Please close this browser.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/QuestPDF.sln b/QuestPDF.sln
index db070d2..d708fcf 100644
--- a/QuestPDF.sln
+++ b/QuestPDF.sln
@@ -8,6 +8,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPDF.UnitTests", "Quest
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPDF.Examples", "QuestPDF.Examples\QuestPDF.Examples.csproj", "{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPDF.Previewer", "QuestPDF.Previewer\QuestPDF.Previewer.csproj", "{B4D3CBBC-2F38-434F-9347-6E5297737047}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPDF.Previewer.Example", "QuestPDF.Previewer.Example\QuestPDF.Previewer.Example.csproj", "{012F05A0-C30B-4112-8EB0-E70CE1D31E57}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -30,5 +34,13 @@ Global
{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B4D3CBBC-2F38-434F-9347-6E5297737047}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B4D3CBBC-2F38-434F-9347-6E5297737047}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B4D3CBBC-2F38-434F-9347-6E5297737047}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B4D3CBBC-2F38-434F-9347-6E5297737047}.Release|Any CPU.Build.0 = Release|Any CPU
+ {012F05A0-C30B-4112-8EB0-E70CE1D31E57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {012F05A0-C30B-4112-8EB0-E70CE1D31E57}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {012F05A0-C30B-4112-8EB0-E70CE1D31E57}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {012F05A0-C30B-4112-8EB0-E70CE1D31E57}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal