Compare commits

...

4 Commits

Author SHA1 Message Date
MarcinZiabek 8551e1f33f Basic previewer implementation 2022-03-19 03:08:37 +01:00
MarcinZiabek 3d0ca07b43 Release 2022.3.1 2022-03-15 13:06:09 +01:00
MarcinZiabek a67fa754af Fixed NullReferenceException when the text is null 2022-03-15 11:55:59 +01:00
MarcinZiabek 208235ad1e Updated description 2022-03-14 13:59:26 +01:00
16 changed files with 448 additions and 28 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ namespace QuestPDF.Examples
.PageSize(PageSizes.A4)
.ProducePdf()
.ShowResults()
.Render(x => GenerateStructure(x, 16));
.Render(x => GenerateStructure(x, 12));
}
private void GenerateStructure(IContainer container, int level)
+1 -1
View File
@@ -742,7 +742,7 @@ namespace QuestPDF.Examples
{
container
.Padding(25)
.PaddingLeft(75)
.PaddingLeft(50)
.Column(column =>
{
column.Item().Width(300).Height(150).Background(Colors.Blue.Lighten4);
+4 -2
View File
@@ -23,6 +23,8 @@ namespace QuestPDF.Examples.Engine
private bool ApplyCaching { get; set; }
private bool ApplyDebugging { get; set; }
private RenderingTestResult ResultType { get; set; } = RenderingTestResult.Images;
private bool ShowingResultsEnabled = true;
private RenderingTest()
{
@@ -114,7 +116,7 @@ namespace QuestPDF.Examples.Engine
Func<int, string> fileNameSchema = i => $"{FileNamePrefix}-${i}.png";
document.GenerateImages(fileNameSchema);
if (ShowResult)
if (ShowResult && ShowingResultsEnabled)
Process.Start("explorer", fileNameSchema(0));
}
@@ -123,7 +125,7 @@ namespace QuestPDF.Examples.Engine
var fileName = $"{FileNamePrefix}.pdf";
document.GeneratePdf(fileName);
if (ShowResult)
if (ShowResult && ShowingResultsEnabled)
Process.Start("explorer", fileName);
}
}
+32
View File
@@ -339,5 +339,37 @@ namespace QuestPDF.Examples
});
});
}
[Test]
public void DrawingNullTextShouldNotThrowException()
{
RenderingTest
.Create()
.ProduceImages()
.ShowResults()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Margin(50);
page.PageColor(Colors.White);
page.Size(PageSizes.A4);
page.Content().Column(column =>
{
column.Item().Text(null);
column.Item().Text(text =>
{
text.Span(null);
text.Line(null);
text.Hyperlink(null, "http://www.questpdf.com");
text.TotalPages().Format(x => null);
});
});
});
});
}
}
}
+80
View File
@@ -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();
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\QuestPDF.Previewer\QuestPDF.Previewer.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Remove="HotReloadManager.cs" />
</ItemGroup>
</Project>
@@ -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();
}
}
+9
View File
@@ -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();
}
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<OutputType>Library</OutputType>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\QuestPDF\QuestPDF.csproj" />
</ItemGroup>
<ItemGroup>
<None Remove="index.html" />
<EmbeddedResource Include="index.html" />
</ItemGroup>
<ItemGroup>
<Content Remove="appsettings.Development.json" />
<Content Remove="appsettings.json" />
</ItemGroup>
</Project>
+117
View File
@@ -0,0 +1,117 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>QuestPDF Previewer</title>
<style>
html {
background-color: #333;
}
body {
margin: 0;
}
embed {
border: none;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.alert {
position: absolute;
top: 0;
left: 0;
right: 0;
width: 100%;
padding: 8px 16px;
color: #FFFA;
font-family: sans-serif;
font-size: 14px;
line-height: 1.5;
box-shadow: 0 5px 5px -3px rgba(0,0,0,.2),0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12) !important;
}
#recommended-alert {
background-color: #616161;
}
#recommended-alert .close {
color: #FFF;
cursor: pointer;
}
#connection-issue-alert {
visibility: hidden;
background-color: #B71C1C;
color: white;
}
</style>
</head>
<body>
<embed id="previewer" src="/render#toolbar=0&scrollbar=0" />
<div id="recommended-alert" class="alert">
The recommended browser for this previewer is Microsoft Edge. It does preserve document scroll position when refreshing document, giving better overall experience.
<a class="close" onclick="closeRecommendation()">Click to close</a>
</div>
<div id="connection-issue-alert" class="alert">
Cannot connect to QuestPDF previewer host. Possibly your debugging session has ended. Please close this browser.
</div>
<script>
const recommendationAlert = document.getElementById("recommended-alert");
const connectionIssueAlert = document.getElementById("connection-issue-alert");
function closeRecommendation() {
recommendationAlert.parentElement.removeChild(recommendationAlert);
localStorage.setItem("show-recommendation-alert", 'false');
}
function refreshRecommendation() {
const showRecommendation = localStorage.getItem("show-recommendation-alert");
if (showRecommendation !== 'false')
return;
recommendationAlert.parentElement.removeChild(recommendationAlert);
}
function changeConnectionIssueAlertVisibility(show) {
connectionIssueAlert.style.setProperty("visibility", show ? "visible" : "hidden");
}
async function refreshDocumentLoop() {
window.addEventListener("change", () => reloading = true);
while (true) {
try {
const response = await fetch('/listen');
const value = await response.text();
if (value === "true")
window.location.reload();
changeConnectionIssueAlertVisibility(false);
}
catch {
changeConnectionIssueAlertVisibility(true);
}
}
}
refreshRecommendation();
refreshDocumentLoop();
</script>
</body>
</html>
+12
View File
@@ -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
@@ -11,19 +11,19 @@ namespace QuestPDF.Elements.Text.Items
public override TextMeasurementResult? Measure(TextMeasurementRequest request)
{
SetPageNumber(request.PageContext);
UpdatePageNumberText(request.PageContext);
return MeasureWithoutCache(request);
}
public override void Draw(TextDrawingRequest request)
{
SetPageNumber(request.PageContext);
UpdatePageNumberText(request.PageContext);
base.Draw(request);
}
private void SetPageNumber(IPageContext context)
private void UpdatePageNumberText(IPageContext context)
{
Text = Source(context) ?? PageNumberPlaceholder;
Text = Source(context) ?? string.Empty;
}
}
}
+6 -2
View File
@@ -24,7 +24,7 @@ namespace QuestPDF.Fluent
public class TextPageNumberDescriptor : TextSpanDescriptor
{
internal PageNumberFormatter FormatFunction { get; private set; } = x => (x ?? 123).ToString();
internal PageNumberFormatter FormatFunction { get; private set; } = x => x?.ToString() ?? string.Empty;
internal TextPageNumberDescriptor(TextStyle textStyle) : base(textStyle)
{
@@ -92,6 +92,10 @@ namespace QuestPDF.Fluent
public TextSpanDescriptor Span(string? text)
{
var style = DefaultStyle.Clone();
var descriptor = new TextSpanDescriptor(style);
if (text == null)
return descriptor;
var items = text
.Replace("\r", string.Empty)
@@ -114,7 +118,7 @@ namespace QuestPDF.Fluent
.ToList()
.ForEach(TextBlocks.Add);
return new TextSpanDescriptor(style);
return descriptor;
}
public TextSpanDescriptor Line(string? text)
+1 -1
View File
@@ -4,7 +4,7 @@
<Authors>MarcinZiabek</Authors>
<Company>CodeFlint</Company>
<PackageId>QuestPDF</PackageId>
<Version>2022.3.0</Version>
<Version>2022.3.1</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>
<PackageReleaseNotes>$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt"))</PackageReleaseNotes>
<LangVersion>9</LangVersion>
+6 -12
View File
@@ -1,9 +1,3 @@
<a href="https://www.questpdf.com/" target="_blank">
<img src="https://github.com/QuestPDF/example-invoice/raw/main/images/logo.svg" width="300">
</a>
---
[![Dotnet](https://img.shields.io/badge/platform-.NET-blue)](https://www.nuget.org/packages/QuestPDF/)
[![GitHub Repo stars](https://img.shields.io/github/stars/QuestPDF/QuestPDF)](https://github.com/QuestPDF/QuestPDF/stargazers)
[![Nuget version](https://img.shields.io/nuget/v/QuestPdf)](https://www.nuget.org/packages/QuestPDF/)
@@ -34,22 +28,22 @@ dotnet add package QuestPDF
## Documentation
[![GitHub Repo stars]( https://img.shields.io/badge/%F0%9F%9A%80%20read-getting%20started-blue)](https://www.questpdf.com/documentation/getting-started.html)
[![Getting started tutorial]( https://img.shields.io/badge/%F0%9F%9A%80%20read-getting%20started-blue)](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.
[![GitHub Repo stars](https://img.shields.io/badge/%F0%9F%93%96%20read-API%20reference-blue)](https://www.questpdf.com/documentation/api-reference.html)
[![API reference](https://img.shields.io/badge/%F0%9F%93%96%20read-API%20reference-blue)](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.
[![GitHub Repo stars](https://img.shields.io/badge/%F0%9F%94%8D%20read-patterns%20and%20practices-blue)](https://www.questpdf.com/documentation/patterns-and-practices.html#document-metadata)
[![Patterns and Practices](https://img.shields.io/badge/%F0%9F%94%8D%20read-patterns%20and%20practices-blue)](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.
## Simplicity is the key
How easy it is to start and prototype with QuestPDF? Really easy thanks to its minimal API! Please analyse the code below:
```csharp
```#
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
@@ -92,10 +86,10 @@ Document.Create(container =>
And compare it to the produced PDF file:
<img src="https://github.com/QuestPDF/example-invoice/raw/main/images/minimal-api.png" width="300px">
![invoice](https://raw.githubusercontent.com/QuestPDF/QuestPDF-Documentation/main/images/minimal-example-shadow.png)
## Are you ready for more?
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).
<img src="https://github.com/QuestPDF/example-invoice/raw/main/images/invoice.png" width="400px">
![invoice](https://raw.githubusercontent.com/QuestPDF/QuestPDF-Documentation/main/images/invoice-small.png)
+8 -5
View File
@@ -53,21 +53,24 @@ Install-Package QuestPDF
dotnet add package QuestPDF
// Package reference in .csproj file
<PackageReference Include="QuestPDF" Version="2022.3.0" />
<PackageReference Include="QuestPDF" Version="2022.3.1" />
```
[![Nuget version](https://img.shields.io/badge/package%20details-QuestPDF-blue?logo=nuget)](https://www.nuget.org/packages/QuestPDF/)
## Documentation
[![GitHub Repo stars]( https://img.shields.io/badge/%F0%9F%9A%80%20read-getting%20started-blue)](https://www.questpdf.com/documentation/getting-started.html)
[![Getting started tutorial]( https://img.shields.io/badge/%F0%9F%9A%80%20read-getting%20started-blue)](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.
[![GitHub Repo stars](https://img.shields.io/badge/%F0%9F%93%96%20read-API%20reference-blue)](https://www.questpdf.com/documentation/api-reference.html)
[![API reference](https://img.shields.io/badge/%F0%9F%93%96%20read-API%20reference-blue)](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.
[![GitHub Repo stars](https://img.shields.io/badge/%F0%9F%94%8D%20read-patterns%20and%20practices-blue)](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.
[![Patterns and Practices](https://img.shields.io/badge/%F0%9F%94%8D%20read-patterns%20and%20practices-blue)](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.
<!-- 🚀 📖 ️-->