Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b9ea70f6e | |||
| 87f4c9774a | |||
| 3bdd03031b | |||
| 26335faa97 | |||
| 7b81e61840 | |||
| 391c5a6092 | |||
| d951e0a6b2 | |||
| 188fbc22a7 | |||
| cc6d1c81c8 | |||
| 6d333da84c | |||
| e411554a29 | |||
| 9fddea84ba | |||
| 84e896e13b | |||
| 569864d48a | |||
| 2c11491d4e | |||
| acdd8164be | |||
| 094b25a9fb | |||
| 122f35d0a7 | |||
| f4c9f8b3c2 | |||
| ae7873680d | |||
| 175db288f6 | |||
| 22401be791 | |||
| e2774ef347 | |||
| 99f004dd85 | |||
| 8ada092531 | |||
| c334390af0 | |||
| deed219a0a | |||
| bb8e590fc6 | |||
| a08a7b85c9 | |||
| 755447e1fd | |||
| b66a53a189 | |||
| 6d41ac9b9a | |||
| a124d23862 | |||
| 5f65b2daed | |||
| 07938a1510 | |||
| f17860f519 | |||
| 6023b4de34 | |||
| 748b8f65f5 | |||
| feab280a27 | |||
| ae32511172 | |||
| 6c4da7285a | |||
| 070b6a20bd | |||
| f7cfdb763f | |||
| 4152a0f701 | |||
| 6c80c59996 | |||
| 08a37666fe | |||
| c2fb68562d | |||
| 683ab362d8 | |||
| 04af33fc31 |
@@ -0,0 +1,33 @@
|
||||
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));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -581,7 +581,7 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(350, 350)
|
||||
.PageSize(650, 450)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
@@ -595,8 +595,8 @@ namespace QuestPDF.Examples
|
||||
foreach (var turns in Enumerable.Range(0, 4))
|
||||
{
|
||||
grid.Item()
|
||||
.Width(150)
|
||||
.Height(150)
|
||||
.Width(300)
|
||||
.Height(200)
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Padding(10)
|
||||
.Element(element =>
|
||||
@@ -778,5 +778,83 @@ 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));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,23 +12,24 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
return container
|
||||
.Border(1)
|
||||
.Background(dark ? "#EEE" : "#FFF")
|
||||
.Background(dark ? Colors.Grey.Lighten2 : Colors.White)
|
||||
.Padding(10);
|
||||
}
|
||||
|
||||
public static IContainer LabelCell(this IContainer container) => container.Cell(true);
|
||||
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text, TextStyle.Default.Medium());
|
||||
public static IContainer ValueCell(this IContainer container) => container.Cell(false);
|
||||
}
|
||||
|
||||
public class FrameExample
|
||||
{
|
||||
[Test]
|
||||
public void Frame(IContainer container)
|
||||
public void Frame()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(550, 400)
|
||||
.FileName()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -40,7 +41,7 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(2).LabelCell().Text(Placeholders.Label());
|
||||
row.RelativeColumn(2).LabelCell(Placeholders.Label());
|
||||
row.RelativeColumn(3).ValueCell().Text(Placeholders.Paragraph());
|
||||
});
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -12,8 +12,9 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(200, 150)
|
||||
.PageSize(300, 300)
|
||||
.FileName()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="nunit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
|
||||
<PackageReference Include="nunit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.2" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -20,6 +20,9 @@
|
||||
<None Update="quo-vadis.txt">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="LibreBarcode39-Regular.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -92,8 +92,8 @@ namespace QuestPDF.Examples
|
||||
|
||||
var lineFrom = chapterPointers[index];
|
||||
var lineTo = chapterPointers[index + 1] - 1;
|
||||
|
||||
var lines = book.Skip(lineFrom + 1).Take(lineTo - lineFrom).Where(x => !string.IsNullOrWhiteSpace(x))
|
||||
|
||||
var lines = book.Skip(lineFrom + 1).Take(lineTo - lineFrom).Where(x => !string.IsNullOrWhiteSpace(x));
|
||||
var content = string.Join(Environment.NewLine, lines);
|
||||
|
||||
yield return new BookChapter
|
||||
@@ -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).Underlined());
|
||||
text.ExternalLocation("wolnelektury.pl", "https://wolnelektury.pl/", normalStyle.Color(Colors.Blue.Medium).Underline());
|
||||
text.Span(". Dziękuję za wspieranie polskiego czytelnictwa!");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
@@ -22,35 +24,166 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
container
|
||||
.Padding(20)
|
||||
.Padding(10)
|
||||
.Box()
|
||||
.Border(1)
|
||||
.Padding(5)
|
||||
.Padding(10)
|
||||
.Text(text =>
|
||||
{
|
||||
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.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 =>
|
||||
{
|
||||
text.DefaultTextStyle(TextStyle.Default);
|
||||
text.AlignLeft();
|
||||
text.ParagraphSpacing(10);
|
||||
|
||||
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));
|
||||
foreach (var i in Enumerable.Range(1, 100))
|
||||
text.Line($"{i}: {Placeholders.Paragraph()}");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SpaceIssue()
|
||||
{
|
||||
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(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.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()));
|
||||
// }
|
||||
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();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,11 +8,10 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DeepCloner" Version="0.10.2" />
|
||||
<PackageReference Include="nunit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Include="nunit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.2" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -10,11 +10,7 @@ namespace QuestPDF.UnitTests
|
||||
public class AlignmentTests
|
||||
{
|
||||
[Test]
|
||||
public void Measure_ShouldHandleNullChild() => new Alignment().MeasureWithoutChild();
|
||||
|
||||
[Test]
|
||||
public void Draw_ShouldHandleNullChild() => new Alignment().DrawWithoutChild();
|
||||
|
||||
public void Measure() => SimpleContainerTests.Measure<Alignment>();
|
||||
[Test]
|
||||
public void Draw_HorizontalCenter_VerticalCenter()
|
||||
{
|
||||
|
||||
@@ -9,12 +9,6 @@ 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()
|
||||
{
|
||||
|
||||
@@ -10,8 +10,8 @@ namespace QuestPDF.UnitTests
|
||||
public class BackgroundTests
|
||||
{
|
||||
[Test]
|
||||
public void Measure_ShouldHandleNullChild() => new Background().MeasureWithoutChild();
|
||||
|
||||
public void Measure() => SimpleContainerTests.Measure<Background>();
|
||||
|
||||
[Test]
|
||||
public void Draw_ShouldHandleNullChild()
|
||||
{
|
||||
|
||||
@@ -11,8 +11,8 @@ namespace QuestPDF.UnitTests
|
||||
public class BorderTests
|
||||
{
|
||||
[Test]
|
||||
public void Measure_ShouldHandleNullChild() => new Border().MeasureWithoutChild();
|
||||
|
||||
public void Measure() => SimpleContainerTests.Measure<Border>();
|
||||
|
||||
[Test]
|
||||
public void ComponentShouldNotAffectLayout()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,12 +9,6 @@ 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()
|
||||
{
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace QuestPDF.UnitTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class DebugTests
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -78,5 +78,8 @@ namespace QuestPDF.UnitTests
|
||||
.ExpectChildMeasure(new Size(400, 300), new FullRender(300, 250))
|
||||
.CheckMeasureResult(new FullRender(300, 250));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Draw() => SimpleContainerTests.Draw<EnsureSpace>();
|
||||
}
|
||||
}
|
||||
@@ -9,12 +9,6 @@ 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()
|
||||
{
|
||||
@@ -87,5 +81,8 @@ namespace QuestPDF.UnitTests
|
||||
.ExpectChildMeasure(new Size(400, 200), new FullRender(100, 100))
|
||||
.CheckMeasureResult(new FullRender(100, 200));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Draw() => SimpleContainerTests.Draw<Extend>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -39,30 +39,30 @@ namespace QuestPDF.UnitTests
|
||||
// assert
|
||||
var expected = new Container();
|
||||
|
||||
expected.Container().Stack(stack =>
|
||||
expected.Stack(stack =>
|
||||
{
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(6).Container().Element(childA);
|
||||
row.RelativeColumn(4).Container().Element(childB);
|
||||
row.RelativeColumn(6).Element(childA);
|
||||
row.RelativeColumn(4).Element(childB);
|
||||
row.RelativeColumn(2);
|
||||
});
|
||||
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(4).Container().Element(childC);
|
||||
row.RelativeColumn(2).Container().Element(childD);
|
||||
row.RelativeColumn(4).Element(childC);
|
||||
row.RelativeColumn(2).Element(childD);
|
||||
row.RelativeColumn(6);
|
||||
});
|
||||
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(8).Container().Element(childE);
|
||||
row.RelativeColumn(8).Element(childE);
|
||||
row.RelativeColumn(4);
|
||||
});
|
||||
});
|
||||
|
||||
structure.Should().BeEquivalentTo(expected, o => o.WithTracing().WithAutoConversion().WithStrictOrdering().IncludingAllRuntimeProperties());
|
||||
TestPlan.CompareOperations(structure, expected);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -93,33 +93,33 @@ namespace QuestPDF.UnitTests
|
||||
// assert
|
||||
var expected = new Container();
|
||||
|
||||
expected.Container().Stack(stack =>
|
||||
expected.Stack(stack =>
|
||||
{
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(1);
|
||||
row.RelativeColumn(6).Container().Element(childA);
|
||||
row.RelativeColumn(4).Container().Element(childB);
|
||||
row.RelativeColumn(6).Element(childA);
|
||||
row.RelativeColumn(4).Element(childB);
|
||||
row.RelativeColumn(1);
|
||||
});
|
||||
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(3);
|
||||
row.RelativeColumn(4).Container().Element(childC);
|
||||
row.RelativeColumn(2).Container().Element(childD);
|
||||
row.RelativeColumn(4).Element(childC);
|
||||
row.RelativeColumn(2).Element(childD);
|
||||
row.RelativeColumn(3);
|
||||
});
|
||||
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(2);
|
||||
row.RelativeColumn(8).Container().Element(childE);
|
||||
row.RelativeColumn(8).Element(childE);
|
||||
row.RelativeColumn(2);
|
||||
});
|
||||
});
|
||||
|
||||
structure.Should().BeEquivalentTo(expected, o => o.WithTracing().WithAutoConversion().WithStrictOrdering().IncludingAllRuntimeProperties());
|
||||
TestPlan.CompareOperations(structure, expected);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -150,30 +150,30 @@ namespace QuestPDF.UnitTests
|
||||
// assert
|
||||
var expected = new Container();
|
||||
|
||||
expected.Container().Stack(stack =>
|
||||
expected.Stack(stack =>
|
||||
{
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(2);
|
||||
row.RelativeColumn(6).Container().Element(childA);
|
||||
row.RelativeColumn(4).Container().Element(childB);
|
||||
row.RelativeColumn(6).Element(childA);
|
||||
row.RelativeColumn(4).Element(childB);
|
||||
});
|
||||
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(6);
|
||||
row.RelativeColumn(4).Container().Element(childC);
|
||||
row.RelativeColumn(2).Container().Element(childD);
|
||||
row.RelativeColumn(4).Element(childC);
|
||||
row.RelativeColumn(2).Element(childD);
|
||||
});
|
||||
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(4);
|
||||
row.RelativeColumn(8).Container().Element(childE);
|
||||
row.RelativeColumn(8).Element(childE);
|
||||
});
|
||||
});
|
||||
|
||||
structure.Should().BeEquivalentTo(expected, o => o.WithTracing().WithAutoConversion().WithStrictOrdering().IncludingAllRuntimeProperties());
|
||||
TestPlan.CompareOperations(structure, expected);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -210,7 +210,7 @@ namespace QuestPDF.UnitTests
|
||||
// assert
|
||||
var expected = new Container();
|
||||
|
||||
expected.Container().Stack(stack =>
|
||||
expected.Stack(stack =>
|
||||
{
|
||||
stack.Spacing(20);
|
||||
|
||||
@@ -219,8 +219,8 @@ namespace QuestPDF.UnitTests
|
||||
row.Spacing(30);
|
||||
|
||||
row.RelativeColumn(3);
|
||||
row.RelativeColumn(5).Container().Element(childA);
|
||||
row.RelativeColumn(5).Container().Element(childB);
|
||||
row.RelativeColumn(5).Element(childA);
|
||||
row.RelativeColumn(5).Element(childB);
|
||||
row.RelativeColumn(3);
|
||||
});
|
||||
|
||||
@@ -229,7 +229,7 @@ namespace QuestPDF.UnitTests
|
||||
row.Spacing(30);
|
||||
|
||||
row.RelativeColumn(3);
|
||||
row.RelativeColumn(10).Container().Element(childC);
|
||||
row.RelativeColumn(10).Element(childC);
|
||||
row.RelativeColumn(3);
|
||||
});
|
||||
|
||||
@@ -238,12 +238,12 @@ namespace QuestPDF.UnitTests
|
||||
row.Spacing(30);
|
||||
|
||||
row.RelativeColumn(2);
|
||||
row.RelativeColumn(12).Container().Element(childD);
|
||||
row.RelativeColumn(12).Element(childD);
|
||||
row.RelativeColumn(2);
|
||||
});
|
||||
});
|
||||
|
||||
structure.Should().BeEquivalentTo(expected, o => o.WithTracing().WithAutoConversion().WithStrictOrdering().IncludingAllRuntimeProperties().AllowingInfiniteRecursion());
|
||||
TestPlan.CompareOperations(structure, expected);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
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());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,12 +11,6 @@ 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()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -9,12 +9,6 @@ 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()
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace QuestPDF.UnitTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class PageNumberTests
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<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="FluentAssertions" Version="6.1.0" />
|
||||
<PackageReference Include="nunit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.2" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.UnitTests.TestEngine;
|
||||
|
||||
@@ -109,8 +110,158 @@ namespace QuestPDF.UnitTests
|
||||
|
||||
#endregion
|
||||
|
||||
// TODO: add tests for the spacing property
|
||||
// TODO: add tests for the tree builder method
|
||||
// TODO: add tests for relative column
|
||||
#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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -47,5 +47,8 @@ namespace QuestPDF.UnitTests
|
||||
.ExpectChildMeasure(new Size(400, 300), new FullRender(300, 200))
|
||||
.CheckMeasureResult(new FullRender(300, 200));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Draw() => SimpleContainerTests.Draw<ShowEntire>();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
@@ -11,33 +10,7 @@ namespace QuestPDF.UnitTests
|
||||
public class ShowOnceTest
|
||||
{
|
||||
[Test]
|
||||
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()
|
||||
public void Draw()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new ShowOnce()
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
using NUnit.Framework;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.UnitTests.TestEngine;
|
||||
|
||||
@@ -229,7 +231,81 @@ namespace QuestPDF.UnitTests
|
||||
|
||||
#endregion
|
||||
|
||||
// TODO: add tests for the spacing property
|
||||
// TODO: add tests for the tree builder method
|
||||
#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
|
||||
}
|
||||
}
|
||||
+9
-25
@@ -4,41 +4,25 @@ using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.UnitTests.TestEngine
|
||||
{
|
||||
internal class CanvasMock : ICanvas
|
||||
internal class MockCanvas : 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 DrawLink(string url, Size size)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Size MeasureText(string text, TextStyle style)
|
||||
{
|
||||
return new Size(text.Length * style.Size, style.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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
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 CanvasDrawImageOperationBase : OperationBase
|
||||
internal class CanvasDrawImageOperation : OperationBase
|
||||
{
|
||||
public Position Position { get; }
|
||||
public Size Size { get; }
|
||||
|
||||
public CanvasDrawImageOperationBase(Position position, Size size)
|
||||
public CanvasDrawImageOperation(Position position, Size size)
|
||||
{
|
||||
Position = position;
|
||||
Size = size;
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
namespace QuestPDF.UnitTests.TestEngine.Operations
|
||||
{
|
||||
internal class CanvasDrawRectangleOperationBase : OperationBase
|
||||
internal class CanvasDrawRectangleOperation : OperationBase
|
||||
{
|
||||
public Position Position { get; }
|
||||
public Size Size { get; }
|
||||
public string Color { get; }
|
||||
|
||||
public CanvasDrawRectangleOperationBase(Position position, Size size, string color)
|
||||
public CanvasDrawRectangleOperation(Position position, Size size, string color)
|
||||
{
|
||||
Position = position;
|
||||
Size = size;
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
namespace QuestPDF.UnitTests.TestEngine.Operations
|
||||
{
|
||||
internal class CanvasDrawTextOperationBase : OperationBase
|
||||
internal class CanvasDrawTextOperation : OperationBase
|
||||
{
|
||||
public string Text { get; }
|
||||
public Position Position { get; }
|
||||
public TextStyle Style { get; }
|
||||
|
||||
public CanvasDrawTextOperationBase(string text, Position position, TextStyle style)
|
||||
public CanvasDrawTextOperation(string text, Position position, TextStyle style)
|
||||
{
|
||||
Text = text;
|
||||
Position = position;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace QuestPDF.UnitTests.TestEngine.Operations
|
||||
{
|
||||
public class CanvasRotateOperation : OperationBase
|
||||
{
|
||||
public float Angle { get; }
|
||||
|
||||
public CanvasRotateOperation(float angle)
|
||||
{
|
||||
Angle = angle;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
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 CanvasTranslateOperationBase : OperationBase
|
||||
internal class CanvasTranslateOperation : OperationBase
|
||||
{
|
||||
public Position Position { get; }
|
||||
|
||||
public CanvasTranslateOperationBase(Position position)
|
||||
public CanvasTranslateOperation(Position position)
|
||||
{
|
||||
Position = position;
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
namespace QuestPDF.UnitTests.TestEngine.Operations
|
||||
{
|
||||
public class ChildDrawOperationBase : OperationBase
|
||||
public class ChildDrawOperation : OperationBase
|
||||
{
|
||||
public string ChildId { get; }
|
||||
public Size Input { get; }
|
||||
|
||||
public ChildDrawOperationBase(string childId, Size input)
|
||||
public ChildDrawOperation(string childId, Size input)
|
||||
{
|
||||
ChildId = childId;
|
||||
Input = input;
|
||||
|
||||
@@ -3,13 +3,13 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.UnitTests.TestEngine.Operations
|
||||
{
|
||||
internal class ChildMeasureOperationBase : OperationBase
|
||||
internal class ChildMeasureOperation : OperationBase
|
||||
{
|
||||
public string ChildId { get; }
|
||||
public Size Input { get; }
|
||||
public ISpacePlan Output { get; }
|
||||
|
||||
public ChildMeasureOperationBase(string childId, Size input, ISpacePlan output)
|
||||
public ChildMeasureOperation(string childId, Size input, ISpacePlan output)
|
||||
{
|
||||
ChildId = childId;
|
||||
Input = input;
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace QuestPDF.UnitTests.TestEngine.Operations
|
||||
{
|
||||
public class ElementMeasureOperationBase : OperationBase
|
||||
public class ElementMeasureOperation : OperationBase
|
||||
{
|
||||
public ElementMeasureOperationBase(Size input)
|
||||
public ElementMeasureOperation(Size input)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
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();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
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;
|
||||
@@ -13,6 +15,8 @@ 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; }
|
||||
@@ -45,20 +49,31 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
|
||||
private ICanvas CreateCanvas()
|
||||
{
|
||||
return new CanvasMock
|
||||
return new MockCanvas
|
||||
{
|
||||
TranslateFunc = position =>
|
||||
{
|
||||
var expected = GetExpected<CanvasTranslateOperationBase>();
|
||||
var expected = GetExpected<CanvasTranslateOperation>();
|
||||
|
||||
Assert.AreEqual(expected.Position.X, position.X, "Translate X");
|
||||
Assert.AreEqual(expected.Position.Y, position.Y, "Translate Y");
|
||||
|
||||
//position.Should().BeEquivalentTo(expected.Position);
|
||||
},
|
||||
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");
|
||||
},
|
||||
DrawRectFunc = (position, size, color) =>
|
||||
{
|
||||
var expected = GetExpected<CanvasDrawRectangleOperationBase>();
|
||||
var expected = GetExpected<CanvasDrawRectangleOperation>();
|
||||
|
||||
Assert.AreEqual(expected.Position.X, position.X, "Draw rectangle: X");
|
||||
Assert.AreEqual(expected.Position.Y, position.Y, "Draw rectangle: Y");
|
||||
@@ -67,14 +82,10 @@ 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<CanvasDrawTextOperationBase>();
|
||||
var expected = GetExpected<CanvasDrawTextOperation>();
|
||||
|
||||
Assert.AreEqual(expected.Text, text);
|
||||
|
||||
@@ -84,23 +95,16 @@ 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<CanvasDrawImageOperationBase>();
|
||||
var expected = GetExpected<CanvasDrawImageOperation>();
|
||||
|
||||
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);*/
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -114,29 +118,23 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
Id = id,
|
||||
MeasureFunc = availableSpace =>
|
||||
{
|
||||
var expected = GetExpected<ChildMeasureOperationBase>();
|
||||
var expected = GetExpected<ChildMeasureOperation>();
|
||||
|
||||
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<ChildDrawOperationBase>();
|
||||
var expected = GetExpected<ChildDrawOperation>();
|
||||
|
||||
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);*/
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -166,7 +164,7 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
|
||||
public TestPlan ExpectChildMeasure(string child, Size expectedInput, ISpacePlan returns)
|
||||
{
|
||||
return AddOperation(new ChildMeasureOperationBase(child, expectedInput, returns));
|
||||
return AddOperation(new ChildMeasureOperation(child, expectedInput, returns));
|
||||
}
|
||||
|
||||
public TestPlan ExpectChildDraw(Size expectedInput)
|
||||
@@ -176,32 +174,42 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
|
||||
public TestPlan ExpectChildDraw(string child, Size expectedInput)
|
||||
{
|
||||
return AddOperation(new ChildDrawOperationBase(child, expectedInput));
|
||||
return AddOperation(new ChildDrawOperation(child, expectedInput));
|
||||
}
|
||||
|
||||
public TestPlan ExpectCanvasTranslate(Position position)
|
||||
{
|
||||
return AddOperation(new CanvasTranslateOperationBase(position));
|
||||
return AddOperation(new CanvasTranslateOperation(position));
|
||||
}
|
||||
|
||||
public TestPlan ExpectCanvasTranslate(float left, float top)
|
||||
{
|
||||
return AddOperation(new CanvasTranslateOperationBase(new Position(left, top)));
|
||||
return AddOperation(new CanvasTranslateOperation(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 CanvasDrawRectangleOperationBase(position, size, color));
|
||||
return AddOperation(new CanvasDrawRectangleOperation(position, size, color));
|
||||
}
|
||||
|
||||
public TestPlan ExpectCanvasDrawText(string text, Position position, TextStyle style)
|
||||
{
|
||||
return AddOperation(new CanvasDrawTextOperationBase(text, position, style));
|
||||
return AddOperation(new CanvasDrawTextOperation(text, position, style));
|
||||
}
|
||||
|
||||
public TestPlan ExpectCanvasDrawImage(Position position, Size size)
|
||||
{
|
||||
return AddOperation(new CanvasDrawImageOperationBase(position, size));
|
||||
return AddOperation(new CanvasDrawImageOperation(position, size));
|
||||
}
|
||||
|
||||
public TestPlan CheckMeasureResult(ISpacePlan expected)
|
||||
@@ -246,10 +254,51 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
|
||||
public static Element CreateUniqueElement()
|
||||
{
|
||||
return new DynamicImage
|
||||
return new Constrained
|
||||
{
|
||||
Source = Placeholders.Image
|
||||
MinWidth = 90,
|
||||
MinHeight = 60,
|
||||
|
||||
Child = new DynamicImage
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace QuestPDF.UnitTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TextTests
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,10 @@ namespace QuestPDF.Drawing
|
||||
var spacePlan = content.Measure(Size.Max) as Size;
|
||||
|
||||
if (spacePlan == null)
|
||||
break;
|
||||
{
|
||||
canvas.EndDocument();
|
||||
ThrowLayoutException();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -84,6 +87,11 @@ namespace QuestPDF.Drawing
|
||||
}
|
||||
|
||||
canvas.EndDocument();
|
||||
|
||||
void ThrowLayoutException()
|
||||
{
|
||||
throw new DocumentLayoutException("Composed layout generates infinite document.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,24 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
internal static class CanvasCache
|
||||
public static class FontManager
|
||||
{
|
||||
internal static ConcurrentDictionary<string, SKPaint> Paints = new ConcurrentDictionary<string, SKPaint>();
|
||||
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);
|
||||
@@ -29,16 +38,25 @@ namespace QuestPDF.Drawing
|
||||
|
||||
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),
|
||||
Typeface = GetTypeface(style),
|
||||
TextSize = style.Size,
|
||||
TextEncoding = SKTextEncoding.Utf32
|
||||
};
|
||||
}
|
||||
|
||||
static SKTypeface GetTypeface(TextStyle style)
|
||||
{
|
||||
if (Typefaces.TryGetValue(style.FontType, out var result))
|
||||
return result;
|
||||
|
||||
var slant = style.IsItalic ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
|
||||
|
||||
return SKTypeface.FromFamilyName(style.FontType, (int)style.FontWeight, (int)SKFontStyleWidth.Normal, slant)
|
||||
?? throw new ArgumentException($"The typeface {style.FontType} could not be found.");
|
||||
}
|
||||
}
|
||||
|
||||
internal static SKFontMetrics ToFontMetrics(this TextStyle style)
|
||||
@@ -66,6 +66,16 @@ namespace QuestPDF.Drawing
|
||||
|
||||
}
|
||||
|
||||
public void Rotate(float angle)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void Scale(float scaleX, float scaleY)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -51,5 +51,15 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
public const int DefaultColumnsCount = 12;
|
||||
|
||||
public List<GridElement> Children { get; set; } = new List<GridElement>();
|
||||
public List<GridElement> Children { get; } = new List<GridElement>();
|
||||
public Queue<GridElement> ChildrenQueue { get; set; } = new Queue<GridElement>();
|
||||
public int ColumnsCount { get; set; } = DefaultColumnsCount;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
@@ -36,9 +37,20 @@ namespace QuestPDF.Elements
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
decoration.Header().Element(Header);
|
||||
decoration.Content().Extend().Element(Content);
|
||||
|
||||
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.Footer().Element(Footer);
|
||||
});
|
||||
|
||||
bool IsClose(float x, float y)
|
||||
{
|
||||
return Math.Abs(x - y) < Size.Epsilon;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,16 +8,9 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
|
||||
|
||||
if (skiaCanvas == null)
|
||||
return;
|
||||
|
||||
var currentMatrix = skiaCanvas.TotalMatrix;
|
||||
|
||||
skiaCanvas.RotateDegrees(Angle);
|
||||
Canvas.Rotate(Angle);
|
||||
Child?.Draw(availableSpace);
|
||||
skiaCanvas.SetMatrix(currentMatrix);
|
||||
Canvas.Rotate(-Angle);
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
-44
@@ -6,29 +6,31 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal abstract class RowElement : ContainerElement
|
||||
internal abstract class RowElement : Constrained
|
||||
{
|
||||
public float Width { get; set; } = 1;
|
||||
public float Size { get; protected set; }
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
public void SetWidth(float width)
|
||||
{
|
||||
Child?.Draw(availableSpace);
|
||||
MinWidth = width;
|
||||
MaxWidth = width;
|
||||
}
|
||||
}
|
||||
|
||||
internal class ConstantRowElement : RowElement
|
||||
{
|
||||
public ConstantRowElement(float width)
|
||||
public ConstantRowElement(float size)
|
||||
{
|
||||
Width = width;
|
||||
Size = size;
|
||||
SetWidth(size);
|
||||
}
|
||||
}
|
||||
|
||||
internal class RelativeRowElement : RowElement
|
||||
{
|
||||
public RelativeRowElement(float width)
|
||||
public RelativeRowElement(float size)
|
||||
{
|
||||
Width = width;
|
||||
Size = size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,70 +85,61 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal class Row : Element
|
||||
{
|
||||
public ICollection<RowElement> Children { get; internal set; } = new List<RowElement>();
|
||||
public float Spacing { get; set; } = 0;
|
||||
|
||||
|
||||
public ICollection<RowElement> Children { get; internal set; } = new List<RowElement>();
|
||||
private Element? RootElement { get; set; }
|
||||
|
||||
internal override void HandleVisitor(Action<Element?> visit)
|
||||
{
|
||||
Children.ToList().ForEach(x => x.HandleVisitor(visit));
|
||||
if (RootElement == null)
|
||||
ComposeTree();
|
||||
|
||||
RootElement.HandleVisitor(visit);
|
||||
base.HandleVisitor(visit);
|
||||
}
|
||||
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
return Compose(availableSpace.Width).Measure(availableSpace);
|
||||
UpdateElementsWidth(availableSpace.Width);
|
||||
return RootElement.Measure(availableSpace);
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
Compose(availableSpace.Width).Draw(availableSpace);
|
||||
UpdateElementsWidth(availableSpace.Width);
|
||||
RootElement.Draw(availableSpace);
|
||||
}
|
||||
|
||||
#region structure
|
||||
|
||||
private Element Compose(float availableWidth)
|
||||
private void ComposeTree()
|
||||
{
|
||||
var elements = AddSpacing(Children, Spacing);
|
||||
var rowElements = ReduceRows(elements, availableWidth);
|
||||
var tree = BuildTree(rowElements.ToArray());
|
||||
tree.HandleVisitor(x => x.Initialize(PageContext, Canvas));
|
||||
Children = AddSpacing(Children, Spacing);
|
||||
|
||||
return tree;
|
||||
var elements = Children.Cast<Element>().ToArray();
|
||||
RootElement = BuildTree(elements);
|
||||
}
|
||||
|
||||
private static ICollection<Element> ReduceRows(ICollection<RowElement> elements, float availableWidth)
|
||||
|
||||
private void UpdateElementsWidth(float availableWidth)
|
||||
{
|
||||
var constantWidth = elements
|
||||
var constantWidth = Children
|
||||
.Where(x => x is ConstantRowElement)
|
||||
.Cast<ConstantRowElement>()
|
||||
.Sum(x => x.Width);
|
||||
.Sum(x => x.Size);
|
||||
|
||||
var relativeWidth = elements
|
||||
var relativeWidth = Children
|
||||
.Where(x => x is RelativeRowElement)
|
||||
.Cast<RelativeRowElement>()
|
||||
.Sum(x => x.Width);
|
||||
.Sum(x => x.Size);
|
||||
|
||||
var widthPerRelativeUnit = (availableWidth - constantWidth) / relativeWidth;
|
||||
|
||||
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();
|
||||
Children
|
||||
.Where(x => x is RelativeRowElement)
|
||||
.Cast<RelativeRowElement>()
|
||||
.ToList()
|
||||
.ForEach(x => x.SetWidth(x.Size * widthPerRelativeUnit));
|
||||
}
|
||||
|
||||
private static ICollection<RowElement> AddSpacing(ICollection<RowElement> elements, float spacing)
|
||||
|
||||
@@ -35,26 +35,21 @@ 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 currentMatrix = skiaCanvas.TotalMatrix;
|
||||
var translate = new Position(
|
||||
ScaleX < 0 ? availableSpace.Width : 0,
|
||||
ScaleY < 0 ? availableSpace.Height : 0);
|
||||
|
||||
if (ScaleX < 0)
|
||||
skiaCanvas.Translate(availableSpace.Width, 0);
|
||||
Canvas.Translate(translate);
|
||||
Canvas.Scale(ScaleX, ScaleY);
|
||||
|
||||
if (ScaleY < 0)
|
||||
skiaCanvas.Translate(0, availableSpace.Height);
|
||||
|
||||
skiaCanvas.Scale(ScaleX, ScaleY);
|
||||
Child?.Draw(targetSpace);
|
||||
skiaCanvas.SetMatrix(currentMatrix);
|
||||
|
||||
Canvas.Scale(1/ScaleX, 1/ScaleY);
|
||||
Canvas.Translate(translate.Reverse());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,26 +33,22 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
|
||||
var translate = new Position(
|
||||
(NormalizedTurnCount == 1 || NormalizedTurnCount == 2) ? availableSpace.Width : 0,
|
||||
(NormalizedTurnCount == 2 || NormalizedTurnCount == 3) ? availableSpace.Height : 0);
|
||||
|
||||
var rotate = NormalizedTurnCount * 90;
|
||||
|
||||
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);
|
||||
Canvas.Translate(translate);
|
||||
Canvas.Rotate(rotate);
|
||||
|
||||
if (NormalizedTurnCount == 1 || NormalizedTurnCount == 3)
|
||||
availableSpace = new Size(availableSpace.Height, availableSpace.Width);
|
||||
|
||||
Child?.Draw(availableSpace);
|
||||
skiaCanvas.SetMatrix(currentMatrix);
|
||||
|
||||
Canvas.Rotate(-rotate);
|
||||
Canvas.Translate(translate.Reverse());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,7 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal class Stack : IComponent
|
||||
{
|
||||
public ICollection<Element> Children { get; internal set; } = new List<Element>();
|
||||
public ICollection<Element> Children { get; } = new List<Element>();
|
||||
public float Spacing { get; set; } = 0;
|
||||
|
||||
public void Compose(IContainer container)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Elements.Text.Items;
|
||||
|
||||
@@ -6,14 +7,41 @@ namespace QuestPDF.Elements.Text.Calculation
|
||||
{
|
||||
internal class TextLine
|
||||
{
|
||||
public ICollection<TextLineElement> Elements { get; set; }
|
||||
public ICollection<TextLineElement> Elements { 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 TextHeight { get; private set; }
|
||||
public float LineHeight { 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 Ascent { get; private set; }
|
||||
public float Descent { get; private set; }
|
||||
|
||||
public float Width => Elements.Sum(x => x.Measurement.Width);
|
||||
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)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
using QuestPDF.Elements.Text.Items;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
namespace QuestPDF.Elements.Text.Calculation
|
||||
{
|
||||
internal class TextLineElement
|
||||
{
|
||||
|
||||
@@ -33,9 +33,12 @@ namespace QuestPDF.Elements.Text.Items
|
||||
|
||||
var startIndex = request.StartIndex;
|
||||
|
||||
while (startIndex + 1 < Text.Length && Text[startIndex] == space)
|
||||
startIndex++;
|
||||
|
||||
if (request.IsFirstLineElement)
|
||||
{
|
||||
while (startIndex + 1 < Text.Length && Text[startIndex] == space)
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
if (Text.Length == 0)
|
||||
{
|
||||
return new TextMeasurementResult
|
||||
@@ -51,15 +54,18 @@ namespace QuestPDF.Elements.Text.Items
|
||||
// start breaking text from requested position
|
||||
var text = Text.Substring(startIndex);
|
||||
|
||||
var breakingIndex = (int)paint.BreakText(text, request.AvailableWidth);
|
||||
var textLength = (int)paint.BreakText(text, request.AvailableWidth);
|
||||
|
||||
if (breakingIndex <= 0)
|
||||
if (textLength <= 0)
|
||||
return null;
|
||||
|
||||
if (textLength < text.Length && text[textLength] == space)
|
||||
textLength++;
|
||||
|
||||
// break text only on spaces
|
||||
if (breakingIndex < text.Length)
|
||||
if (textLength < text.Length)
|
||||
{
|
||||
var lastSpaceIndex = text.Substring(0, breakingIndex).LastIndexOf(space) - 1;
|
||||
var lastSpaceIndex = text.Substring(0, textLength).LastIndexOf(space) - 1;
|
||||
|
||||
if (lastSpaceIndex <= 0)
|
||||
{
|
||||
@@ -68,13 +74,13 @@ namespace QuestPDF.Elements.Text.Items
|
||||
}
|
||||
else
|
||||
{
|
||||
breakingIndex = lastSpaceIndex + 1;
|
||||
textLength = lastSpaceIndex + 1;
|
||||
}
|
||||
}
|
||||
|
||||
text = text.Substring(0, breakingIndex);
|
||||
text = text.Substring(0, textLength);
|
||||
|
||||
var endIndex = startIndex + breakingIndex;
|
||||
var endIndex = startIndex + textLength;
|
||||
var nextIndex = endIndex;
|
||||
|
||||
while (nextIndex + 1 < Text.Length && Text[nextIndex] == space)
|
||||
@@ -109,11 +115,11 @@ namespace QuestPDF.Elements.Text.Items
|
||||
request.Canvas.DrawText(text, Position.Zero, Style);
|
||||
|
||||
// draw underline
|
||||
if (Style.IsUnderlined && fontMetrics.UnderlinePosition.HasValue)
|
||||
if (Style.HasUnderline && fontMetrics.UnderlinePosition.HasValue)
|
||||
DrawLine(fontMetrics.UnderlinePosition.Value, fontMetrics.UnderlineThickness.Value);
|
||||
|
||||
// draw stroke
|
||||
if (Style.IsStroked && fontMetrics.StrikeoutPosition.HasValue)
|
||||
if (Style.HasStrikethrough && fontMetrics.StrikeoutPosition.HasValue)
|
||||
DrawLine(fontMetrics.StrikeoutPosition.Value, fontMetrics.StrikeoutThickness.Value);
|
||||
|
||||
void DrawLine(float offset, float thickness)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
@@ -26,7 +27,7 @@ namespace QuestPDF.Elements.Text
|
||||
if (!RenderingQueue.Any())
|
||||
return new FullRender(Size.Zero);
|
||||
|
||||
var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height);
|
||||
var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height).ToList();
|
||||
|
||||
if (!lines.Any())
|
||||
return new PartialRender(Size.Zero);
|
||||
@@ -62,14 +63,9 @@ namespace QuestPDF.Elements.Text
|
||||
{
|
||||
widthOffset = 0f;
|
||||
|
||||
var emptySpace = availableSpace.Width - line.Width;
|
||||
|
||||
if (Alignment == HorizontalAlignment.Center)
|
||||
emptySpace /= 2f;
|
||||
|
||||
if (Alignment != HorizontalAlignment.Left)
|
||||
Canvas.Translate(new Position(emptySpace, 0));
|
||||
var alignmentOffset = GetAlignmentOffset(line.Width);
|
||||
|
||||
Canvas.Translate(new Position(alignmentOffset, 0));
|
||||
Canvas.Translate(new Position(0, -line.Ascent));
|
||||
|
||||
foreach (var item in line.Elements)
|
||||
@@ -92,12 +88,10 @@ namespace QuestPDF.Elements.Text
|
||||
widthOffset += item.Measurement.Width;
|
||||
}
|
||||
|
||||
if (Alignment != HorizontalAlignment.Right)
|
||||
Canvas.Translate(new Position(emptySpace, 0));
|
||||
|
||||
Canvas.Translate(new Position(-line.Width - emptySpace, line.Ascent));
|
||||
|
||||
Canvas.Translate(new Position(-alignmentOffset, 0));
|
||||
Canvas.Translate(new Position(-line.Width, line.Ascent));
|
||||
Canvas.Translate(new Position(0, line.LineHeight));
|
||||
|
||||
heightOffset += line.LineHeight;
|
||||
}
|
||||
|
||||
@@ -116,6 +110,22 @@ namespace QuestPDF.Elements.Text
|
||||
|
||||
if (!RenderingQueue.Any())
|
||||
ResetState();
|
||||
|
||||
float GetAlignmentOffset(float lineWidth)
|
||||
{
|
||||
if (Alignment == HorizontalAlignment.Left)
|
||||
return 0;
|
||||
|
||||
var emptySpace = availableSpace.Width - lineWidth;
|
||||
|
||||
if (Alignment == HorizontalAlignment.Right)
|
||||
return emptySpace;
|
||||
|
||||
if (Alignment == HorizontalAlignment.Center)
|
||||
return emptySpace / 2;
|
||||
|
||||
throw new ArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<TextLine> DivideTextItemsIntoLines(float availableWidth, float availableHeight)
|
||||
@@ -182,10 +192,7 @@ namespace QuestPDF.Elements.Text
|
||||
queue.Dequeue();
|
||||
}
|
||||
|
||||
return new TextLine
|
||||
{
|
||||
Elements = currentLineElements
|
||||
};
|
||||
return TextLine.From(currentLineElements);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,14 +10,11 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
|
||||
var translate = new Position(TranslateX, TranslateY);
|
||||
|
||||
if (skiaCanvas == null)
|
||||
return;
|
||||
|
||||
skiaCanvas.Translate(TranslateX, TranslateY);
|
||||
Canvas.Translate(translate);
|
||||
base.Draw(availableSpace);
|
||||
skiaCanvas.Translate(-TranslateX, -TranslateY);
|
||||
Canvas.Translate(translate.Reverse());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,10 +18,10 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
if (element?.Child != null && element.Child is Empty == false)
|
||||
{
|
||||
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.";
|
||||
|
||||
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.";
|
||||
|
||||
throw new DocumentComposeException(message);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,14 +12,24 @@ namespace QuestPDF.Fluent
|
||||
|
||||
public void Size(PageSize pageSize)
|
||||
{
|
||||
Page.MinSize = pageSize;
|
||||
Page.MaxSize = pageSize;
|
||||
MinSize(pageSize);
|
||||
MaxSize(pageSize);
|
||||
}
|
||||
|
||||
|
||||
public void ContinuousSize(float width)
|
||||
{
|
||||
Page.MinSize = new PageSize(width, 0);
|
||||
Page.MaxSize = new PageSize(width, Infrastructure.Size.Max.Height);
|
||||
MinSize(new PageSize(width, 0));
|
||||
MaxSize(new PageSize(width, Infrastructure.Size.Max.Height));
|
||||
}
|
||||
|
||||
public void MinSize(PageSize pageSize)
|
||||
{
|
||||
Page.MinSize = pageSize;
|
||||
}
|
||||
|
||||
public void MaxSize(PageSize pageSize)
|
||||
{
|
||||
Page.MaxSize = pageSize;
|
||||
}
|
||||
|
||||
public void MarginLeft(float value)
|
||||
|
||||
@@ -5,6 +5,7 @@ using QuestPDF.Elements;
|
||||
using QuestPDF.Elements.Text;
|
||||
using QuestPDF.Elements.Text.Items;
|
||||
using QuestPDF.Infrastructure;
|
||||
using static System.String;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
@@ -53,7 +54,8 @@ namespace QuestPDF.Fluent
|
||||
style ??= DefaultStyle;
|
||||
|
||||
var items = text
|
||||
.Split(new[] { Environment.NewLine }, StringSplitOptions.None)
|
||||
.Replace("\r", string.Empty)
|
||||
.Split(new[] { '\n' }, StringSplitOptions.None)
|
||||
.Select(x => new TextBlockSpan
|
||||
{
|
||||
Text = x,
|
||||
@@ -73,7 +75,12 @@ namespace QuestPDF.Fluent
|
||||
.ForEach(TextBlocks.Add);
|
||||
}
|
||||
|
||||
public void NewLine()
|
||||
public void Line(string text)
|
||||
{
|
||||
Span(text + Environment.NewLine);
|
||||
}
|
||||
|
||||
public void EmptyLine()
|
||||
{
|
||||
Span(Environment.NewLine);
|
||||
}
|
||||
|
||||
@@ -43,14 +43,14 @@ namespace QuestPDF.Fluent
|
||||
return style.Mutate(x => x.IsItalic = value);
|
||||
}
|
||||
|
||||
public static TextStyle Stroked(this TextStyle style, bool value = true)
|
||||
public static TextStyle Strikethrough(this TextStyle style, bool value = true)
|
||||
{
|
||||
return style.Mutate(x => x.IsStroked = value);
|
||||
return style.Mutate(x => x.HasStrikethrough = value);
|
||||
}
|
||||
|
||||
public static TextStyle Underlined(this TextStyle style, bool value = true)
|
||||
public static TextStyle Underline(this TextStyle style, bool value = true)
|
||||
{
|
||||
return style.Mutate(x => x.IsUnderlined = value);
|
||||
return style.Mutate(x => x.HasUnderline = value);
|
||||
}
|
||||
|
||||
#region Weight
|
||||
|
||||
@@ -13,5 +13,8 @@ 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);
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,8 @@ namespace QuestPDF.Infrastructure
|
||||
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;
|
||||
internal bool HasStrikethrough { get; set; } = false;
|
||||
internal bool HasUnderline { get; set; } = false;
|
||||
|
||||
public static TextStyle Default => new TextStyle();
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace QuestPDF.Infrastructure
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
KeyCache ??= $"{Color}|{BackgroundColor}|{FontType}|{Size}|{LineHeight}|{FontWeight}|{IsItalic}|{IsStroked}|{IsUnderlined}";
|
||||
KeyCache ??= $"{Color}|{BackgroundColor}|{FontType}|{Size}|{LineHeight}|{FontWeight}|{IsItalic}|{HasStrikethrough}|{HasUnderline}";
|
||||
return KeyCache;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
<Authors>MarcinZiabek</Authors>
|
||||
<Company>CodeFlint</Company>
|
||||
<PackageId>QuestPDF</PackageId>
|
||||
<Version>2021.8</Version>
|
||||
<Version>2021.10.0</Version>
|
||||
<PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
|
||||
<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>
|
||||
<PackageReleaseNotes>Enhanced text rendering capabilities. Improved rendering performance.</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>QuestPDF contributors</Copyright>
|
||||
<PackageTags>PDF file export generate create render portable document format quest free</PackageTags>
|
||||
<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>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<Nullable>enable</Nullable>
|
||||
<TargetFrameworks>net462;netstandard2.0;netcoreapp2.0;netcoreapp3.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.2" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -4,17 +4,17 @@
|
||||
|
||||
## Overview
|
||||
|
||||
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.
|
||||
**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.
|
||||
|
||||
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.
|
||||
**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.
|
||||
|
||||
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!
|
||||
**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!
|
||||
|
||||
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.
|
||||
**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.
|
||||
|
||||
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.
|
||||
**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.
|
||||
|
||||
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.
|
||||
**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
|
||||
|
||||
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.
|
||||
**[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.
|
||||
|
||||
## Example invoice
|
||||
|
||||
@@ -51,39 +51,184 @@ 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:
|
||||
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:
|
||||
|
||||
```csharp
|
||||
public void Compose(IDocumentContainer container)
|
||||
{
|
||||
{
|
||||
container
|
||||
.Page(page =>
|
||||
{
|
||||
page.MarginVertical(60);
|
||||
page.MarginHorizontal(40);
|
||||
page.Margin(50);
|
||||
|
||||
page.Size(PageSizes.A4);
|
||||
|
||||
page.Header().Element(ComposeHeader);
|
||||
page.Content().Element(ComposeContent);
|
||||
page.Footer().AlignCenter().PageNumber();
|
||||
|
||||
page.Footer().AlignCenter().Text(x =>
|
||||
{
|
||||
x.CurrentPageNumber();
|
||||
x.Span(" / ");
|
||||
x.TotalPages();
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**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}", TextStyle.Default.Size(20).Bold());
|
||||
stack.Item().Text($"Issue date: {Model.IssueDate:d}");
|
||||
stack.Item().Text($"Due date: {Model.DueDate:d}");
|
||||
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}");
|
||||
});
|
||||
});
|
||||
|
||||
row.ConstantColumn(100).Height(50).Placeholder();
|
||||
});
|
||||
}
|
||||
|
||||
// code describing content
|
||||
```
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user