Compare commits

..

3 Commits

Author SHA1 Message Date
Marcin Ziąbek 2772b5b4a4 Size component cleanup 2021-09-12 20:25:50 +02:00
Marcin Ziąbek 3dfe7a59e0 Fixed space removal 2021-09-12 20:15:41 +02:00
Marcin Ziąbek df5c5524ee Fixed text alignment 2021-09-12 20:10:52 +02:00
91 changed files with 651 additions and 2224 deletions
-33
View File
@@ -1,33 +0,0 @@
using System.IO;
using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class BarCode
{
[Test]
public void Example()
{
FontManager.RegisterFontType("LibreBarcode39", File.OpenRead("LibreBarcode39-Regular.ttf"));
RenderingTest
.Create()
.PageSize(400, 100)
.FileName()
.ShowResults()
.Render(container =>
{
container
.Background(Colors.White)
.AlignCenter()
.AlignMiddle()
.Text("*QuestPDF*", TextStyle.Default.FontType("LibreBarcode39").Size(64));
});
}
}
}
-46
View File
@@ -1,46 +0,0 @@
using System.Diagnostics;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class ContinuousPageDocument : IDocument
{
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public void Compose(IDocumentContainer container)
{
container.Page(page =>
{
page.Margin(20);
page.ContinuousSize(150);
page.Header().Text("Header");
page.Content().PaddingVertical(10).Border(1).Padding(10).Stack(stack =>
{
foreach (var index in Enumerable.Range(1, 100))
stack.Item().Text($"Line {index}", TextStyle.Default.Color(Placeholders.Color()));
});
page.Footer().Text("Footer");
});
}
}
public class ContinuousPageExamples
{
[Test]
public void ContinuousPage()
{
var path = "example.pdf";
new ContinuousPageDocument().GeneratePdf(path);
Process.Start("explorer", path);
}
}
}
+3 -81
View File
@@ -581,7 +581,7 @@ namespace QuestPDF.Examples
{
RenderingTest
.Create()
.PageSize(650, 450)
.PageSize(350, 350)
.FileName()
.Render(container =>
{
@@ -595,8 +595,8 @@ namespace QuestPDF.Examples
foreach (var turns in Enumerable.Range(0, 4))
{
grid.Item()
.Width(300)
.Height(200)
.Width(150)
.Height(150)
.Background(Colors.Grey.Lighten2)
.Padding(10)
.Element(element =>
@@ -778,83 +778,5 @@ namespace QuestPDF.Examples
});
});
}
[Test]
public void ComplexLayout()
{
RenderingTest
.Create()
.PageSize(500, 225)
.FileName()
.Render(container =>
{
container
.Padding(25)
.Stack(stack =>
{
stack.Item().Row(row =>
{
row.RelativeColumn().LabelCell("Label 1");
row.RelativeColumn(3).Grid(grid =>
{
grid.Columns(3);
grid.Item(2).LabelCell("Label 2");
grid.Item().LabelCell("Label 3");
grid.Item(2).ValueCell().Text("Value 2");
grid.Item().ValueCell().Text("Value 3");
});
});
stack.Item().Row(row =>
{
row.RelativeColumn().ValueCell().Text("Value 1");
row.RelativeColumn(3).Grid(grid =>
{
grid.Columns(3);
grid.Item().LabelCell("Label 4");
grid.Item(2).LabelCell("Label 5");
grid.Item().ValueCell().Text("Value 4");
grid.Item(2).ValueCell().Text("Value 5");
});
});
stack.Item().Row(row =>
{
row.RelativeColumn().LabelCell("Label 6");
row.RelativeColumn().ValueCell().Text("Value 6");
});
});
});
}
[Test]
public void DomainSpecificLanguage()
{
RenderingTest
.Create()
.PageSize(600, 310)
.FileName()
.Render(container =>
{
container
.Padding(25)
.Grid(grid =>
{
grid.Columns(10);
for(var i=1; i<=4; i++)
{
grid.Item(2).LabelCell(Placeholders.Label());
grid.Item(3).ValueCell().Image(Placeholders.Image(200, 150));
}
});
});
}
}
}
+4 -5
View File
@@ -12,24 +12,23 @@ namespace QuestPDF.Examples
{
return container
.Border(1)
.Background(dark ? Colors.Grey.Lighten2 : Colors.White)
.Background(dark ? "#EEE" : "#FFF")
.Padding(10);
}
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text, TextStyle.Default.Medium());
public static IContainer LabelCell(this IContainer container) => container.Cell(true);
public static IContainer ValueCell(this IContainer container) => container.Cell(false);
}
public class FrameExample
{
[Test]
public void Frame()
public void Frame(IContainer container)
{
RenderingTest
.Create()
.PageSize(550, 400)
.FileName()
.ShowResults()
.Render(container =>
{
container
@@ -41,7 +40,7 @@ namespace QuestPDF.Examples
{
stack.Item().Row(row =>
{
row.RelativeColumn(2).LabelCell(Placeholders.Label());
row.RelativeColumn(2).LabelCell().Text(Placeholders.Label());
row.RelativeColumn(3).ValueCell().Text(Placeholders.Paragraph());
});
}
Binary file not shown.
+1 -2
View File
@@ -12,9 +12,8 @@ namespace QuestPDF.Examples
{
RenderingTest
.Create()
.PageSize(300, 300)
.PageSize(200, 150)
.FileName()
.ShowResults()
.Render(container =>
{
container
+3 -6
View File
@@ -6,10 +6,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="nunit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Include="nunit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="SkiaSharp" Version="2.80.3" />
<PackageReference Include="SkiaSharp" Version="2.80.2" />
</ItemGroup>
<ItemGroup>
@@ -20,9 +20,6 @@
<None Update="quo-vadis.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="LibreBarcode39-Regular.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
+1 -1
View File
@@ -200,7 +200,7 @@ namespace QuestPDF.Examples
text.DefaultTextStyle(normalStyle);
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.ExternalLocation("wolnelektury.pl", "https://wolnelektury.pl/", normalStyle.Color(Colors.Blue.Medium).Underlined());
text.Span(". Dziękuję za wspieranie polskiego czytelnictwa!");
});
});
+58 -213
View File
@@ -1,6 +1,5 @@
using System;
using System.Linq;
using System.Text;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
@@ -11,140 +10,21 @@ namespace QuestPDF.Examples
{
public class TextExamples
{
[Test]
public void SimpleTextBlock()
{
RenderingTest
.Create()
.PageSize(500, 300)
.FileName()
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(5)
.Box()
.Border(1)
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.Size(20));
text.Span("This is a normal text, followed by an ");
text.Span("underlined red text.", TextStyle.Default.Size(20).Color(Colors.Red.Medium).Underline());
});
});
}
[Test]
public void ParagraphSpacing()
{
RenderingTest
.Create()
.PageSize(500, 300)
.FileName()
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(5)
.Box()
.Border(1)
.Padding(10)
.Text(text =>
{
text.ParagraphSpacing(10);
foreach (var i in Enumerable.Range(1, 3))
{
text.Span($"Paragraph {i}: ", TextStyle.Default.SemiBold());
text.Line(Placeholders.Paragraph());
}
});
});
}
[Test]
public void CustomElement()
{
RenderingTest
.Create()
.PageSize(500, 200)
.FileName()
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(5)
.Box()
.Border(1)
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.Size(20));
text.Span("This is a random image aligned to the baseline: ");
text.Element()
.PaddingBottom(-6)
.Height(24)
.Width(48)
.Image(Placeholders.Image);
text.Span(".");
});
});
}
[Test]
public void TextElements()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.PageSize(500, 400)
.FileName()
.ProducePdf()
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(20)
.Padding(10)
.Box()
.Border(1)
.Padding(5)
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default);
text.AlignLeft();
text.ParagraphSpacing(10);
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());
});
});
}
[Test]
public void TextStack()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.FileName()
.ProducePdf()
.ShowResults()
.Render(container =>
{
container
.Padding(20)
.Padding(10)
.Box()
.Border(1)
.Padding(5)
.Padding(10)
.Text(text =>
{
@@ -152,29 +32,78 @@ namespace QuestPDF.Examples
text.AlignLeft();
text.ParagraphSpacing(10);
foreach (var i in Enumerable.Range(1, 100))
text.Line($"{i}: {Placeholders.Paragraph()}");
text.Span(Placeholders.LoremIpsum());
text.NewLine();
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).Underlined());
text.Span("and this is slightly bigger text.", TextStyle.Default.Size(16));
text.NewLine();
text.Span("The new text element also supports injecting custom content between words: ");
text.Element().PaddingBottom(-10).Height(16).Width(32).Image(Placeholders.Image);
text.Span(".");
text.NewLine();
text.Span("This is page number ");
text.CurrentPageNumber();
text.Span(" out of ");
text.TotalPages();
text.NewLine();
text.ExternalLocation("Please visit QuestPDF website", "https://www.questpdf.com");
text.NewLine();
/*
text.Span("Let's start with bold text. ", TextStyle.Default.Bold().BackgroundColor(Colors.Grey.Lighten3).Size(16));
text.Span("Then something bigger. ", TextStyle.Default.Size(28).Color(Colors.DeepOrange.Darken2).BackgroundColor(Colors.Yellow.Lighten3).Underlined());
text.Span("And tiny \r\n teeny-tiny. ", TextStyle.Default.Size(6));
text.Span("Stroked text also works fine. ", TextStyle.Default.Size(14).Stroked().BackgroundColor(Colors.Grey.Lighten4));
text.Span("0123456789-0123456789-0123456789-0123456789-0123456789-0123456789-0123456789", TextStyle.Default.Size(18));
text.NewLine();
text.NewLine();
text.NewLine();
text.Span("Is it time for lorem ipsum? ", TextStyle.Default.Size(12).Underlined().BackgroundColor(Colors.Grey.Lighten3));
text.Span(Placeholders.LoremIpsum(), TextStyle.Default.Size(12));
text.Span("Before element - ");
text.Element().PaddingBottom(-10).Background(Colors.Red.Lighten4).Height(20).PaddingHorizontal(5).AlignMiddle().Text("Text inside text", TextStyle.Default.Size(8));
text.Span(" - end of element.");
text.NewLine();
// text.Span("And now some colors: ", TextStyle.Default.Size(16).Color(Colors.Green.Medium));
//
// foreach (var i in Enumerable.Range(1, 100))
// {
// text.Span($"{i}: {Placeholders.Sentence()} ", TextStyle.Default.Size(12 + i / 5).LineHeight(2.75f - i / 50f).Color(Placeholders.Color()).BackgroundColor(Placeholders.BackgroundColor()));
// }*/
});
});
}
[Test]
public void SpaceIssue()
public void PageNumber()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.PageSize(500, 400)
.FileName()
.ProducePdf()
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(20)
.Padding(10)
.Box()
.Border(1)
.Padding(5)
.Padding(10)
.Text(text =>
{
@@ -182,94 +111,10 @@ namespace QuestPDF.Examples
text.AlignLeft();
text.ParagraphSpacing(10);
text.Span(Placeholders.LoremIpsum());
text.EmptyLine();
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.EmptyLine();
text.Span("The new text element also supports injecting custom content between words: ");
text.Element().PaddingBottom(-10).Height(16).Width(32).Image(Placeholders.Image);
text.Span(".");
text.EmptyLine();
text.Span("This is page number ");
text.CurrentPageNumber();
text.Span(" out of ");
text.TotalPages();
text.EmptyLine();
text.ExternalLocation("Please visit QuestPDF website", "https://www.questpdf.com");
text.EmptyLine();
text.Span(Placeholders.Paragraphs());
text.EmptyLine();
text.Span(Placeholders.Paragraphs(), TextStyle.Default.Italic());
text.Line("This is target text that does not show up. " + Placeholders.Paragraph());
});
});
}
[Test]
public void HugeList()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.FileName()
.ProducePdf()
.ShowResults()
.Render(container =>
{
container
.Padding(20)
.Padding(10)
.Box()
.Border(1)
.Padding(5)
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default);
text.AlignLeft();
text.ParagraphSpacing(10);
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("The new text element also supports injecting custom content between words: ");
text.Element().PaddingBottom(-10).Height(16).Width(32).Image(Placeholders.Image);
text.Span(".");
text.EmptyLine();
foreach (var i in Enumerable.Range(1, 100))
{
text.Line($"{i}: {Placeholders.Paragraph()}");
text.ExternalLocation("Please visit QuestPDF website", "https://www.questpdf.com");
text.Span("This is page number ");
text.CurrentPageNumber();
text.Span(" out of ");
text.TotalPages();
text.EmptyLine();
}
});
});
}
@@ -1,6 +1,5 @@
using System;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.ReportSample.Layouts
@@ -25,7 +24,7 @@ namespace QuestPDF.ReportSample.Layouts
{
container
.AspectRatio(AspectRatio)
.Background(Colors.Grey.Lighten3)
.Background("#EEEEEE")
.Image(Source(Size.Zero));
}
}
@@ -48,13 +48,13 @@ namespace QuestPDF.ReportSample.Layouts
{
grid.Columns(6);
grid.Item().LabelCell().Text("Date");
grid.Item(2).ValueCell().Text(Model.Date?.ToString("g") ?? string.Empty);
grid.Item().LabelCell().Text("Location");
grid.Item(2).ValueCell().Text(Model.Location.Format());
grid.Item().LabelCell().Text("Date", Typography.Normal);
grid.Item(2).ValueCell().Text(Model.Date?.ToString("g") ?? string.Empty, Typography.Normal);
grid.Item().LabelCell().Text("Location", Typography.Normal);
grid.Item(2).ValueCell().Text(Model.Location.Format(), Typography.Normal);
grid.Item().LabelCell().Text("Comments");
grid.Item(5).ValueCell().Text(Model.Comments);
grid.Item().LabelCell().Text("Comments", Typography.Normal);
grid.Item(5).ValueCell().Text(Model.Comments, Typography.Normal);
});
}
}
@@ -30,11 +30,11 @@ namespace QuestPDF.ReportSample.Layouts
{
stack.Item().EnsureSpace(25).Row(row =>
{
row.ConstantColumn(150).LabelCell().Text(part.Label);
row.ConstantColumn(150).LabelCell().Text(part.Label, Typography.Normal);
var frame = row.RelativeColumn().ValueCell();
if (part is ReportSectionText text)
frame.ShowEntire().Text(text.Text);
frame.ShowEntire().Text(text.Text, Typography.Normal);
if (part is ReportSectionMap map)
frame.Element(x => MapElement(x, map));
@@ -51,7 +51,7 @@ namespace QuestPDF.ReportSample.Layouts
{
if (model.ImageSource == null || model.Location == null)
{
container.Text("No location provided");
container.Text("No location provided", Typography.Normal);
return;
}
@@ -60,7 +60,7 @@ namespace QuestPDF.ReportSample.Layouts
stack.Spacing(5);
stack.Item().MaxWidth(250).AspectRatio(4 / 3f).Image(Placeholders.Image);
stack.Item().Text(model.Location.Format());
stack.Item().Text(model.Location.Format(), Typography.Normal);
});
}
@@ -68,7 +68,7 @@ namespace QuestPDF.ReportSample.Layouts
{
if (model.Photos.Count == 0)
{
container.Text("No photos");
container.Text("No photos", Typography.Normal);
return;
}
@@ -25,7 +25,6 @@ namespace QuestPDF.ReportSample.Layouts
public void Compose(IDocumentContainer container)
{
container
.DefaultTextStyle(Typography.Normal)
.Page(page =>
{
page.MarginVertical(40);
@@ -38,6 +37,8 @@ namespace QuestPDF.ReportSample.Layouts
page.Footer().AlignCenter().Text(text =>
{
text.DefaultTextStyle(Typography.Normal);
text.CurrentPageNumber();
text.Span(" / ");
text.TotalPages();
@@ -68,8 +69,8 @@ namespace QuestPDF.ReportSample.Layouts
{
grid.Item().Text(text =>
{
text.Span($"{field.Label}: ", TextStyle.Default.SemiBold());
text.Span(field.Value);
text.Span($"{field.Label}: ", Typography.Normal.SemiBold());
text.Span(field.Value, Typography.Normal);
});
}
});
@@ -41,9 +41,9 @@ namespace QuestPDF.ReportSample.Layouts
.InternalLink(locationName)
.Row(row =>
{
row.ConstantColumn(25).Text($"{number}.");
row.RelativeColumn().Text(locationName);
row.ConstantColumn(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName));
row.ConstantColumn(25).Text($"{number}.", Typography.Normal);
row.RelativeColumn().Text(locationName, Typography.Normal);
row.ConstantColumn(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName, Typography.Normal));
});
}
}
@@ -8,10 +8,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="nunit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Include="DeepCloner" Version="0.10.2" />
<PackageReference Include="nunit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="SkiaSharp" Version="2.80.3" />
<PackageReference Include="SkiaSharp" Version="2.80.2" />
</ItemGroup>
<ItemGroup>
+1 -1
View File
@@ -8,6 +8,6 @@ namespace QuestPDF.ReportSample
{
public static TextStyle Title => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Darken3).Size(26).Black();
public static TextStyle Headline => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Medium).Size(16).SemiBold();
public static TextStyle Normal => TextStyle.Default.FontType(Fonts.Verdana).Color(Colors.Black).Size(10).LineHeight(1.2f);
public static TextStyle Normal => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Black).Size(11).LineHeight(1.1f);
}
}
+5 -1
View File
@@ -10,7 +10,11 @@ namespace QuestPDF.UnitTests
public class AlignmentTests
{
[Test]
public void Measure() => SimpleContainerTests.Measure<Alignment>();
public void Measure_ShouldHandleNullChild() => new Alignment().MeasureWithoutChild();
[Test]
public void Draw_ShouldHandleNullChild() => new Alignment().DrawWithoutChild();
[Test]
public void Draw_HorizontalCenter_VerticalCenter()
{
+6
View File
@@ -9,6 +9,12 @@ namespace QuestPDF.UnitTests
[TestFixture]
public class AspectRatioTests
{
[Test]
public void Measure_ShouldHandleNullChild() => new AspectRatio().MeasureWithoutChild();
[Test]
public void Draw_ShouldHandleNullChild() => new AspectRatio().DrawWithoutChild();
[Test]
public void Measure_FitWidth_EnoughSpace_FullRender()
{
+2 -2
View File
@@ -10,8 +10,8 @@ namespace QuestPDF.UnitTests
public class BackgroundTests
{
[Test]
public void Measure() => SimpleContainerTests.Measure<Background>();
public void Measure_ShouldHandleNullChild() => new Background().MeasureWithoutChild();
[Test]
public void Draw_ShouldHandleNullChild()
{
+2 -2
View File
@@ -11,8 +11,8 @@ namespace QuestPDF.UnitTests
public class BorderTests
{
[Test]
public void Measure() => SimpleContainerTests.Measure<Border>();
public void Measure_ShouldHandleNullChild() => new Border().MeasureWithoutChild();
[Test]
public void ComponentShouldNotAffectLayout()
{
-56
View File
@@ -1,56 +0,0 @@
using NUnit.Framework;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class BoxTests
{
[Test]
public void Measure() => SimpleContainerTests.Measure<Box>();
[Test]
public void Draw_Wrap()
{
TestPlan
.For(x => new Box
{
Child = x.CreateChild()
})
.DrawElement(new Size(400, 300))
.ExpectChildMeasure(expectedInput: new Size(400, 300), returns: new Wrap())
.CheckDrawResult();
}
[Test]
public void Measure_PartialRender()
{
TestPlan
.For(x => new Box
{
Child = x.CreateChild()
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure(expectedInput: new Size(400, 300), returns: new PartialRender(200, 100))
.ExpectChildDraw(new Size(200, 100))
.CheckDrawResult();
}
[Test]
public void Measure_FullRender()
{
TestPlan
.For(x => new Box
{
Child = x.CreateChild()
})
.MeasureElement(new Size(500, 400))
.ExpectChildMeasure(expectedInput: new Size(500, 400), returns: new FullRender(300, 200))
.ExpectChildDraw(new Size(300, 200))
.CheckDrawResult();
}
}
}
+6
View File
@@ -9,6 +9,12 @@ namespace QuestPDF.UnitTests
[TestFixture]
public class ConstrainedTests
{
[Test]
public void Measure_ShouldHandleNullChild() => new Constrained().MeasureWithoutChild();
[Test]
public void Draw_ShouldHandleNullChild() => new Constrained().DrawWithoutChild();
[Test]
public void Measure_MinHeight_ExpectWrap()
{
+10
View File
@@ -0,0 +1,10 @@
using NUnit.Framework;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class DebugTests
{
}
}
-3
View File
@@ -78,8 +78,5 @@ namespace QuestPDF.UnitTests
.ExpectChildMeasure(new Size(400, 300), new FullRender(300, 250))
.CheckMeasureResult(new FullRender(300, 250));
}
[Test]
public void Draw() => SimpleContainerTests.Draw<EnsureSpace>();
}
}
+6 -3
View File
@@ -9,6 +9,12 @@ namespace QuestPDF.UnitTests
[TestFixture]
public class ExtendTests
{
[Test]
public void Measure_ShouldHandleNullChild() => new Extend().MeasureWithoutChild();
[Test]
public void Draw_ShouldHandleNullChild() => new Extend().DrawWithoutChild();
[Test]
public void Measure_ReturnsWrap_WhenChildReturnsWrap()
{
@@ -81,8 +87,5 @@ namespace QuestPDF.UnitTests
.ExpectChildMeasure(new Size(400, 200), new FullRender(100, 100))
.CheckMeasureResult(new FullRender(100, 200));
}
[Test]
public void Draw() => SimpleContainerTests.Draw<Extend>();
}
}
-15
View File
@@ -1,15 +0,0 @@
using NUnit.Framework;
using QuestPDF.Elements;
using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class ExternalLinkTests
{
[Test]
public void Measure() => SimpleContainerTests.Measure<ExternalLink>();
// TODO: consider tests for the Draw method
}
}
+27 -27
View File
@@ -39,30 +39,30 @@ namespace QuestPDF.UnitTests
// assert
var expected = new Container();
expected.Stack(stack =>
expected.Container().Stack(stack =>
{
stack.Item().Row(row =>
{
row.RelativeColumn(6).Element(childA);
row.RelativeColumn(4).Element(childB);
row.RelativeColumn(6).Container().Element(childA);
row.RelativeColumn(4).Container().Element(childB);
row.RelativeColumn(2);
});
stack.Item().Row(row =>
{
row.RelativeColumn(4).Element(childC);
row.RelativeColumn(2).Element(childD);
row.RelativeColumn(4).Container().Element(childC);
row.RelativeColumn(2).Container().Element(childD);
row.RelativeColumn(6);
});
stack.Item().Row(row =>
{
row.RelativeColumn(8).Element(childE);
row.RelativeColumn(8).Container().Element(childE);
row.RelativeColumn(4);
});
});
TestPlan.CompareOperations(structure, expected);
structure.Should().BeEquivalentTo(expected, o => o.WithTracing().WithAutoConversion().WithStrictOrdering().IncludingAllRuntimeProperties());
}
[Test]
@@ -93,33 +93,33 @@ namespace QuestPDF.UnitTests
// assert
var expected = new Container();
expected.Stack(stack =>
expected.Container().Stack(stack =>
{
stack.Item().Row(row =>
{
row.RelativeColumn(1);
row.RelativeColumn(6).Element(childA);
row.RelativeColumn(4).Element(childB);
row.RelativeColumn(6).Container().Element(childA);
row.RelativeColumn(4).Container().Element(childB);
row.RelativeColumn(1);
});
stack.Item().Row(row =>
{
row.RelativeColumn(3);
row.RelativeColumn(4).Element(childC);
row.RelativeColumn(2).Element(childD);
row.RelativeColumn(4).Container().Element(childC);
row.RelativeColumn(2).Container().Element(childD);
row.RelativeColumn(3);
});
stack.Item().Row(row =>
{
row.RelativeColumn(2);
row.RelativeColumn(8).Element(childE);
row.RelativeColumn(8).Container().Element(childE);
row.RelativeColumn(2);
});
});
TestPlan.CompareOperations(structure, expected);
structure.Should().BeEquivalentTo(expected, o => o.WithTracing().WithAutoConversion().WithStrictOrdering().IncludingAllRuntimeProperties());
}
[Test]
@@ -150,30 +150,30 @@ namespace QuestPDF.UnitTests
// assert
var expected = new Container();
expected.Stack(stack =>
expected.Container().Stack(stack =>
{
stack.Item().Row(row =>
{
row.RelativeColumn(2);
row.RelativeColumn(6).Element(childA);
row.RelativeColumn(4).Element(childB);
row.RelativeColumn(6).Container().Element(childA);
row.RelativeColumn(4).Container().Element(childB);
});
stack.Item().Row(row =>
{
row.RelativeColumn(6);
row.RelativeColumn(4).Element(childC);
row.RelativeColumn(2).Element(childD);
row.RelativeColumn(4).Container().Element(childC);
row.RelativeColumn(2).Container().Element(childD);
});
stack.Item().Row(row =>
{
row.RelativeColumn(4);
row.RelativeColumn(8).Element(childE);
row.RelativeColumn(8).Container().Element(childE);
});
});
TestPlan.CompareOperations(structure, expected);
structure.Should().BeEquivalentTo(expected, o => o.WithTracing().WithAutoConversion().WithStrictOrdering().IncludingAllRuntimeProperties());
}
#endregion
@@ -210,7 +210,7 @@ namespace QuestPDF.UnitTests
// assert
var expected = new Container();
expected.Stack(stack =>
expected.Container().Stack(stack =>
{
stack.Spacing(20);
@@ -219,8 +219,8 @@ namespace QuestPDF.UnitTests
row.Spacing(30);
row.RelativeColumn(3);
row.RelativeColumn(5).Element(childA);
row.RelativeColumn(5).Element(childB);
row.RelativeColumn(5).Container().Element(childA);
row.RelativeColumn(5).Container().Element(childB);
row.RelativeColumn(3);
});
@@ -229,7 +229,7 @@ namespace QuestPDF.UnitTests
row.Spacing(30);
row.RelativeColumn(3);
row.RelativeColumn(10).Element(childC);
row.RelativeColumn(10).Container().Element(childC);
row.RelativeColumn(3);
});
@@ -238,12 +238,12 @@ namespace QuestPDF.UnitTests
row.Spacing(30);
row.RelativeColumn(2);
row.RelativeColumn(12).Element(childD);
row.RelativeColumn(12).Container().Element(childD);
row.RelativeColumn(2);
});
});
TestPlan.CompareOperations(structure, expected);
structure.Should().BeEquivalentTo(expected, o => o.WithTracing().WithAutoConversion().WithStrictOrdering().IncludingAllRuntimeProperties().AllowingInfiniteRecursion());
}
#endregion
+30
View File
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests
{
public static class Helpers
{
public static Random Random = new Random();
public static Size RandomSize => new Size(Random.Next(200, 400), Random.Next(100, 200));
public static void ShouldEqual(this IEnumerable<OperationBase> commands, IEnumerable<OperationBase> expected)
{
commands.Should().HaveSameCount(expected);
commands
.Zip(expected)
.ToList()
.ForEach(x =>
{
x.First.Should().BeOfType(x.Second.GetType());
x.First.Should().BeEquivalentTo(x.Second, y => y.RespectingRuntimeTypes());
});
}
}
}
+6
View File
@@ -11,6 +11,12 @@ namespace QuestPDF.UnitTests
[TestFixture]
public class ImageTests
{
[Test]
public void Measure_ShouldHandleNullChild() => new AspectRatio().MeasureWithoutChild();
[Test]
public void Draw_ShouldHandleNullChild() => new AspectRatio().DrawWithoutChild();
[Test]
public void Measure_TakesAvailableSpaceRegardlessOfSize()
{
-15
View File
@@ -1,15 +0,0 @@
using NUnit.Framework;
using QuestPDF.Elements;
using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class InternalLinkTests
{
[Test]
public void Measure() => SimpleContainerTests.Measure<InternalLink>();
// TODO: consider tests for the Draw method
}
}
@@ -1,15 +0,0 @@
using NUnit.Framework;
using QuestPDF.Elements;
using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class InternalLocationTests
{
[Test]
public void Measure() => SimpleContainerTests.Measure<InternalLink>();
// TODO: consider tests for the Draw method
}
}
-131
View File
@@ -1,131 +0,0 @@
using System.Collections.Generic;
using NUnit.Framework;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class LayersTests
{
private const string BackgroundLayer = "background";
private const string MainLayer = "main";
private const string ForegroundLayer = "foreground";
private static Layers GetLayers(TestPlan x)
{
return new Layers
{
Children = new List<Layer>
{
new Layer
{
Child = x.CreateChild(BackgroundLayer)
},
new Layer
{
Child = x.CreateChild(MainLayer),
IsPrimary = true
},
new Layer
{
Child = x.CreateChild(ForegroundLayer)
}
}
};
}
#region measure
[Test]
public void Measure_Wrap()
{
TestPlan
.For(GetLayers)
.MeasureElement(new Size(800, 600))
.ExpectChildMeasure(MainLayer, new Size(800, 600), new Wrap())
.CheckMeasureResult(new Wrap());
}
[Test]
public void Measure_PartialRender()
{
TestPlan
.For(GetLayers)
.MeasureElement(new Size(800, 600))
.ExpectChildMeasure(MainLayer, new Size(800, 600), new PartialRender(700, 500))
.CheckMeasureResult(new PartialRender(700, 500));
}
[Test]
public void Measure_FullRender()
{
TestPlan
.For(GetLayers)
.MeasureElement(new Size(800, 600))
.ExpectChildMeasure(MainLayer, new Size(800, 600), new FullRender(500, 400))
.CheckMeasureResult(new FullRender(500, 400));
}
#endregion
#region draw
[Test]
public void Draw_Simple()
{
TestPlan
.For(GetLayers)
.MeasureElement(new Size(800, 600))
.ExpectChildMeasure(BackgroundLayer, new Size(800, 600), new FullRender(100, 200))
.ExpectChildMeasure(MainLayer, new Size(800, 600), new PartialRender(200, 300))
.ExpectChildMeasure(ForegroundLayer, new Size(800, 600), new FullRender(300, 400))
.ExpectChildDraw(BackgroundLayer, new Size(800, 600))
.ExpectChildDraw(MainLayer, new Size(800, 600))
.ExpectChildDraw(ForegroundLayer, new Size(800, 600))
.CheckDrawResult();
}
[Test]
public void Draw_WhenSecondaryLayerReturnsWrap_SkipThatLayer_1()
{
TestPlan
.For(GetLayers)
.MeasureElement(new Size(800, 600))
.ExpectChildMeasure(BackgroundLayer, new Size(800, 600), new PartialRender(100, 200))
.ExpectChildMeasure(MainLayer, new Size(800, 600), new PartialRender(200, 300))
.ExpectChildMeasure(ForegroundLayer, new Size(800, 600), new Wrap())
.ExpectChildDraw(BackgroundLayer, new Size(800, 600))
.ExpectChildDraw(MainLayer, new Size(800, 600))
.CheckDrawResult();
}
[Test]
public void Draw_WhenSecondaryLayerReturnsWrap_SkipThatLayer_2()
{
TestPlan
.For(GetLayers)
.MeasureElement(new Size(800, 600))
.ExpectChildMeasure(BackgroundLayer, new Size(800, 600), new Wrap())
.ExpectChildMeasure(MainLayer, new Size(800, 600), new PartialRender(200, 300))
.ExpectChildMeasure(ForegroundLayer, new Size(800, 600), new PartialRender(300, 400))
.ExpectChildDraw(MainLayer, new Size(800, 600))
.ExpectChildDraw(ForegroundLayer, new Size(800, 600))
.CheckDrawResult();
}
#endregion
}
}
+6
View File
@@ -9,6 +9,12 @@ namespace QuestPDF.UnitTests
[TestFixture]
public class PaddingTests
{
[Test]
public void Measure_ShouldHandleNullChild() => new Padding().MeasureWithoutChild();
[Test]
public void Draw_ShouldHandleNullChild() => new Padding().DrawWithoutChild();
private Padding GetPadding(TestPlan plan)
{
return new Padding()
+10
View File
@@ -0,0 +1,10 @@
using NUnit.Framework;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class PageNumberTests
{
}
}
+5 -4
View File
@@ -6,11 +6,12 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.1.0" />
<PackageReference Include="nunit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Include="FluentAssertions" Version="5.10.3" />
<PackageReference Include="Moq" Version="4.13.1" />
<PackageReference Include="nunit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
<PackageReference Include="SkiaSharp" Version="2.80.3" />
<PackageReference Include="SkiaSharp" Version="2.80.2" />
</ItemGroup>
<ItemGroup>
-31
View File
@@ -1,31 +0,0 @@
using NUnit.Framework;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class RotateTests
{
[Test]
public void Measure() => SimpleContainerTests.Measure<Rotate>();
[Test]
public void Draw()
{
TestPlan
.For(x => new Rotate
{
Child = x.CreateChild(),
Angle = 123
})
.DrawElement(new Size(400, 300))
.ExpectCanvasRotate(123)
.ExpectChildDraw(new Size(400, 300))
.ExpectCanvasRotate(-123)
.CheckDrawResult();
}
}
}
+3 -154
View File
@@ -1,7 +1,6 @@
using NUnit.Framework;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.TestEngine;
@@ -110,158 +109,8 @@ namespace QuestPDF.UnitTests
#endregion
#region Structure
[Test]
public void Structure_RelativeColumnsHandling()
{
// arrange
var childA = TestPlan.CreateUniqueElement();
var childB = TestPlan.CreateUniqueElement();
var childC = TestPlan.CreateUniqueElement();
var childD = TestPlan.CreateUniqueElement();
var childE = TestPlan.CreateUniqueElement();
const int spacing = 25;
var availableSpace = new Size(1100, 400);
// act
var value = new Container();
value.Row(row =>
{
row.Spacing(spacing);
row.ConstantColumn(150).Element(childA);
row.ConstantColumn(250).Element(childB);
row.RelativeColumn(1).Element(childC);
row.RelativeColumn(2).Element(childD);
row.RelativeColumn(3).Element(childE);
});
// assert
var expected = new Container();
expected.Row(row =>
{
row.Spacing(spacing);
row.ConstantColumn(150).Element(childA);
row.ConstantColumn(250).Element(childB);
row.ConstantColumn(100).Element(childC);
row.ConstantColumn(200).Element(childD);
row.ConstantColumn(300).Element(childE);
});
TestPlan.CompareOperations(value, expected, availableSpace);
}
[Test]
public void Structure_Tree()
{
// arrange
var childA = TestPlan.CreateUniqueElement();
var childB = TestPlan.CreateUniqueElement();
var childC = TestPlan.CreateUniqueElement();
var childD = TestPlan.CreateUniqueElement();
var childE = TestPlan.CreateUniqueElement();
const int spacing = 25;
var availableSpace = new Size(1200, 400);
// act
var value = new Container();
value.Row(row =>
{
row.Spacing(spacing);
row.ConstantColumn(150).Element(childA);
row.ConstantColumn(200).Element(childB);
row.ConstantColumn(250).Element(childC);
row.RelativeColumn(2).Element(childD);
row.RelativeColumn(3).Element(childE);
});
// assert
var expected = new SimpleRow
{
Left = new SimpleRow
{
Left = new SimpleRow
{
Left = new Constrained
{
MinWidth = 150,
MaxWidth = 150,
Child = childA
},
Right = new Constrained
{
MinWidth = 25,
MaxWidth = 25
}
},
Right = new SimpleRow
{
Left = new Constrained
{
MinWidth = 200,
MaxWidth = 200,
Child = childB
},
Right = new Constrained
{
MinWidth = 25,
MaxWidth = 25
}
}
},
Right = new SimpleRow
{
Left = new SimpleRow
{
Left = new Constrained
{
MinWidth = 250,
MaxWidth = 250,
Child = childC
},
Right = new Constrained
{
MinWidth = 25,
MaxWidth = 25
}
},
Right = new SimpleRow
{
Left = new Constrained
{
MinWidth = 200,
MaxWidth = 200,
Child = childD
},
Right = new SimpleRow
{
Left = new Constrained
{
MinWidth = 25,
MaxWidth = 25
},
Right = new Constrained
{
MinWidth = 300,
MaxWidth = 300,
Child = childE
}
}
}
}
};
TestPlan.CompareOperations(value, expected, availableSpace);
}
#endregion
// TODO: add tests for the spacing property
// TODO: add tests for the tree builder method
// TODO: add tests for relative column
}
}
-152
View File
@@ -1,152 +0,0 @@
using NUnit.Framework;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class ScaleTests
{
#region measure
[Test]
public void Measure_Wrap()
{
TestPlan
.For(x => new Scale
{
Child = x.CreateChild(),
ScaleX = 3,
ScaleY = 2
})
.MeasureElement(new Size(900, 800))
.ExpectChildMeasure(new Size(300, 400), new Wrap())
.CheckMeasureResult(new Wrap());
}
[Test]
public void Measure_PartialRender()
{
TestPlan
.For(x => new Scale
{
Child = x.CreateChild(),
ScaleX = 3,
ScaleY = 2
})
.MeasureElement(new Size(900, 800))
.ExpectChildMeasure(new Size(300, 400), new PartialRender(200, 350))
.CheckMeasureResult(new PartialRender(600, 700));
}
[Test]
public void Measure_FullRender()
{
TestPlan
.For(x => new Scale
{
Child = x.CreateChild(),
ScaleX = 3,
ScaleY = 2
})
.MeasureElement(new Size(900, 800))
.ExpectChildMeasure(new Size(300, 400), new FullRender(250, 300))
.CheckMeasureResult(new FullRender(750, 600));
}
[Test]
public void Measure_NegativeScaleX()
{
TestPlan
.For(x => new Scale
{
Child = x.CreateChild(),
ScaleX = -2,
ScaleY = 1
})
.MeasureElement(new Size(800, 600))
.ExpectChildMeasure(new Size(400, 600), new FullRender(300, 500))
.CheckMeasureResult(new FullRender(600, 500));
}
[Test]
public void Measure_NegativeScaleY()
{
TestPlan
.For(x => new Scale
{
Child = x.CreateChild(),
ScaleX = 1,
ScaleY = -3
})
.MeasureElement(new Size(800, 600))
.ExpectChildMeasure(new Size(800, 200), new FullRender(800, 100))
.CheckMeasureResult(new FullRender(800, 300));
}
#endregion
#region draw
[Test]
public void Draw_Simple()
{
TestPlan
.For(x => new Scale
{
Child = x.CreateChild(),
ScaleX = 3,
ScaleY = 2
})
.DrawElement(new Size(900, 800))
.ExpectCanvasTranslate(0, 0)
.ExpectCanvasScale(3, 2)
.ExpectChildDraw(new Size(300, 400))
.ExpectCanvasScale(1/3f, 1/2f)
.ExpectCanvasTranslate(0, 0)
.CheckDrawResult();
}
[Test]
public void Draw_NegativeScaleX()
{
TestPlan
.For(x => new Scale
{
Child = x.CreateChild(),
ScaleX = -3,
ScaleY = 2
})
.DrawElement(new Size(900, 800))
.ExpectCanvasTranslate(900, 0)
.ExpectCanvasScale(-3, 2)
.ExpectChildDraw(new Size(300, 400))
.ExpectCanvasScale(-1/3f, 1/2f)
.ExpectCanvasTranslate(-900, 0)
.CheckDrawResult();
}
[Test]
public void Draw_NegativeScaleY()
{
TestPlan
.For(x => new Scale
{
Child = x.CreateChild(),
ScaleX = 3,
ScaleY = -2
})
.DrawElement(new Size(900, 800))
.ExpectCanvasTranslate(0, 800)
.ExpectCanvasScale(3, -2)
.ExpectChildDraw(new Size(300, 400))
.ExpectCanvasScale(1/3f, -1/2f)
.ExpectCanvasTranslate(0, -800)
.CheckDrawResult();
}
#endregion
}
}
-3
View File
@@ -47,8 +47,5 @@ namespace QuestPDF.UnitTests
.ExpectChildMeasure(new Size(400, 300), new FullRender(300, 200))
.CheckMeasureResult(new FullRender(300, 200));
}
[Test]
public void Draw() => SimpleContainerTests.Draw<ShowEntire>();
}
}
+29 -2
View File
@@ -1,4 +1,5 @@
using NUnit.Framework;
using Moq;
using NUnit.Framework;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
@@ -10,7 +11,33 @@ namespace QuestPDF.UnitTests
public class ShowOnceTest
{
[Test]
public void Draw()
public void Measure_ShouldHandleNullChild() => new ShowOnce().MeasureWithoutChild();
[Test]
public void Draw_ShouldHandleNullChild() => new ShowOnce().DrawWithoutChild();
[Test]
public void ShouldRenderOnce_WhenRenderingCalledMultipleTimes()
{
var child = new Mock<Element>();
child
.Setup(x => x.Measure(It.IsAny<Size>()))
.Returns(() => new FullRender(Size.Zero));
var element = new ShowOnce()
{
Child = child.Object
};
element.Draw(Size.Zero);
element.Draw(Size.Zero);
child.Verify(x => x.Draw(It.IsAny<Size>()), Times.Once);
}
[Test]
public void Draw_HorizontalRight_VerticalTop()
{
TestPlan
.For(x => new ShowOnce()
-162
View File
@@ -1,162 +0,0 @@
using NUnit.Framework;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class SimpleRotateTests
{
#region measure
[Test]
public void Measure_Wrap()
{
TestPlan
.For(x => new SimpleRotate
{
Child = x.CreateChild(),
TurnCount = 0
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure(new Size(400, 300), new Wrap())
.CheckMeasureResult(new Wrap());
}
[Test]
public void Measure_PartialRender()
{
TestPlan
.For(x => new SimpleRotate
{
Child = x.CreateChild(),
TurnCount = 0
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure(new Size(400, 300), new PartialRender(300, 200))
.CheckMeasureResult(new PartialRender(300, 200));
}
[Test]
public void Measure_RotateRight()
{
TestPlan
.For(x => new SimpleRotate
{
Child = x.CreateChild(),
TurnCount = 1
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure(new Size(300, 400), new FullRender(200, 300))
.CheckMeasureResult(new FullRender(300, 200));
}
[Test]
public void Measure_RotateFlip()
{
TestPlan
.For(x => new SimpleRotate
{
Child = x.CreateChild(),
TurnCount = 2
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure(new Size(400, 300), new FullRender(200, 100))
.CheckMeasureResult(new FullRender(200, 100));
}
[Test]
public void Measure_RotateLeft()
{
TestPlan
.For(x => new SimpleRotate
{
Child = x.CreateChild(),
TurnCount = 3 // or -1
})
.MeasureElement(new Size(500, 400))
.ExpectChildMeasure(new Size(400, 500), new FullRender(300, 350))
.CheckMeasureResult(new FullRender(350, 300));
}
#endregion
#region draw
[Test]
public void Draw_Simple()
{
TestPlan
.For(x => new SimpleRotate
{
Child = x.CreateChild(),
TurnCount = 0
})
.DrawElement(new Size(640, 480))
.ExpectCanvasTranslate(0, 0)
.ExpectCanvasRotate(0)
.ExpectChildDraw(new Size(640, 480))
.ExpectCanvasRotate(0)
.ExpectCanvasTranslate(0, 0)
.CheckDrawResult();
}
[Test]
public void Draw_RotateRight()
{
TestPlan
.For(x => new SimpleRotate
{
Child = x.CreateChild(),
TurnCount = 1
})
.DrawElement(new Size(640, 480))
.ExpectCanvasTranslate(640, 0)
.ExpectCanvasRotate(90)
.ExpectChildDraw(new Size(480, 640))
.ExpectCanvasRotate(-90)
.ExpectCanvasTranslate(-640, 0)
.CheckDrawResult();
}
[Test]
public void Draw_RotateFlip()
{
TestPlan
.For(x => new SimpleRotate
{
Child = x.CreateChild(),
TurnCount = 2
})
.DrawElement(new Size(640, 480))
.ExpectCanvasTranslate(640, 480)
.ExpectCanvasRotate(180)
.ExpectChildDraw(new Size(640, 480))
.ExpectCanvasRotate(-180)
.ExpectCanvasTranslate(-640, -480)
.CheckDrawResult();
}
[Test]
public void Draw_RotateLeft()
{
TestPlan
.For(x => new SimpleRotate
{
Child = x.CreateChild(),
TurnCount = 3 // or -1
})
.DrawElement(new Size(640, 480))
.ExpectCanvasTranslate(0, 480)
.ExpectCanvasRotate(270)
.ExpectChildDraw(new Size(480, 640))
.ExpectCanvasRotate(-270)
.ExpectCanvasTranslate(0, -480)
.CheckDrawResult();
}
#endregion
}
}
+3 -79
View File
@@ -1,8 +1,6 @@
using FluentAssertions;
using NUnit.Framework;
using NUnit.Framework;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.TestEngine;
@@ -231,81 +229,7 @@ namespace QuestPDF.UnitTests
#endregion
#region Structure
[Test]
public void Structure_Simple()
{
// arrange
var childA = TestPlan.CreateUniqueElement();
var childB = TestPlan.CreateUniqueElement();
var childC = TestPlan.CreateUniqueElement();
var childD = TestPlan.CreateUniqueElement();
var childE = TestPlan.CreateUniqueElement();
const int spacing = 20;
// act
var structure = new Container();
structure.Stack(stack =>
{
stack.Spacing(spacing);
stack.Item().Element(childA);
stack.Item().Element(childB);
stack.Item().Element(childC);
stack.Item().Element(childD);
stack.Item().Element(childE);
});
// assert
var expected = new Padding
{
Bottom = -spacing,
Child = new SimpleStack
{
First = new SimpleStack
{
First = new Padding
{
Bottom = spacing,
Child = childA
},
Second = new Padding
{
Bottom = spacing,
Child = childB
}
},
Second = new SimpleStack
{
First = new Padding
{
Bottom = spacing,
Child = childC
},
Second = new SimpleStack
{
First = new Padding
{
Bottom = spacing,
Child = childD
},
Second = new Padding
{
Bottom = spacing,
Child = childE
}
}
}
}
};
TestPlan.CompareOperations(structure, expected);
}
#endregion
// TODO: add tests for the spacing property
// TODO: add tests for the tree builder method
}
}
@@ -4,25 +4,41 @@ using SkiaSharp;
namespace QuestPDF.UnitTests.TestEngine
{
internal class MockCanvas : ICanvas
internal class CanvasMock : ICanvas
{
public Action<Position> TranslateFunc { get; set; }
public Action<float> RotateFunc { get; set; }
public Action<float, float> ScaleFunc { get; set; }
public Action<SKImage, Position, Size> DrawImageFunc { get; set; }
public Action<string, Position, TextStyle> DrawTextFunc { get; set; }
public Action<Position, Size, string> DrawRectFunc { get; set; }
public void Translate(Position vector) => TranslateFunc(vector);
public void Rotate(float angle) => RotateFunc(angle);
public void Scale(float scaleX, float scaleY) => ScaleFunc(scaleX, scaleY);
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 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 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 DrawLink(string url, Size size)
{
throw new NotImplementedException();
}
public Size MeasureText(string text, TextStyle style)
{
return new Size(text.Length * style.Size, style.Size);
}
}
}
@@ -1,25 +0,0 @@
using System;
using System.Collections.Generic;
using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.TestEngine.Operations;
using SkiaSharp;
namespace QuestPDF.UnitTests.TestEngine
{
internal class OperationRecordingCanvas : ICanvas
{
public ICollection<OperationBase> Operations { get; } = new List<OperationBase>();
public void Translate(Position vector) => Operations.Add(new CanvasTranslateOperation(vector));
public void Rotate(float angle) => Operations.Add(new CanvasRotateOperation(angle));
public void Scale(float scaleX, float scaleY) => Operations.Add(new CanvasScaleOperation(scaleX, scaleY));
public void DrawRectangle(Position vector, Size size, string color) => Operations.Add(new CanvasDrawRectangleOperation(vector, size, color));
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 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();
}
}
@@ -2,12 +2,12 @@
namespace QuestPDF.UnitTests.TestEngine.Operations
{
internal class CanvasDrawImageOperation : OperationBase
internal class CanvasDrawImageOperationBase : OperationBase
{
public Position Position { get; }
public Size Size { get; }
public CanvasDrawImageOperation(Position position, Size size)
public CanvasDrawImageOperationBase(Position position, Size size)
{
Position = position;
Size = size;
@@ -2,13 +2,13 @@
namespace QuestPDF.UnitTests.TestEngine.Operations
{
internal class CanvasDrawRectangleOperation : OperationBase
internal class CanvasDrawRectangleOperationBase : OperationBase
{
public Position Position { get; }
public Size Size { get; }
public string Color { get; }
public CanvasDrawRectangleOperation(Position position, Size size, string color)
public CanvasDrawRectangleOperationBase(Position position, Size size, string color)
{
Position = position;
Size = size;
@@ -2,13 +2,13 @@
namespace QuestPDF.UnitTests.TestEngine.Operations
{
internal class CanvasDrawTextOperation : OperationBase
internal class CanvasDrawTextOperationBase : OperationBase
{
public string Text { get; }
public Position Position { get; }
public TextStyle Style { get; }
public CanvasDrawTextOperation(string text, Position position, TextStyle style)
public CanvasDrawTextOperationBase(string text, Position position, TextStyle style)
{
Text = text;
Position = position;
@@ -1,12 +0,0 @@
namespace QuestPDF.UnitTests.TestEngine.Operations
{
public class CanvasRotateOperation : OperationBase
{
public float Angle { get; }
public CanvasRotateOperation(float angle)
{
Angle = angle;
}
}
}
@@ -1,14 +0,0 @@
namespace QuestPDF.UnitTests.TestEngine.Operations
{
public class CanvasScaleOperation : OperationBase
{
public float ScaleX { get; }
public float ScaleY { get; }
public CanvasScaleOperation(float scaleX, float scaleY)
{
ScaleX = scaleX;
ScaleY = scaleY;
}
}
}
@@ -2,11 +2,11 @@
namespace QuestPDF.UnitTests.TestEngine.Operations
{
internal class CanvasTranslateOperation : OperationBase
internal class CanvasTranslateOperationBase : OperationBase
{
public Position Position { get; }
public CanvasTranslateOperation(Position position)
public CanvasTranslateOperationBase(Position position)
{
Position = position;
}
@@ -2,12 +2,12 @@
namespace QuestPDF.UnitTests.TestEngine.Operations
{
public class ChildDrawOperation : OperationBase
public class ChildDrawOperationBase : OperationBase
{
public string ChildId { get; }
public Size Input { get; }
public ChildDrawOperation(string childId, Size input)
public ChildDrawOperationBase(string childId, Size input)
{
ChildId = childId;
Input = input;
@@ -3,13 +3,13 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.UnitTests.TestEngine.Operations
{
internal class ChildMeasureOperation : OperationBase
internal class ChildMeasureOperationBase : OperationBase
{
public string ChildId { get; }
public Size Input { get; }
public ISpacePlan Output { get; }
public ChildMeasureOperation(string childId, Size input, ISpacePlan output)
public ChildMeasureOperationBase(string childId, Size input, ISpacePlan output)
{
ChildId = childId;
Input = input;
@@ -2,9 +2,9 @@
namespace QuestPDF.UnitTests.TestEngine.Operations
{
public class ElementMeasureOperation : OperationBase
public class ElementMeasureOperationBase : OperationBase
{
public ElementMeasureOperation(Size input)
public ElementMeasureOperationBase(Size input)
{
}
@@ -1,68 +0,0 @@
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
namespace QuestPDF.UnitTests.TestEngine
{
internal static class SimpleContainerTests
{
#region measure
public static void Measure<TElement>() where TElement : Element, IContainer, new()
{
Measure_Wrap<TElement>();
Measure_PartialRender<TElement>();
Measure_FullRender<TElement>();
}
private static void Measure_Wrap<TElement>() where TElement : Element, IContainer, new()
{
TestPlan
.For(x => new TElement
{
Child = x.CreateChild()
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure(new Size(400, 300), new Wrap())
.CheckMeasureResult(new Wrap());
}
private static void Measure_PartialRender<TElement>() where TElement : Element, IContainer, new()
{
TestPlan
.For(x => new TElement
{
Child = x.CreateChild()
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure(new Size(400, 300), new PartialRender(200, 100))
.CheckMeasureResult(new PartialRender(200, 100));
}
private static void Measure_FullRender<TElement>() where TElement : Element, IContainer, new()
{
TestPlan
.For(x => new TElement
{
Child = x.CreateChild()
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure(new Size(400, 300), new FullRender(250, 150))
.CheckMeasureResult(new FullRender(250, 150));
}
#endregion
public static void Draw<TElement>() where TElement : Element, IContainer, new()
{
TestPlan
.For(x => new TElement
{
Child = x.CreateChild()
})
.DrawElement(new Size(1200, 900))
.ExpectChildDraw(new Size(1200, 900))
.CheckDrawResult();
}
}
}
@@ -0,0 +1,31 @@
using FluentAssertions;
using NUnit.Framework;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
namespace QuestPDF.UnitTests.TestEngine
{
public static class SingleChildTests
{
internal static void MeasureWithoutChild<T>(this T element) where T : ContainerElement
{
element.Child = null;
element.Measure(Size.Zero).Should().BeEquivalentTo(new FullRender(Size.Zero));
}
internal static void DrawWithoutChild<T>(this T element) where T : ContainerElement
{
// component does not throw an exception when called with null child
Assert.DoesNotThrow(() =>
{
element.Child = null;
// component does not perform any canvas operation when called with null child
TestPlan
.For(x => element)
.DrawElement(new Size(200, 100))
.CheckDrawResult();
});
}
}
}
+35 -84
View File
@@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using FluentAssertions;
using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements;
using QuestPDF.Helpers;
@@ -15,8 +13,6 @@ namespace QuestPDF.UnitTests.TestEngine
internal class TestPlan
{
private const string DefaultChildName = "child";
private static Random Random { get; } = new Random();
private Element Element { get; set; }
private ICanvas Canvas { get; }
@@ -49,31 +45,20 @@ namespace QuestPDF.UnitTests.TestEngine
private ICanvas CreateCanvas()
{
return new MockCanvas
return new CanvasMock
{
TranslateFunc = position =>
{
var expected = GetExpected<CanvasTranslateOperation>();
var expected = GetExpected<CanvasTranslateOperationBase>();
Assert.AreEqual(expected.Position.X, position.X, "Translate X");
Assert.AreEqual(expected.Position.Y, position.Y, "Translate Y");
},
RotateFunc = angle =>
{
var expected = GetExpected<CanvasRotateOperation>();
Assert.AreEqual(expected.Angle, angle, "Rotate angle");
},
ScaleFunc = (scaleX, scaleY) =>
{
var expected = GetExpected<CanvasScaleOperation>();
Assert.AreEqual(expected.ScaleX, scaleX, "Scale X");
Assert.AreEqual(expected.ScaleY, scaleY, "Scale Y");
//position.Should().BeEquivalentTo(expected.Position);
},
DrawRectFunc = (position, size, color) =>
{
var expected = GetExpected<CanvasDrawRectangleOperation>();
var expected = GetExpected<CanvasDrawRectangleOperationBase>();
Assert.AreEqual(expected.Position.X, position.X, "Draw rectangle: X");
Assert.AreEqual(expected.Position.Y, position.Y, "Draw rectangle: Y");
@@ -82,10 +67,14 @@ namespace QuestPDF.UnitTests.TestEngine
Assert.AreEqual(expected.Size.Height, size.Height, "Draw rectangle: height");
Assert.AreEqual(expected.Color, color, "Draw rectangle: color");
/*position.Should().BeEquivalentTo(expected.Position);
size.Should().BeEquivalentTo(expected.Size);
color.Should().Be(expected.Color);*/
},
DrawTextFunc = (text, position, style) =>
{
var expected = GetExpected<CanvasDrawTextOperation>();
var expected = GetExpected<CanvasDrawTextOperationBase>();
Assert.AreEqual(expected.Text, text);
@@ -95,16 +84,23 @@ namespace QuestPDF.UnitTests.TestEngine
Assert.AreEqual(expected.Style.Color, style.Color, "Draw text: color");
Assert.AreEqual(expected.Style.FontType, style.FontType, "Draw text: font");
Assert.AreEqual(expected.Style.Size, style.Size, "Draw text: size");
/*text.Should().Be(expected.Text);
position.Should().BeEquivalentTo(expected.Position);
style.Should().BeEquivalentTo(expected.Style);*/
},
DrawImageFunc = (image, position, size) =>
{
var expected = GetExpected<CanvasDrawImageOperation>();
var expected = GetExpected<CanvasDrawImageOperationBase>();
Assert.AreEqual(expected.Position.X, position.X, "Draw image: X");
Assert.AreEqual(expected.Position.Y, position.Y, "Draw image: Y");
Assert.AreEqual(expected.Size.Width, size.Width, "Draw image: width");
Assert.AreEqual(expected.Size.Height, size.Height, "Draw image: height");
/*position.Should().BeEquivalentTo(expected.Position);
size.Should().BeEquivalentTo(expected.Size);*/
}
};
}
@@ -118,23 +114,29 @@ namespace QuestPDF.UnitTests.TestEngine
Id = id,
MeasureFunc = availableSpace =>
{
var expected = GetExpected<ChildMeasureOperation>();
var expected = GetExpected<ChildMeasureOperationBase>();
Assert.AreEqual(expected.ChildId, id);
Assert.AreEqual(expected.Input.Width, availableSpace.Width, $"Measure: width of child '{expected.ChildId}'");
Assert.AreEqual(expected.Input.Height, availableSpace.Height, $"Measure: height of child '{expected.ChildId}'");
// id.Should().Be(expected.ChildId);
// availableSpace.Should().Be(expected.Input);
return expected.Output;
},
DrawFunc = availableSpace =>
{
var expected = GetExpected<ChildDrawOperation>();
var expected = GetExpected<ChildDrawOperationBase>();
Assert.AreEqual(expected.ChildId, id);
Assert.AreEqual(expected.Input.Width, availableSpace.Width, $"Draw: width of child '{expected.ChildId}'");
Assert.AreEqual(expected.Input.Height, availableSpace.Height, $"Draw: width of child '{expected.ChildId}'");
/*id.Should().Be(expected.ChildId);
availableSpace.Should().Be(expected.Input);*/
}
};
}
@@ -164,7 +166,7 @@ namespace QuestPDF.UnitTests.TestEngine
public TestPlan ExpectChildMeasure(string child, Size expectedInput, ISpacePlan returns)
{
return AddOperation(new ChildMeasureOperation(child, expectedInput, returns));
return AddOperation(new ChildMeasureOperationBase(child, expectedInput, returns));
}
public TestPlan ExpectChildDraw(Size expectedInput)
@@ -174,42 +176,32 @@ namespace QuestPDF.UnitTests.TestEngine
public TestPlan ExpectChildDraw(string child, Size expectedInput)
{
return AddOperation(new ChildDrawOperation(child, expectedInput));
return AddOperation(new ChildDrawOperationBase(child, expectedInput));
}
public TestPlan ExpectCanvasTranslate(Position position)
{
return AddOperation(new CanvasTranslateOperation(position));
return AddOperation(new CanvasTranslateOperationBase(position));
}
public TestPlan ExpectCanvasTranslate(float left, float top)
{
return AddOperation(new CanvasTranslateOperation(new Position(left, top)));
return AddOperation(new CanvasTranslateOperationBase(new Position(left, top)));
}
public TestPlan ExpectCanvasScale(float scaleX, float scaleY)
{
return AddOperation(new CanvasScaleOperation(scaleX, scaleY));
}
public TestPlan ExpectCanvasRotate(float angle)
{
return AddOperation(new CanvasRotateOperation(angle));
}
public TestPlan ExpectCanvasDrawRectangle(Position position, Size size, string color)
{
return AddOperation(new CanvasDrawRectangleOperation(position, size, color));
return AddOperation(new CanvasDrawRectangleOperationBase(position, size, color));
}
public TestPlan ExpectCanvasDrawText(string text, Position position, TextStyle style)
{
return AddOperation(new CanvasDrawTextOperation(text, position, style));
return AddOperation(new CanvasDrawTextOperationBase(text, position, style));
}
public TestPlan ExpectCanvasDrawImage(Position position, Size size)
{
return AddOperation(new CanvasDrawImageOperation(position, size));
return AddOperation(new CanvasDrawImageOperationBase(position, size));
}
public TestPlan CheckMeasureResult(ISpacePlan expected)
@@ -254,51 +246,10 @@ namespace QuestPDF.UnitTests.TestEngine
public static Element CreateUniqueElement()
{
return new Constrained
return new DynamicImage
{
MinWidth = 90,
MinHeight = 60,
Child = new DynamicImage
{
Source = Placeholders.Image
}
Source = Placeholders.Image
};
}
public static void CompareOperations(Element value, Element expected, Size? availableSpace = null)
{
CompareMeasureOperations(value, expected, availableSpace);
CompareDrawOperations(value, expected, availableSpace);
}
private static void CompareMeasureOperations(Element value, Element expected, Size? availableSpace = null)
{
availableSpace ??= new Size(400, 300);
var canvas = new FreeCanvas();
value.HandleVisitor(x => x.Initialize(null, canvas));
var valueMeasure = value.Measure(availableSpace);
expected.HandleVisitor(x => x.Initialize(null, canvas));
var expectedMeasure = expected.Measure(availableSpace);
valueMeasure.Should().BeEquivalentTo(expectedMeasure);
}
private static void CompareDrawOperations(Element value, Element expected, Size? availableSpace = null)
{
availableSpace ??= new Size(400, 300);
var valueCanvas = new OperationRecordingCanvas();
value.HandleVisitor(x => x.Initialize(null, valueCanvas));
value.Draw(availableSpace);
var expectedCanvas = new OperationRecordingCanvas();
expected.HandleVisitor(x => x.Initialize(null, expectedCanvas));
expected.Draw(availableSpace);
valueCanvas.Operations.Should().BeEquivalentTo(expectedCanvas.Operations);
}
}
}
-22
View File
@@ -1,22 +0,0 @@
using FluentAssertions;
using NUnit.Framework;
namespace QuestPDF.UnitTests
{
[SetUpFixture]
public class TestsBase
{
[OneTimeSetUp]
public void RunBeforeAnyTests()
{
AssertionOptions.AssertEquivalencyUsing(options => options
.IncludingNestedObjects()
.IncludingInternalProperties()
.IncludingInternalFields()
.AllowingInfiniteRecursion()
.RespectingRuntimeTypes()
.WithTracing()
.WithStrictOrdering());
}
}
}
+10
View File
@@ -0,0 +1,10 @@
using NUnit.Framework;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class TextTests
{
}
}
-32
View File
@@ -1,32 +0,0 @@
using NUnit.Framework;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class TranslateTests
{
[Test]
public void Measure() => SimpleContainerTests.Measure<Translate>();
[Test]
public void Draw()
{
TestPlan
.For(x => new Translate
{
Child = x.CreateChild(),
TranslateX = 50,
TranslateY = 75
})
.DrawElement(new Size(400, 300))
.ExpectCanvasTranslate(50, 75)
.ExpectChildDraw(new Size(400, 300))
.ExpectCanvasTranslate(-50, -75)
.CheckDrawResult();
}
}
}
-100
View File
@@ -1,100 +0,0 @@
using NUnit.Framework;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class UnconstrainedTests
{
#region measure
[Test]
public void Measure_Wrap()
{
TestPlan
.For(x => new Unconstrained
{
Child = x.CreateChild()
})
.MeasureElement(new Size(900, 800))
.ExpectChildMeasure(Size.Max, new Wrap())
.CheckMeasureResult(new Wrap());
}
[Test]
public void Measure_PartialRender()
{
TestPlan
.For(x => new Unconstrained
{
Child = x.CreateChild()
})
.MeasureElement(new Size(900, 800))
.ExpectChildMeasure(Size.Max, new PartialRender(1200, 1600))
.CheckMeasureResult(new PartialRender(Size.Zero));
}
[Test]
public void Measure_FullRender()
{
TestPlan
.For(x => new Unconstrained
{
Child = x.CreateChild()
})
.MeasureElement(new Size(900, 800))
.ExpectChildMeasure(Size.Max, new FullRender(1200, 1600))
.CheckMeasureResult(new FullRender(Size.Zero));
}
#endregion
#region draw
[Test]
public void Draw_SkipWhenChildWraps()
{
TestPlan
.For(x => new Unconstrained
{
Child = x.CreateChild()
})
.DrawElement(new Size(900, 800))
.ExpectChildMeasure(Size.Max, new Wrap())
.CheckDrawResult();
}
[Test]
public void Draw_WhenChildPartiallyRenders()
{
TestPlan
.For(x => new Unconstrained
{
Child = x.CreateChild()
})
.DrawElement(new Size(900, 800))
.ExpectChildMeasure(Size.Max, new PartialRender(1200, 1600))
.ExpectChildDraw(new Size(1200, 1600))
.CheckDrawResult();
}
[Test]
public void Draw_WhenChildFullyRenders()
{
TestPlan
.For(x => new Unconstrained
{
Child = x.CreateChild()
})
.DrawElement(new Size(900, 800))
.ExpectChildMeasure(Size.Max, new FullRender(1600, 1000))
.ExpectChildDraw(new Size(1600, 1000))
.CheckDrawResult();
}
#endregion
}
}
+49
View File
@@ -0,0 +1,49 @@
using System.Collections.Concurrent;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Drawing
{
internal static class CanvasCache
{
internal static ConcurrentDictionary<string, SKPaint> Paints = new ConcurrentDictionary<string, SKPaint>();
private static ConcurrentDictionary<string, SKFontMetrics> FontMetrics = new ConcurrentDictionary<string, SKFontMetrics>();
private static ConcurrentDictionary<string, SKPaint> ColorPaint = new ConcurrentDictionary<string, SKPaint>();
internal static SKPaint ColorToPaint(this string color)
{
return ColorPaint.GetOrAdd(color, Convert);
static SKPaint Convert(string color)
{
return new SKPaint
{
Color = SKColor.Parse(color)
};
}
}
internal static SKPaint ToPaint(this TextStyle style)
{
return Paints.GetOrAdd(style.ToString(), key => Convert(style));
static SKPaint Convert(TextStyle style)
{
var slant = style.IsItalic ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
return new SKPaint
{
Color = SKColor.Parse(style.Color),
Typeface = SKTypeface.FromFamilyName(style.FontType, (int)style.FontWeight, (int)SKFontStyleWidth.Normal, slant),
TextSize = style.Size,
TextEncoding = SKTextEncoding.Utf32
};
}
}
internal static SKFontMetrics ToFontMetrics(this TextStyle style)
{
return FontMetrics.GetOrAdd(style.ToString(), key => style.ToPaint().FontMetrics);
}
}
}
-1
View File
@@ -9,7 +9,6 @@ namespace QuestPDF.Drawing
{
internal class DocumentContainer : IDocumentContainer
{
internal TextStyle DefaultTextStyle { get; set; } = TextStyle.Default;
internal List<IComponent> Pages { get; set; } = new List<IComponent>();
internal Container Compose()
+1 -35
View File
@@ -1,12 +1,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements;
using QuestPDF.Elements.Text;
using QuestPDF.Elements.Text.Items;
using QuestPDF.Infrastructure;
namespace QuestPDF.Drawing
@@ -39,7 +36,6 @@ namespace QuestPDF.Drawing
var metadata = document.GetMetadata();
var pageContext = new PageContext();
ApplyDefaultTextStyle(content, container.DefaultTextStyle);
RenderPass(pageContext, new FreeCanvas(), content, metadata);
RenderPass(pageContext, canvas, content, metadata);
}
@@ -60,10 +56,7 @@ namespace QuestPDF.Drawing
var spacePlan = content.Measure(Size.Max) as Size;
if (spacePlan == null)
{
canvas.EndDocument();
ThrowLayoutException();
}
break;
try
{
@@ -91,33 +84,6 @@ namespace QuestPDF.Drawing
}
canvas.EndDocument();
void ThrowLayoutException()
{
throw new DocumentLayoutException("Composed layout generates infinite document.");
}
}
private static void ApplyDefaultTextStyle(Element content, TextStyle documentDefaultTextStyle)
{
documentDefaultTextStyle.ApplyGlobalStyle(TextStyle.LibraryDefault);
content.HandleVisitor(element =>
{
var text = element as TextBlock;
if (text == null)
return;
foreach (var child in text.Children)
{
if (child is TextBlockSpan textSpan)
textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle);
if (child is TextBlockElement textElement)
ApplyDefaultTextStyle(textElement.Element, documentDefaultTextStyle);
}
});
}
}
}
-67
View File
@@ -1,67 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Drawing
{
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>();
public static void RegisterFontType(string fontName, Stream stream)
{
Typefaces.TryAdd(fontName, SKTypeface.FromStream(stream));
}
internal static SKPaint ColorToPaint(this string color)
{
return ColorPaint.GetOrAdd(color, Convert);
static SKPaint Convert(string color)
{
return new SKPaint
{
Color = SKColor.Parse(color)
};
}
}
internal static SKPaint ToPaint(this TextStyle style)
{
return Paints.GetOrAdd(style.Key, key => Convert(style));
static SKPaint Convert(TextStyle style)
{
return new SKPaint
{
Color = SKColor.Parse(style.Color),
Typeface = GetTypeface(style),
TextSize = (style.Size ?? 12),
TextEncoding = SKTextEncoding.Utf32
};
}
static SKTypeface GetTypeface(TextStyle style)
{
if (Typefaces.TryGetValue(style.FontType, out var result))
return result;
var slant = (style.IsItalic ?? false) ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
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.");
}
}
internal static SKFontMetrics ToFontMetrics(this TextStyle style)
{
return FontMetrics.GetOrAdd(style.Key, key => style.ToPaint().FontMetrics);
}
}
}
-10
View File
@@ -66,16 +66,6 @@ namespace QuestPDF.Drawing
}
public void Rotate(float angle)
{
}
public void Scale(float scaleX, float scaleY)
{
}
#endregion
}
}
-10
View File
@@ -51,15 +51,5 @@ namespace QuestPDF.Drawing
{
Canvas.DrawNamedDestinationAnnotation(new SKPoint(0, 0), locationName);
}
public void Rotate(float angle)
{
Canvas.RotateDegrees(angle);
}
public void Scale(float scaleX, float scaleY)
{
Canvas.Scale(scaleX, scaleY);
}
}
}
+1 -1
View File
@@ -16,7 +16,7 @@ namespace QuestPDF.Elements
{
public const int DefaultColumnsCount = 12;
public List<GridElement> Children { get; } = new List<GridElement>();
public List<GridElement> Children { get; set; } = new List<GridElement>();
public Queue<GridElement> ChildrenQueue { get; set; } = new Queue<GridElement>();
public int ColumnsCount { get; set; } = DefaultColumnsCount;
+1 -13
View File
@@ -1,4 +1,3 @@
using System;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
@@ -37,20 +36,9 @@ namespace QuestPDF.Elements
.Decoration(decoration =>
{
decoration.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)
.Element(Content);
decoration.Content().Extend().Element(Content);
decoration.Footer().Element(Footer);
});
bool IsClose(float x, float y)
{
return Math.Abs(x - y) < Size.Epsilon;
}
}
}
}
+9 -2
View File
@@ -8,9 +8,16 @@ namespace QuestPDF.Elements
internal override void Draw(Size availableSpace)
{
Canvas.Rotate(Angle);
var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
if (skiaCanvas == null)
return;
var currentMatrix = skiaCanvas.TotalMatrix;
skiaCanvas.RotateDegrees(Angle);
Child?.Draw(availableSpace);
Canvas.Rotate(-Angle);
skiaCanvas.SetMatrix(currentMatrix);
}
}
}
+44 -37
View File
@@ -6,31 +6,29 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal abstract class RowElement : Constrained
internal abstract class RowElement : ContainerElement
{
public float Size { get; protected set; }
public float Width { get; set; } = 1;
public void SetWidth(float width)
internal override void Draw(Size availableSpace)
{
MinWidth = width;
MaxWidth = width;
Child?.Draw(availableSpace);
}
}
internal class ConstantRowElement : RowElement
{
public ConstantRowElement(float size)
public ConstantRowElement(float width)
{
Size = size;
SetWidth(size);
Width = width;
}
}
internal class RelativeRowElement : RowElement
{
public RelativeRowElement(float size)
public RelativeRowElement(float width)
{
Size = size;
Width = width;
}
}
@@ -85,61 +83,70 @@ namespace QuestPDF.Elements
internal class Row : Element
{
public float Spacing { get; set; } = 0;
public ICollection<RowElement> Children { get; internal set; } = new List<RowElement>();
private Element? RootElement { get; set; }
public float Spacing { get; set; } = 0;
internal override void HandleVisitor(Action<Element?> visit)
{
if (RootElement == null)
ComposeTree();
RootElement.HandleVisitor(visit);
Children.ToList().ForEach(x => x.HandleVisitor(visit));
base.HandleVisitor(visit);
}
internal override ISpacePlan Measure(Size availableSpace)
{
UpdateElementsWidth(availableSpace.Width);
return RootElement.Measure(availableSpace);
return Compose(availableSpace.Width).Measure(availableSpace);
}
internal override void Draw(Size availableSpace)
{
UpdateElementsWidth(availableSpace.Width);
RootElement.Draw(availableSpace);
Compose(availableSpace.Width).Draw(availableSpace);
}
#region structure
private void ComposeTree()
private Element Compose(float availableWidth)
{
Children = AddSpacing(Children, Spacing);
var elements = AddSpacing(Children, Spacing);
var rowElements = ReduceRows(elements, availableWidth);
var tree = BuildTree(rowElements.ToArray());
tree.HandleVisitor(x => x.Initialize(PageContext, Canvas));
var elements = Children.Cast<Element>().ToArray();
RootElement = BuildTree(elements);
return tree;
}
private void UpdateElementsWidth(float availableWidth)
private static ICollection<Element> ReduceRows(ICollection<RowElement> elements, float availableWidth)
{
var constantWidth = Children
var constantWidth = elements
.Where(x => x is ConstantRowElement)
.Cast<ConstantRowElement>()
.Sum(x => x.Size);
.Sum(x => x.Width);
var relativeWidth = Children
var relativeWidth = elements
.Where(x => x is RelativeRowElement)
.Cast<RelativeRowElement>()
.Sum(x => x.Size);
.Sum(x => x.Width);
var widthPerRelativeUnit = (availableWidth - constantWidth) / relativeWidth;
Children
.Where(x => x is RelativeRowElement)
.Cast<RelativeRowElement>()
.ToList()
.ForEach(x => x.SetWidth(x.Size * widthPerRelativeUnit));
return elements
.Select(x =>
{
if (x is RelativeRowElement r)
return new ConstantRowElement(r.Width * widthPerRelativeUnit)
{
Child = x.Child
};
return x;
})
.Select(x => new Constrained
{
MinWidth = x.Width,
MaxWidth = x.Width,
Child = x.Child
})
.Cast<Element>()
.ToList();
}
private static ICollection<RowElement> AddSpacing(ICollection<RowElement> elements, float spacing)
+13 -8
View File
@@ -35,21 +35,26 @@ namespace QuestPDF.Elements
internal override void Draw(Size availableSpace)
{
var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
if (skiaCanvas == null)
return;
var targetSpace = new Size(
Math.Abs(availableSpace.Width / ScaleX),
Math.Abs(availableSpace.Height / ScaleY));
var translate = new Position(
ScaleX < 0 ? availableSpace.Width : 0,
ScaleY < 0 ? availableSpace.Height : 0);
var currentMatrix = skiaCanvas.TotalMatrix;
Canvas.Translate(translate);
Canvas.Scale(ScaleX, ScaleY);
if (ScaleX < 0)
skiaCanvas.Translate(availableSpace.Width, 0);
if (ScaleY < 0)
skiaCanvas.Translate(0, availableSpace.Height);
skiaCanvas.Scale(ScaleX, ScaleY);
Child?.Draw(targetSpace);
Canvas.Scale(1/ScaleX, 1/ScaleY);
Canvas.Translate(translate.Reverse());
skiaCanvas.SetMatrix(currentMatrix);
}
}
}
+14 -10
View File
@@ -33,22 +33,26 @@ namespace QuestPDF.Elements
internal override void Draw(Size availableSpace)
{
var translate = new Position(
(NormalizedTurnCount == 1 || NormalizedTurnCount == 2) ? availableSpace.Width : 0,
(NormalizedTurnCount == 2 || NormalizedTurnCount == 3) ? availableSpace.Height : 0);
var rotate = NormalizedTurnCount * 90;
var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
Canvas.Translate(translate);
Canvas.Rotate(rotate);
if (skiaCanvas == null)
return;
var currentMatrix = skiaCanvas.TotalMatrix;
if (NormalizedTurnCount == 1 || NormalizedTurnCount == 2)
skiaCanvas.Translate(availableSpace.Width, 0);
if (NormalizedTurnCount == 2 || NormalizedTurnCount == 3)
skiaCanvas.Translate(0, availableSpace.Height);
skiaCanvas.RotateDegrees(NormalizedTurnCount * 90);
if (NormalizedTurnCount == 1 || NormalizedTurnCount == 3)
availableSpace = new Size(availableSpace.Height, availableSpace.Width);
Child?.Draw(availableSpace);
Canvas.Rotate(-rotate);
Canvas.Translate(translate.Reverse());
skiaCanvas.SetMatrix(currentMatrix);
}
}
}
+1 -1
View File
@@ -91,7 +91,7 @@ namespace QuestPDF.Elements
internal class Stack : IComponent
{
public ICollection<Element> Children { get; } = new List<Element>();
public ICollection<Element> Children { get; internal set; } = new List<Element>();
public float Spacing { get; set; } = 0;
public void Compose(IContainer container)
+7 -35
View File
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Elements.Text.Items;
@@ -7,41 +6,14 @@ namespace QuestPDF.Elements.Text.Calculation
{
internal class TextLine
{
public ICollection<TextLineElement> Elements { get; private set; }
public ICollection<TextLineElement> Elements { get; set; }
public float TextHeight { get; private set; }
public float LineHeight { get; private set; }
public float TextHeight => Elements.Max(x => x.Measurement.Height);
public float LineHeight => Elements.Max(x => x.Measurement.LineHeight * x.Measurement.Height);
public float Ascent { get; private set; }
public float Descent { get; private set; }
public float Ascent => Elements.Min(x => x.Measurement.Ascent) - (LineHeight - TextHeight) / 2;
public float Descent => Elements.Max(x => x.Measurement.Descent) + (LineHeight - TextHeight) / 2;
public float Width { get; private set; }
public static TextLine From(ICollection<TextLineElement> elements)
{
if (elements.Count == 0)
{
return new TextLine
{
Elements = elements
};
}
var textHeight = elements.Max(x => x.Measurement.Height);
var lineHeight = elements.Max(x => x.Measurement.LineHeight * x.Measurement.Height);
return new TextLine
{
Elements = elements,
TextHeight = textHeight,
LineHeight = lineHeight,
Ascent = elements.Min(x => x.Measurement.Ascent) - (lineHeight - textHeight) / 2,
Descent = elements.Max(x => x.Measurement.Descent) + (lineHeight - textHeight) / 2,
Width = elements.Sum(x => x.Measurement.Width)
};
}
public float Width => Elements.Sum(x => x.Measurement.Width);
}
}
@@ -1,6 +1,6 @@
using QuestPDF.Elements.Text.Items;
using QuestPDF.Elements.Text.Calculation;
namespace QuestPDF.Elements.Text.Calculation
namespace QuestPDF.Elements.Text.Items
{
internal class TextLineElement
{
@@ -1,5 +1,4 @@
using QuestPDF.Elements.Text.Calculation;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Items
{
@@ -3,22 +3,33 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Items
{
internal class TextBlockExternalLink : TextBlockSpan
internal class TextBlockExternalLink : ITextBlockItem
{
public TextStyle Style { get; set; } = new TextStyle();
public string Text { get; set; }
public string Url { get; set; }
public override TextMeasurementResult? Measure(TextMeasurementRequest request)
public TextMeasurementResult? Measure(TextMeasurementRequest request)
{
return MeasureWithoutCache(request);
return GetItem().MeasureWithoutCache(request);
}
public override void Draw(TextDrawingRequest request)
public void Draw(TextDrawingRequest request)
{
request.Canvas.Translate(new Position(0, request.TotalAscent));
request.Canvas.DrawExternalLink(Url, new Size(request.TextSize.Width, request.TextSize.Height));
request.Canvas.Translate(new Position(0, -request.TotalAscent));
base.Draw(request);
GetItem().Draw(request);
}
private TextBlockSpan GetItem()
{
return new TextBlockSpan
{
Style = Style,
Text = Text
};
}
}
}
@@ -3,22 +3,33 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Items
{
internal class TextBlockInternalLink : TextBlockSpan
internal class TextBlockInternalLink : ITextBlockItem
{
public TextStyle Style { get; set; } = new TextStyle();
public string Text { get; set; }
public string LocationName { get; set; }
public override TextMeasurementResult? Measure(TextMeasurementRequest request)
public TextMeasurementResult? Measure(TextMeasurementRequest request)
{
return MeasureWithoutCache(request);
return GetItem().MeasureWithoutCache(request);
}
public override void Draw(TextDrawingRequest request)
public void Draw(TextDrawingRequest request)
{
request.Canvas.Translate(new Position(0, request.TotalAscent));
request.Canvas.DrawLocationLink(LocationName, new Size(request.TextSize.Width, request.TextSize.Height));
request.Canvas.Translate(new Position(0, -request.TotalAscent));
base.Draw(request);
GetItem().Draw(request);
}
private TextBlockSpan GetItem()
{
return new TextBlockSpan
{
Style = Style,
Text = Text
};
}
}
}
@@ -3,31 +3,34 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Items
{
internal class TextBlockPageNumber : TextBlockSpan
internal class TextBlockPageNumber : ITextBlockItem
{
public TextStyle Style { get; set; } = new TextStyle();
public string SlotName { get; set; }
public override TextMeasurementResult? Measure(TextMeasurementRequest request)
public TextMeasurementResult? Measure(TextMeasurementRequest request)
{
SetPageNumber(request.PageContext);
return MeasureWithoutCache(request);
return GetItem(request.PageContext).MeasureWithoutCache(request);
}
public override void Draw(TextDrawingRequest request)
public void Draw(TextDrawingRequest request)
{
SetPageNumber(request.PageContext);
base.Draw(request);
GetItem(request.PageContext).Draw(request);
}
private void SetPageNumber(IPageContext context)
private TextBlockSpan GetItem(IPageContext context)
{
var pageNumberPlaceholder = 123;
var pageNumber = context.GetRegisteredLocations().Contains(SlotName)
? context.GetLocationPage(SlotName)
: pageNumberPlaceholder;
Text = pageNumber.ToString();
return new TextBlockSpan
{
Style = Style,
Text = pageNumber.ToString()
};
}
}
}
+16 -19
View File
@@ -14,7 +14,7 @@ namespace QuestPDF.Elements.Text.Items
private Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?> MeasureCache =
new Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?>();
public virtual TextMeasurementResult? Measure(TextMeasurementRequest request)
public TextMeasurementResult? Measure(TextMeasurementRequest request)
{
var cacheKey = (request.StartIndex, request.AvailableWidth);
@@ -32,7 +32,7 @@ namespace QuestPDF.Elements.Text.Items
var fontMetrics = Style.ToFontMetrics();
var startIndex = request.StartIndex;
if (request.IsFirstLineElement)
{
while (startIndex + 1 < Text.Length && Text[startIndex] == space)
@@ -45,7 +45,7 @@ namespace QuestPDF.Elements.Text.Items
{
Width = 0,
LineHeight = Style.LineHeight ?? 1,
LineHeight = Style.LineHeight,
Ascent = fontMetrics.Ascent,
Descent = fontMetrics.Descent
};
@@ -54,18 +54,15 @@ namespace QuestPDF.Elements.Text.Items
// start breaking text from requested position
var text = Text.Substring(startIndex);
var textLength = (int)paint.BreakText(text, request.AvailableWidth);
var breakingIndex = (int)paint.BreakText(text, request.AvailableWidth);
if (textLength <= 0)
if (breakingIndex <= 0)
return null;
if (textLength < text.Length && text[textLength] == space)
textLength++;
// break text only on spaces
if (textLength < text.Length)
if (breakingIndex < text.Length)
{
var lastSpaceIndex = text.Substring(0, textLength).LastIndexOf(space) - 1;
var lastSpaceIndex = text.Substring(0, breakingIndex).LastIndexOf(space) - 1;
if (lastSpaceIndex <= 0)
{
@@ -74,13 +71,13 @@ namespace QuestPDF.Elements.Text.Items
}
else
{
textLength = lastSpaceIndex + 1;
breakingIndex = lastSpaceIndex + 1;
}
}
text = text.Substring(0, textLength);
text = text.Substring(0, breakingIndex);
var endIndex = startIndex + textLength;
var endIndex = startIndex + breakingIndex;
var nextIndex = endIndex;
while (nextIndex + 1 < Text.Length && Text[nextIndex] == space)
@@ -96,7 +93,7 @@ namespace QuestPDF.Elements.Text.Items
Ascent = fontMetrics.Ascent,
Descent = fontMetrics.Descent,
LineHeight = Style.LineHeight ?? 1,
LineHeight = Style.LineHeight,
StartIndex = startIndex,
EndIndex = endIndex,
@@ -105,7 +102,7 @@ namespace QuestPDF.Elements.Text.Items
};
}
public virtual void Draw(TextDrawingRequest request)
public void Draw(TextDrawingRequest request)
{
var fontMetrics = Style.ToFontMetrics();
@@ -115,12 +112,12 @@ namespace QuestPDF.Elements.Text.Items
request.Canvas.DrawText(text, Position.Zero, Style);
// draw underline
if ((Style.HasUnderline ?? false) && fontMetrics.UnderlinePosition.HasValue)
DrawLine(fontMetrics.UnderlinePosition.Value, fontMetrics.UnderlineThickness ?? 1);
if (Style.IsUnderlined && fontMetrics.UnderlinePosition.HasValue)
DrawLine(fontMetrics.UnderlinePosition.Value, fontMetrics.UnderlineThickness.Value);
// draw stroke
if ((Style.HasStrikethrough ?? false) && fontMetrics.StrikeoutPosition.HasValue)
DrawLine(fontMetrics.StrikeoutPosition.Value, fontMetrics.StrikeoutThickness ?? 1);
if (Style.IsStroked && fontMetrics.StrikeoutPosition.HasValue)
DrawLine(fontMetrics.StrikeoutPosition.Value, fontMetrics.StrikeoutThickness.Value);
void DrawLine(float offset, float thickness)
{
+7 -4
View File
@@ -27,7 +27,7 @@ namespace QuestPDF.Elements.Text
if (!RenderingQueue.Any())
return new FullRender(Size.Zero);
var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height).ToList();
var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height);
if (!lines.Any())
return new PartialRender(Size.Zero);
@@ -64,7 +64,7 @@ namespace QuestPDF.Elements.Text
widthOffset = 0f;
var alignmentOffset = GetAlignmentOffset(line.Width);
Canvas.Translate(new Position(alignmentOffset, 0));
Canvas.Translate(new Position(0, -line.Ascent));
@@ -110,7 +110,7 @@ namespace QuestPDF.Elements.Text
if (!RenderingQueue.Any())
ResetState();
float GetAlignmentOffset(float lineWidth)
{
if (Alignment == HorizontalAlignment.Left)
@@ -192,7 +192,10 @@ namespace QuestPDF.Elements.Text
queue.Dequeue();
}
return TextLine.From(currentLineElements);
return new TextLine
{
Elements = currentLineElements
};
}
}
}
+6 -3
View File
@@ -10,11 +10,14 @@ namespace QuestPDF.Elements
internal override void Draw(Size availableSpace)
{
var translate = new Position(TranslateX, TranslateY);
var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
Canvas.Translate(translate);
if (skiaCanvas == null)
return;
skiaCanvas.Translate(TranslateX, TranslateY);
base.Draw(availableSpace);
Canvas.Translate(translate.Reverse());
skiaCanvas.Translate(-TranslateX, -TranslateY);
}
}
}
+4 -4
View File
@@ -18,10 +18,10 @@ namespace QuestPDF.Fluent
{
if (element?.Child != null && element.Child is Empty == false)
{
var message = "You should not assign multiple child elements to a single-child container. " +
"This may happen when a container variable is used outside of its scope/closure OR the container is used in multiple fluent API chains OR the container is used incorrectly in a loop. " +
"This exception is thrown to help you detect that some part of the code is overriding fragments of the document layout with a new content - essentially destroying existing content.";
var message = $"Container {element.GetType().Name} already contains an {element.Child.GetType().Name} child. " +
$"Tried to assign {child?.GetType()?.Name}." +
$"You should not assign multiple elements to single-child containers.";
throw new DocumentComposeException(message);
}
+5 -21
View File
@@ -12,24 +12,14 @@ namespace QuestPDF.Fluent
public void Size(PageSize pageSize)
{
MinSize(pageSize);
MaxSize(pageSize);
}
public void ContinuousSize(float width)
{
MinSize(new PageSize(width, 0));
MaxSize(new PageSize(width, Infrastructure.Size.Max.Height));
Page.MinSize = pageSize;
Page.MaxSize = pageSize;
}
public void MinSize(PageSize pageSize)
public void ContinuousSize(float width)
{
Page.MinSize = pageSize;
}
public void MaxSize(PageSize pageSize)
{
Page.MaxSize = pageSize;
Page.MinSize = new PageSize(width, 0);
Page.MaxSize = new PageSize(width, Infrastructure.Size.Max.Height);
}
public void MarginLeft(float value)
@@ -94,12 +84,6 @@ namespace QuestPDF.Fluent
public static class PageExtensions
{
public static IDocumentContainer DefaultTextStyle(this IDocumentContainer document, TextStyle textStyle)
{
(document as DocumentContainer).DefaultTextStyle = textStyle;
return document;
}
public static IDocumentContainer Page(this IDocumentContainer document, Action<PageDescriptor> handler)
{
var descriptor = new PageDescriptor();
+12 -27
View File
@@ -1,12 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing;
using QuestPDF.Elements;
using QuestPDF.Elements.Text;
using QuestPDF.Elements.Text.Items;
using QuestPDF.Infrastructure;
using static System.String;
namespace QuestPDF.Fluent
{
@@ -14,7 +12,7 @@ namespace QuestPDF.Fluent
{
private ICollection<TextBlock> TextBlocks { get; } = new List<TextBlock>();
private TextStyle DefaultStyle { get; set; } = TextStyle.Default;
internal HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
private HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
private float Spacing { get; set; } = 0f;
public void DefaultTextStyle(TextStyle style)
@@ -52,11 +50,10 @@ namespace QuestPDF.Fluent
public void Span(string text, TextStyle? style = null)
{
style ??= TextStyle.Default;
style ??= DefaultStyle;
var items = text
.Replace("\r", string.Empty)
.Split(new[] { '\n' }, StringSplitOptions.None)
.Split(new[] { Environment.NewLine }, StringSplitOptions.None)
.Select(x => new TextBlockSpan
{
Text = x,
@@ -76,24 +73,14 @@ namespace QuestPDF.Fluent
.ForEach(TextBlocks.Add);
}
public void Line(string text, TextStyle? style = null)
{
Span(text + Environment.NewLine, style);
}
public void Line(string text)
{
Span(text + Environment.NewLine);
}
public void EmptyLine()
public void NewLine()
{
Span(Environment.NewLine);
}
private void PageNumber(string slotName, TextStyle? style = null)
{
style ??= TextStyle.Default;
style ??= DefaultStyle;
AddItemToLastTextBlock(new TextBlockPageNumber()
{
@@ -119,7 +106,7 @@ namespace QuestPDF.Fluent
public void InternalLocation(string text, string locationName, TextStyle? style = null)
{
style ??= TextStyle.Default;
style ??= DefaultStyle;
AddItemToLastTextBlock(new TextBlockInternalLink
{
@@ -131,7 +118,7 @@ namespace QuestPDF.Fluent
public void ExternalLocation(string text, string url, TextStyle? style = null)
{
style ??= TextStyle.Default;
style ??= DefaultStyle;
AddItemToLastTextBlock(new TextBlockExternalLink
{
@@ -150,16 +137,13 @@ namespace QuestPDF.Fluent
Element = container
});
return container.AlignBottom().Box();
return container.Box();
}
internal void Compose(IContainer container)
{
TextBlocks.ToList().ForEach(x => x.Alignment = Alignment);
foreach (var textBlockSpan in TextBlocks.SelectMany(x => x.Children).Where(x => x is TextBlockSpan).Cast<TextBlockSpan>())
textBlockSpan.Style.ApplyParentStyle(DefaultStyle);
container.Stack(stack =>
{
stack.Spacing(Spacing);
@@ -174,11 +158,12 @@ namespace QuestPDF.Fluent
{
public static void Text(this IContainer element, Action<TextDescriptor> content)
{
var descriptor = new TextDescriptor();
var textBlock = new TextBlock();
if (element is Alignment alignment)
descriptor.Alignment = alignment.Horizontal;
textBlock.Alignment = alignment.Horizontal;
var descriptor = new TextDescriptor();
content?.Invoke(descriptor);
descriptor.Compose(element);
}
+4 -4
View File
@@ -43,14 +43,14 @@ namespace QuestPDF.Fluent
return style.Mutate(x => x.IsItalic = value);
}
public static TextStyle Strikethrough(this TextStyle style, bool value = true)
public static TextStyle Stroked(this TextStyle style, bool value = true)
{
return style.Mutate(x => x.HasStrikethrough = value);
return style.Mutate(x => x.IsStroked = value);
}
public static TextStyle Underline(this TextStyle style, bool value = true)
public static TextStyle Underlined(this TextStyle style, bool value = true)
{
return style.Mutate(x => x.HasUnderline = value);
return style.Mutate(x => x.IsUnderlined = value);
}
#region Weight
-3
View File
@@ -13,8 +13,5 @@ namespace QuestPDF.Infrastructure
void DrawExternalLink(string url, Size size);
void DrawLocationLink(string locationName, Size size);
void DrawLocation(string locationName);
void Rotate(float angle);
void Scale(float scaleX, float scaleY);
}
}
-8
View File
@@ -30,13 +30,5 @@
if (obj.GetType() != this.GetType()) return false;
return Equals((Size) obj);
}
public override int GetHashCode()
{
unchecked
{
return (Width.GetHashCode() * 397) ^ Height.GetHashCode();
}
}
}
}
+18 -59
View File
@@ -1,70 +1,29 @@
using System;
using QuestPDF.Helpers;
using QuestPDF.Helpers;
namespace QuestPDF.Infrastructure
{
public class TextStyle
{
internal bool HasGlobalStyleApplied { get; private set; }
internal string Color { get; set; } = Colors.Black;
internal string BackgroundColor { get; set; } = Colors.Transparent;
internal string FontType { get; set; } = "Calibri";
internal float Size { get; set; } = 12;
internal float LineHeight { get; set; } = 1.2f;
internal FontWeight FontWeight { get; set; } = FontWeight.Normal;
internal bool IsItalic { get; set; } = false;
internal bool IsStroked { get; set; } = false;
internal bool IsUnderlined { get; set; } = false;
public static TextStyle Default => new TextStyle();
private string? KeyCache { get; set; }
internal string? Color { get; set; }
internal string? BackgroundColor { get; set; }
internal string? FontType { get; set; }
internal float? Size { get; set; }
internal float? LineHeight { get; set; }
internal FontWeight? FontWeight { get; set; }
internal bool? IsItalic { get; set; }
internal bool? HasStrikethrough { get; set; }
internal bool? HasUnderline { get; set; }
internal string? Key { get; private set; }
internal static TextStyle LibraryDefault => new TextStyle
public override string ToString()
{
Color = Colors.Black,
BackgroundColor = Colors.Transparent,
FontType = Fonts.Calibri,
Size = 12,
LineHeight = 1.2f,
FontWeight = Infrastructure.FontWeight.Normal,
IsItalic = false,
HasStrikethrough = false,
HasUnderline = false
};
private static TextStyle DefaultTextStyleCache = new TextStyle();
public static TextStyle Default => DefaultTextStyleCache;
internal void ApplyGlobalStyle(TextStyle globalStyle)
{
if (HasGlobalStyleApplied)
return;
HasGlobalStyleApplied = true;
ApplyParentStyle(globalStyle);
Key ??= $"{Color}|{BackgroundColor}|{FontType}|{Size}|{LineHeight}|{FontWeight}|{IsItalic}|{HasStrikethrough}|{HasUnderline}";
}
internal void ApplyParentStyle(TextStyle parentStyle)
{
Color ??= parentStyle.Color;
BackgroundColor ??= parentStyle.BackgroundColor;
FontType ??= parentStyle.FontType;
Size ??= parentStyle.Size;
LineHeight ??= parentStyle.LineHeight;
FontWeight ??= parentStyle.FontWeight;
IsItalic ??= parentStyle.IsItalic;
HasStrikethrough ??= parentStyle.HasStrikethrough;
HasUnderline ??= parentStyle.HasUnderline;
KeyCache ??= $"{Color}|{BackgroundColor}|{FontType}|{Size}|{LineHeight}|{FontWeight}|{IsItalic}|{IsStroked}|{IsUnderlined}";
return KeyCache;
}
internal TextStyle Clone()
{
var clone = (TextStyle)MemberwiseClone();
clone.HasGlobalStyleApplied = false;
return clone;
}
internal TextStyle Clone() => (TextStyle)MemberwiseClone();
}
}
+5 -5
View File
@@ -4,9 +4,9 @@
<Authors>MarcinZiabek</Authors>
<Company>CodeFlint</Company>
<PackageId>QuestPDF</PackageId>
<Version>2021.10.1</Version>
<Version>2021.8</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>Enhanced text rendering capabilities. Improved rendering performance.</PackageReleaseNotes>
<PackageReleaseNotes>New elements: Unconstrained, Scale, ScaleHorizontal, ScaleVertical, FlipOver, FlipHorizontal, FlipVertical, Rotate, RotateLeft, RotateRight, TranslateX, TranslateY. Added support for more placeholders in the PageNumber element (current page, total number of pages, page number of any predefined location). Added support for documents with various page sizes. Various API improvements.</PackageReleaseNotes>
<LangVersion>8</LangVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageIcon>Logo.png</PackageIcon>
@@ -14,15 +14,15 @@
<PackageProjectUrl>https://www.questpdf.com/</PackageProjectUrl>
<RepositoryUrl>https://github.com/QuestPDF/library.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Copyright>Marcin Ziąbek, QuestPDF contributors</Copyright>
<PackageTags>pdf file export generate generation tool create creation render portable document format quest html library converter open source free standard core</PackageTags>
<Copyright>QuestPDF contributors</Copyright>
<PackageTags>PDF file export generate create render portable document format quest free</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Nullable>enable</Nullable>
<TargetFrameworks>net462;netstandard2.0;netcoreapp2.0;netcoreapp3.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SkiaSharp" Version="2.80.3" />
<PackageReference Include="SkiaSharp" Version="2.80.2" />
</ItemGroup>
<ItemGroup>
+22 -167
View File
@@ -4,17 +4,17 @@
## Overview
**Rely on solid fundamentals** - This library is created specifically for designing and arranging document layouts, with full paging support. Alternative solutions, such as HTML-based converters, are not designed for this purpose and therefore are often unpredictable and do not produce desired results.
1) **Rely on solid fundamentals** - This library is created specifically for designing and arranging document layouts, with full paging support. Alternative solutions, such as HTML-based converters, are not designed for this purpose and therefore are often unpredictable and do not produce desired results.
**Work with organized self-explanatory code** - The entire process of implementing PDF document, takes place in your code. Free yourself from slow visual designers and strange technological limitations. Follow simple yet highly effective approaches to create maintainable, high-quality code.
2) **Work with organized self-explanatory code** - The entire process of implementing PDF document, takes place in your code. Free yourself from slow visual designers and strange technological limitations. Follow simple yet highly effective approaches to create maintainable, high-quality code.
**Compose simple components into complex documents** - Do you remember the feeling when your code just works? When your ideas are becoming real without any effort? Working with simple, easy to understand, self-explanatory and highly composable layout elements is the key here!
3) **Compose simple components into complex documents** - Do you remember the feeling when your code just works? When your ideas are becoming real without any effort? Working with simple, easy to understand, self-explanatory and highly composable layout elements is the key here!
**Create and reuse components** - Feel no fear of complex documents! Create custom, reusable components and divide the document's layout into easy to maintain pieces. Inject data to customize content and use slots to enhance composability. Decide how complex approaches your solution needs and follow the best path.
4) **Create and reuse components** - Feel no fear of complex documents! Create custom, reusable components and divide the document's layout into easy to maintain pieces. Inject data to customize content and use slots to enhance composability. Decide how complex approaches your solution needs and follow the best path.
**Prototype with ease** - We understand that document generation is often tricky and require multiple iterations. The library offers additional prototyping tools such as random text generator or image placeholder element. By following best practices, you can develop a document without having data.
5) **Prototype with ease** - We understand that document generation is often tricky and require multiple iterations. The library offers additional prototyping tools such as random text generator or image placeholder element. By following best practices, you can develop a document without having data.
**Enjoy fast PDF generation** - QuestPDF is created upon SkiaSharp, a well-known graphical library, and converts your data into PDF documents. It offers a highly optimized layouting engine capable of generating over 1000 PDF files per minute per core. The entire process is thread-safe.
6) **Enjoy fast PDF generation** - QuestPDF is created upon SkiaSharp, a well-known graphical library, and converts your data into PDF documents. It offers a highly optimized layouting engine capable of generating over 1000 PDF files per minute per core. The entire process is thread-safe.
## Support QuestPDF
@@ -36,10 +36,10 @@ The library is available as a nuget package. You can install it as any other nug
## 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.
**[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.
1. [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.
2. [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.
3. [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.
4. [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.
## Example invoice
@@ -51,184 +51,39 @@ For tutorial, documentation and API reference, please visit [the QuestPDF docume
<img src="https://github.com/QuestPDF/example-invoice/raw/main/images/invoice.png" width="595px">
</a>
Here you can find an example code showing how easy is to write and understand the fluent API.
**General document structure** with header, content and footer:
Here you can find an example code showing how easy is to write and understand the fluent API:
```csharp
public void Compose(IDocumentContainer container)
{
{
container
.Page(page =>
{
page.Margin(50);
page.MarginVertical(60);
page.MarginHorizontal(40);
page.Size(PageSizes.A4);
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().Text(x =>
{
x.CurrentPageNumber();
x.Span(" / ");
x.TotalPages();
});
page.Footer().AlignCenter().PageNumber();
});
}
```
**The header area** consists of basic invoice information along with a logo placeholder.
```csharp
void ComposeHeader(IContainer container)
{
var titleTextStyle = TextStyle.Default.Size(20).SemiBold().Color(Colors.Blue.Medium);
container.Row(row =>
{
row.RelativeColumn().Stack(stack =>
{
stack.Item().Text($"Invoice #{Model.InvoiceNumber}", titleStyle);
stack.Item().Text(text =>
{
text.Span("Issue date: ", TextStyle.Default.SemiBold());
text.Span($"{Model.IssueDate:d}");
});
stack.Item().Text(text =>
{
text.Span("Due date: ", TextStyle.Default.SemiBold());
text.Span($"{Model.DueDate:d}");
});
stack.Item().Text($"Invoice #{Model.InvoiceNumber}", TextStyle.Default.Size(20).Bold());
stack.Item().Text($"Issue date: {Model.IssueDate:d}");
stack.Item().Text($"Due date: {Model.DueDate:d}");
});
row.ConstantColumn(100).Height(50).Placeholder();
});
}
```
Implementation of **the content area** that contains seller and customer details, then listing of all bought products, then a comments section.
```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.Decoration(decoration =>
{
// header
decoration.Header().BorderBottom(1).Padding(5).Row(row =>
{
row.ConstantColumn(25).Text("#", headerStyle);
row.RelativeColumn(3).Text("Product", headerStyle);
row.RelativeColumn().AlignRight().Text("Unit price", headerStyle);
row.RelativeColumn().AlignRight().Text("Quantity", headerStyle);
row.RelativeColumn().AlignRight().Text("Total", headerStyle);
});
// content
decoration
.Content()
.Stack(column =>
{
foreach (var item in Model.Items)
{
column
.Item()
.ShowEntire()
.BorderBottom(1)
.BorderColor(Colors.Grey.Lighten2)
.Padding(5)
.Row(row =>
{
row.ConstantColumn(25).Text(Model.Items.IndexOf(item) + 1);
row.RelativeColumn(3).Text(item.Name);
row.RelativeColumn().AlignRight().Text($"{item.Price}$");
row.RelativeColumn().AlignRight().Text(item.Quantity);
row.RelativeColumn().AlignRight().Text($"{item.Price * item.Quantity}$");
});
}
});
});
}
```
```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:
```csharp
public class AddressComponent : IComponent
{
private string Title { get; }
private Address Address { get; }
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);
});
}
}
// code describing content
```