Compare commits

...

66 Commits

Author SHA1 Message Date
Marcin Ziąbek 17e2727833 Added missing option to style a line 2021-10-01 01:44:53 +02:00
Marcin Ziąbek 1b9ea70f6e Fixed code highlighting 2021-09-30 23:50:34 +02:00
Marcin Ziąbek 87f4c9774a Update readme.md 2021-09-30 23:48:35 +02:00
Marcin Ziąbek 3bdd03031b Updated csproj 2021-09-30 23:08:47 +02:00
Marcin Ziąbek 26335faa97 Improved and fixed old tests 2021-09-30 22:48:39 +02:00
Marcin Ziąbek 7b81e61840 Merge pull request #26 from QuestPDF/richtext
2021.10 Richtext
2021-09-30 22:05:45 +02:00
Marcin Ziąbek 391c5a6092 Merge branch 'main' into richtext 2021-09-30 22:04:47 +02:00
Marcin Ziąbek d951e0a6b2 Removed not needed nuget dependencies 2021-09-19 01:42:52 +02:00
Marcin Ziąbek 188fbc22a7 Added structural tests for: Row, Stack. 2021-09-18 23:40:21 +02:00
Marcin Ziąbek cc6d1c81c8 Updated tests to use FluentAssertions 6 2021-09-18 20:59:21 +02:00
Marcin Ziąbek 6d333da84c Added tests to check simple, container-like constraints for some elements 2021-09-18 01:21:48 +02:00
Marcin Ziąbek e411554a29 Added tests for elements: Box, Layers, Rotate, Scale, SimpleRotate, Translate, Unconstrained 2021-09-18 01:03:58 +02:00
Marcin Ziąbek 9fddea84ba Cleaned tests suite 2021-09-17 22:05:07 +02:00
Marcin Ziąbek 84e896e13b 2021.10.0-beta.2 Improved API naming 2021-09-14 12:16:49 +02:00
Marcin Ziąbek 569864d48a Fixed rendering text when put in the Box element 2021-09-14 02:13:15 +02:00
Marcin Ziąbek 2c11491d4e Library update 2021-09-14 02:02:45 +02:00
Marcin Ziąbek acdd8164be Optimized the TextLine object 2021-09-14 00:16:16 +02:00
Marcin Ziąbek 094b25a9fb Merge remote-tracking branch 'origin/richtext' into richtext
# Conflicts:
#	QuestPDF.Examples/QuestPDF.Examples.csproj
#	QuestPDF/Drawing/DocumentGenerator.cs
#	QuestPDF/Drawing/FontManager.cs
#	QuestPDF/Elements/Text/Items/TextBlockSpan.cs
2021-09-13 20:38:50 +02:00
Marcin Ziąbek 122f35d0a7 2021.10-beta + Code rebase 2021-09-13 20:36:29 +02:00
Marcin Ziąbek f4c9f8b3c2 Added text rendering tests 2021-09-13 20:30:18 +02:00
Marcin Ziąbek ae7873680d Fixed layout rendering 2021-09-13 20:30:16 +02:00
Marcin Ziąbek 175db288f6 Improved text measuring performance 2021-09-13 20:29:53 +02:00
Marcin Ziąbek 22401be791 Fixed rendering spaces, fixed text alignment 2021-09-13 20:29:53 +02:00
Marcin Ziąbek e2774ef347 Improved book layout in benchmark 2021-09-13 20:29:53 +02:00
Marcin Ziąbek 99f004dd85 Improved text rendering (space corner cases) 2021-09-13 20:29:53 +02:00
Marcin Ziąbek 8ada092531 Fixed text rendering (spacing corner case) 2021-09-13 20:29:53 +02:00
Marcin Ziąbek c334390af0 Better new line handling 2021-09-13 20:29:53 +02:00
Marcin Ziąbek deed219a0a Code structure improvements. Text rendering: support for words longer than available space 2021-09-13 20:29:53 +02:00
Marcin Ziąbek bb8e590fc6 Code structure improvements 2021-09-13 20:29:53 +02:00
Marcin Ziąbek a08a7b85c9 Example report fixes 2021-09-13 20:29:52 +02:00
Marcin Ziąbek 755447e1fd Text: rendering custom elements inside text 2021-09-13 20:29:52 +02:00
Marcin Ziąbek b66a53a189 Text component: internal and external links 2021-09-13 20:29:52 +02:00
Marcin Ziąbek 6d41ac9b9a Fix: the Stack may not show last an element in certain scenarios 2021-09-13 20:29:52 +02:00
Marcin Ziąbek a124d23862 Code cleanup 2021-09-13 20:29:50 +02:00
Marcin Ziąbek 5f65b2daed Text Rendering Optimization: Caching Measurement Results 2021-09-13 20:29:44 +02:00
Marcin Ziąbek 07938a1510 Text rendering optimization: style key, font metrics caching 2021-09-13 20:29:34 +02:00
Marcin Ziąbek f17860f519 Implemented text rendering benchmark 2021-09-13 20:29:24 +02:00
Marcin Ziąbek 6023b4de34 Improved text rendering capabilities 2021-09-13 20:29:17 +02:00
Marcin Ziąbek 748b8f65f5 Initial text rendering improvements 2021-09-13 20:29:17 +02:00
Marcin Ziąbek feab280a27 Fixed detecting infinite layout exception in specific cases 2021-09-13 01:09:45 +02:00
Marcin Ziąbek ae32511172 Added text rendering tests 2021-09-13 01:06:06 +02:00
Marcin Ziąbek 6c4da7285a Fixed layout rendering 2021-09-13 00:55:21 +02:00
Marcin Ziąbek 070b6a20bd Improved text measuring performance 2021-09-12 21:37:46 +02:00
Marcin Ziąbek f7cfdb763f Fixed rendering spaces, fixed text alignment 2021-09-12 21:01:10 +02:00
Marcin Ziąbek 5975b19445 Improved book layout in benchmark 2021-09-07 13:34:04 +02:00
Marcin Ziąbek fb7743e828 Improved text rendering (space corner cases) 2021-09-07 13:16:34 +02:00
Marcin Ziąbek 4152a0f701 Improved row performance and layout structure stability 2021-09-02 00:53:50 +02:00
Marcin Ziąbek 6c80c59996 Fixed misspelling in release notes 2021-09-01 19:19:16 +02:00
Marcin Ziąbek 08a37666fe 2021.9.1 2021-09-01 19:14:52 +02:00
Marcin Ziąbek c2fb68562d 2021.9.0 2021-08-30 23:37:44 +02:00
Marcin Ziąbek 683ab362d8 Added support for registering font types based on the suggestion of brendonparker 2021-08-29 20:15:12 +02:00
Marcin Ziąbek 3bb46ecc42 Fixed text rendering (spacing corner case) 2021-08-29 19:18:58 +02:00
Marcin Ziąbek 3112ee10cf Better new line handling 2021-08-27 20:25:20 +02:00
Marcin Ziąbek 899c138842 Code structure improvements. Text rendering: support for words longer than available space 2021-08-27 19:51:28 +02:00
Marcin Ziąbek 5177dcfd2b Code structure improvements 2021-08-27 12:40:06 +02:00
Marcin Ziąbek 4e6415aee2 Example report fixes 2021-08-27 11:40:33 +02:00
Marcin Ziąbek d9b3f016e8 Text: rendering custom elements inside text 2021-08-27 01:55:20 +02:00
Marcin Ziąbek b9cfaf3846 Text component: internal and external links 2021-08-27 01:38:26 +02:00
Marcin Ziąbek e3e7a22af8 Fix: the Stack may not show last an element in certain scenarios 2021-08-27 01:38:10 +02:00
Marcin Ziąbek c938ea32b0 Code cleanup 2021-08-27 01:10:30 +02:00
Marcin Ziąbek 69419c8440 Text Rendering Optimization: Caching Measurement Results 2021-08-27 00:29:04 +02:00
Marcin Ziąbek 23e8aa3491 Text rendering optimization: style key, font metrics caching 2021-08-26 23:36:43 +02:00
Marcin Ziąbek fd5d51e764 Implemented text rendering benchmark 2021-08-26 22:25:33 +02:00
Marcin Ziąbek dbc479db07 Improved text rendering capabilities 2021-08-25 03:40:16 +02:00
Marcin Ziąbek 04af33fc31 Included examples for DSL and complex layouts 2021-08-23 23:51:42 +02:00
Marcin Ziąbek edc595d199 Initial text rendering improvements 2021-08-09 22:35:39 +02:00
109 changed files with 12096 additions and 744 deletions
+33
View File
@@ -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));
});
}
}
}
+32
View File
@@ -0,0 +1,32 @@
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
[TestFixture]
public class BarcodeExamples
{
[Test]
public void Barcode()
{
RenderingTest
.Create()
.PageSize(300, 300)
.FileName()
.Render(container =>
{
container
.Background("#FFF")
.Padding(25)
.Stack(stack =>
{
stack.Item().Border(1).Background(Colors.Grey.Lighten3).Padding(5).Text("Barcode Example");
stack.Item().Border(1).Padding(5).AlignCenter().Text("*123456789*", TextStyle.Default.FontType("CarolinaBar-Demo-25E2").Size(20));
});
});
}
}
}
+46
View File
@@ -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);
}
}
}
+119 -18
View File
@@ -1,4 +1,3 @@
using System;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
@@ -296,7 +295,7 @@ namespace QuestPDF.Examples
layers
.Layer()
.AlignBottom()
.PageNumber("Page {pdf:currentPage}", TextStyle.Default.Size(16).Color(Colors.Green.Medium));
.Text(text => text.CurrentPageNumber(TextStyle.Default.Size(16).Color(Colors.Green.Medium)));
});
});
}
@@ -502,25 +501,49 @@ namespace QuestPDF.Examples
{
RenderingTest
.Create()
.PageSize(400, 250)
.PageSize(300, 175)
.FileName()
.Render(container =>
{
container
.Padding(25)
.Stack(stack =>
.Background(Colors.White)
.Padding(10)
.Decoration(decoration =>
{
var scales = new[] { 0.75f, 1f, 1.25f, 1.5f };
var headerFontStyle = TextStyle
.Default
.Size(20)
.Color(Colors.Blue.Darken2)
.SemiBold();
decoration
.Header()
.PaddingBottom(10)
.Text("Example: scale component", headerFontStyle);
decoration
.Content()
.Stack(stack =>
{
var scales = new[] { 0.8f, 0.9f, 1.1f, 1.2f };
foreach (var scale in scales)
{
stack
.Item()
.Border(1)
.Scale(scale)
.Padding(10)
.Text($"Content with {scale} scale.", TextStyle.Default.Size(20));
}
foreach (var scale in scales)
{
var fontColor = scale <= 1f
? Colors.Red.Lighten4
: Colors.Green.Lighten4;
var fontStyle = TextStyle.Default.Size(16);
stack
.Item()
.Border(1)
.Background(fontColor)
.Scale(scale)
.Padding(5)
.Text($"Content with {scale} scale.", fontStyle);
}
});
});
});
}
@@ -558,7 +581,7 @@ namespace QuestPDF.Examples
{
RenderingTest
.Create()
.PageSize(350, 350)
.PageSize(650, 450)
.FileName()
.Render(container =>
{
@@ -572,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 =>
@@ -755,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));
}
});
});
}
}
}
+51 -8
View File
@@ -1,18 +1,24 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using QuestPDF.Drawing;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples.Engine
{
public enum RenderingTestResult
{
Pdf,
Images
}
public class RenderingTest
{
private string FileNamePrefix = "test";
private Size Size { get; set; }
private bool ShowResult { get; set; }
private RenderingTestResult ResultType { get; set; } = RenderingTestResult.Images;
private RenderingTest()
{
@@ -30,9 +36,32 @@ namespace QuestPDF.Examples.Engine
return this;
}
public RenderingTest PageSize(Size size)
{
Size = size;
return this;
}
public RenderingTest PageSize(int width, int height)
{
Size = new Size(width, height);
return PageSize(new Size(width, height));
}
public RenderingTest ProducePdf()
{
ResultType = RenderingTestResult.Pdf;
return this;
}
public RenderingTest ProduceImages()
{
ResultType = RenderingTestResult.Images;
return this;
}
public RenderingTest ShowResults()
{
ShowResult = true;
return this;
}
@@ -40,13 +69,27 @@ namespace QuestPDF.Examples.Engine
{
var container = new Container();
content(container);
Func<int, string> fileNameSchema = i => $"{FileNamePrefix}-${i}.png";
var document = new SimpleDocument(container, Size);
document.GenerateImages(fileNameSchema);
var maxPages = ResultType == RenderingTestResult.Pdf ? 1000 : 10;
var document = new SimpleDocument(container, Size, maxPages);
Process.Start("explorer", fileNameSchema(0));
if (ResultType == RenderingTestResult.Images)
{
Func<int, string> fileNameSchema = i => $"{FileNamePrefix}-${i}.png";
document.GenerateImages(fileNameSchema);
if (ShowResult)
Process.Start("explorer", fileNameSchema(0));
}
if (ResultType == RenderingTestResult.Pdf)
{
var fileName = $"{FileNamePrefix}.pdf";
document.GeneratePdf(fileName);
if (ShowResult)
Process.Start("explorer", fileName);
}
}
}
}
+4 -2
View File
@@ -12,11 +12,13 @@ namespace QuestPDF.Examples.Engine
private IContainer Container { get; }
private Size Size { get; }
private int MaxPages { get; }
public SimpleDocument(IContainer container, Size size)
public SimpleDocument(IContainer container, Size size, int maxPages)
{
Container = container;
Size = size;
MaxPages = maxPages;
}
public DocumentMetadata GetMetadata()
@@ -24,7 +26,7 @@ namespace QuestPDF.Examples.Engine
return new DocumentMetadata()
{
RasterDpi = PageSizes.PointsPerInch * ImageScalingFactor,
DocumentLayoutExceptionThreshold = 10
DocumentLayoutExceptionThreshold = MaxPages
};
}
+5 -4
View File
@@ -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.
+4 -2
View File
@@ -12,8 +12,9 @@ namespace QuestPDF.Examples
{
RenderingTest
.Create()
.PageSize(200, 150)
.PageSize(300, 300)
.FileName()
.ShowResults()
.Render(container =>
{
container
@@ -50,7 +51,8 @@ namespace QuestPDF.Examples
.Background("FFF")
.Padding(5)
.Text("Sample text", TextStyle.Default.FontType("Segoe UI emoji").Alignment(HorizontalAlignment.Center));
.AlignCenter()
.Text("Sample text", TextStyle.Default.FontType("Segoe UI emoji"));
});
}
+12 -3
View File
@@ -6,14 +6,23 @@
</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>
<ProjectReference Include="..\QuestPDF\QuestPDF.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="quo-vadis.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="LibreBarcode39-Regular.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
+228
View File
@@ -0,0 +1,228 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class TextBenchmark
{
[Test]
public void Generate()
{
var chapters = GetBookChapters().ToList();
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.FileName()
.ProducePdf()
.ShowResults()
.Render(x => ComposeBook(x, chapters));
}
[Test]
public void Benchmark()
{
var chapters = GetBookChapters().ToList();
var results = PerformTest(16).ToList();
Console.WriteLine($"Min: {results.Min():F}");
Console.WriteLine($"Max: {results.Max():F}");
Console.WriteLine($"Avg: {results.Average():F}");
void GenerateDocument()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.FileName()
.ProducePdf()
.Render(x => ComposeBook(x, chapters));
}
IEnumerable<float> PerformTest(int attempts)
{
foreach (var i in Enumerable.Range(0, attempts))
{
var timer = new Stopwatch();
timer.Start();
GenerateDocument();
timer.Stop();
Console.WriteLine($"Attempt {i}: {timer.ElapsedMilliseconds:F}");
yield return timer.ElapsedMilliseconds;
}
}
}
class BookChapter
{
public string Title { get; set; }
public string Content { get; set; }
}
private static IEnumerable<BookChapter> GetBookChapters()
{
var book = File.ReadAllLines("quo-vadis.txt");
var chapterPointers = book
.Select((line, index) => new
{
LineNumber = index,
Text = line
})
.Where(x => x.Text.Length < 50 && x.Text.Contains("Rozdział") || x.Text.Contains("-----"))
.Select(x => x.LineNumber)
.ToList();
foreach (var index in Enumerable.Range(0, chapterPointers.Count - 1))
{
var chapter = chapterPointers[index];
var title = book[chapter];
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 content = string.Join(Environment.NewLine, lines);
yield return new BookChapter
{
Title = title,
Content = content
};
}
}
private void ComposeBook(IContainer container, ICollection<BookChapter> chapters)
{
var subtitleStyle = TextStyle.Default.Size(24).SemiBold().Color(Colors.Blue.Medium);
var normalStyle = TextStyle.Default.Size(14);
ComposePage(container);
void ComposePage(IContainer container)
{
container
.Padding(50)
.Decoration(decoration =>
{
decoration
.Content()
.Stack(stack =>
{
stack.Item().Element(Title);
stack.Item().PageBreak();
stack.Item().Element(TableOfContents);
stack.Item().PageBreak();
Chapters(stack);
stack.Item().Element(Acknowledgements);
});
decoration.Footer().Element(Footer);
});
}
void Title(IContainer container)
{
container
.Extend()
.PaddingBottom(200)
.AlignBottom()
.Stack(stack =>
{
stack.Item().Text("Quo Vadis", TextStyle.Default.Size(72).Bold().Color(Colors.Blue.Darken2));
stack.Item().Text("Henryk Sienkiewicz", TextStyle.Default.Size(24).Color(Colors.Grey.Darken2));
});
}
void TableOfContents(IContainer container)
{
container.Stack(stack =>
{
SectionTitle(stack, "Spis treści");
foreach (var chapter in chapters)
{
stack.Item().InternalLink(chapter.Title).Row(row =>
{
row.RelativeColumn().Text(chapter.Title, normalStyle);
row.ConstantColumn(100).AlignRight().Text(text => text.PageNumberOfLocation(chapter.Title, normalStyle));
});
}
});
}
void Chapters(StackDescriptor stack)
{
foreach (var chapter in chapters)
{
stack.Item().Element(container => Chapter(container, chapter.Title, chapter.Content));
}
}
void Chapter(IContainer container, string title, string content)
{
container.Stack(stack =>
{
SectionTitle(stack, title);
stack.Item().Text(text =>
{
text.ParagraphSpacing(5);
text.Span(content, normalStyle);
});
stack.Item().PageBreak();
});
}
void Acknowledgements(IContainer container)
{
container.Stack(stack =>
{
SectionTitle(stack, "Podziękowania");
stack.Item().Text(text =>
{
text.DefaultTextStyle(normalStyle);
text.Span("Ten dokument został wygenerowany na podstawie książki w formacie TXT opublikowanej w serwisie ");
text.ExternalLocation("wolnelektury.pl", "https://wolnelektury.pl/", normalStyle.Color(Colors.Blue.Medium).Underline());
text.Span(". Dziękuję za wspieranie polskiego czytelnictwa!");
});
});
}
void SectionTitle(StackDescriptor stack, string text)
{
stack.Item().Location(text).Text(text, subtitleStyle);
stack.Item().PaddingTop(10).PaddingBottom(50).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).ExtendHorizontal();
}
void Footer(IContainer container)
{
container
.AlignCenter()
.Text(text =>
{
text.CurrentPageNumber();
text.Span(" / ");
text.TotalPages();
});
}
}
}
}
+277
View File
@@ -0,0 +1,277 @@
using System;
using System.Linq;
using System.Text;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class TextExamples
{
[Test]
public void SimpleTextBlock()
{
RenderingTest
.Create()
.PageSize(500, 300)
.FileName()
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(5)
.Box()
.Border(1)
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.Size(20));
text.Span("This is a normal text, followed by an ");
text.Span("underlined red text.", TextStyle.Default.Size(20).Color(Colors.Red.Medium).Underline());
});
});
}
[Test]
public void ParagraphSpacing()
{
RenderingTest
.Create()
.PageSize(500, 300)
.FileName()
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(5)
.Box()
.Border(1)
.Padding(10)
.Text(text =>
{
text.ParagraphSpacing(10);
foreach (var i in Enumerable.Range(1, 3))
{
text.Span($"Paragraph {i}: ", TextStyle.Default.SemiBold());
text.Line(Placeholders.Paragraph());
}
});
});
}
[Test]
public void CustomElement()
{
RenderingTest
.Create()
.PageSize(500, 200)
.FileName()
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(5)
.Box()
.Border(1)
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.Size(20));
text.Span("This is a random image aligned to the baseline: ");
text.Element()
.PaddingBottom(-6)
.Height(24)
.Width(48)
.Image(Placeholders.Image);
text.Span(".");
});
});
}
[Test]
public void TextElements()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.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.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);
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.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();
}
});
});
}
}
}
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -18,8 +18,8 @@ namespace QuestPDF.ReportSample
HeaderFields = HeaderFields(),
LogoData = Helpers.GetImage("Logo.png"),
Sections = Enumerable.Range(0, 50).Select(x => GenerateSection()).ToList(),
Photos = Enumerable.Range(0, 30).Select(x => GetReportPhotos()).ToList()
Sections = Enumerable.Range(0, 40).Select(x => GenerateSection()).ToList(),
Photos = Enumerable.Range(0, 25).Select(x => GetReportPhotos()).ToList()
};
List<ReportHeaderField> HeaderFields()
@@ -28,13 +28,13 @@ namespace QuestPDF.ReportSample.Layouts
{
foreach (var part in Model.Parts)
{
stack.Item().Row(row =>
stack.Item().EnsureSpace(25).Row(row =>
{
row.ConstantColumn(150).LabelCell().Text(part.Label, Typography.Normal);
var frame = row.RelativeColumn().ValueCell();
if (part is ReportSectionText text)
frame.Text(text.Text, Typography.Normal);
frame.ShowEntire().Text(text.Text, Typography.Normal);
if (part is ReportSectionMap map)
frame.Element(x => MapElement(x, map));
@@ -34,7 +34,15 @@ namespace QuestPDF.ReportSample.Layouts
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().PageNumber();
page.Footer().AlignCenter().Text(text =>
{
text.DefaultTextStyle(Typography.Normal);
text.CurrentPageNumber();
text.Span(" / ");
text.TotalPages();
});
});
}
@@ -59,10 +67,10 @@ namespace QuestPDF.ReportSample.Layouts
foreach (var field in Model.HeaderFields)
{
grid.Item().Stack(row =>
{
row.Item().AlignLeft().Text(field.Label, Typography.Normal.SemiBold());
row.Item().Text(field.Value, Typography.Normal);
grid.Item().Text(text =>
{
text.Span($"{field.Label}: ", Typography.Normal.SemiBold());
text.Span(field.Value, Typography.Normal);
});
}
});
@@ -77,6 +85,8 @@ namespace QuestPDF.ReportSample.Layouts
stack.Item().Component(new TableOfContentsTemplate(Model.Sections));
stack.Item().PageBreak();
foreach (var section in Model.Sections)
stack.Item().Location(section.Title).Component(new SectionTemplate(section));
@@ -43,7 +43,7 @@ namespace QuestPDF.ReportSample.Layouts
{
row.ConstantColumn(25).Text($"{number}.", Typography.Normal);
row.RelativeColumn().Text(locationName, Typography.Normal);
row.ConstantColumn(150).AlignRight().PageNumber($"Page {{pdf:{locationName}}}", Typography.Normal.AlignRight());
row.ConstantColumn(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName, Typography.Normal));
});
}
}
@@ -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>
+3 -3
View File
@@ -28,7 +28,7 @@ namespace QuestPDF.ReportSample
// target document length should be around 100 pages
// test size
const int testSize = 100;
const int testSize = 10;
const decimal performanceTarget = 1; // documents per second
// create report models
@@ -57,8 +57,8 @@ namespace QuestPDF.ReportSample
Console.WriteLine($"Time per document: {performance:N} ms");
Console.WriteLine($"Documents per second: {speed:N} d/s");
if (speed < performanceTarget)
throw new Exception("Rendering algorithm is too slow.");
//if (speed < performanceTarget)
// throw new Exception("Rendering algorithm is too slow.");
}
}
}
+1 -1
View File
@@ -8,6 +8,6 @@ namespace QuestPDF.ReportSample
{
public static TextStyle Title => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Darken3).Size(26).Black();
public static TextStyle Headline => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Medium).Size(16).SemiBold();
public static TextStyle Normal => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Black).Size(11).LineHeight(1.25f).AlignLeft();
public static TextStyle Normal => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Black).Size(11).LineHeight(1.1f);
}
}
+1 -5
View File
@@ -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()
{
-6
View File
@@ -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()
{
+2 -2
View File
@@ -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()
{
+2 -2
View File
@@ -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()
{
+56
View File
@@ -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();
}
}
}
-6
View File
@@ -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()
{
-10
View File
@@ -1,10 +0,0 @@
using NUnit.Framework;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class DebugTests
{
}
}
+3
View File
@@ -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>();
}
}
+3 -6
View File
@@ -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>();
}
}
+15
View File
@@ -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
}
}
+27 -28
View File
@@ -1,5 +1,4 @@
using FluentAssertions;
using FluentAssertions.Equivalency;
using NUnit.Framework;
using QuestPDF.Elements;
using QuestPDF.Fluent;
@@ -40,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]
@@ -94,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]
@@ -151,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
@@ -211,7 +210,7 @@ namespace QuestPDF.UnitTests
// assert
var expected = new Container();
expected.Container().Stack(stack =>
expected.Stack(stack =>
{
stack.Spacing(20);
@@ -220,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);
});
@@ -230,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);
});
@@ -239,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
-30
View File
@@ -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());
});
}
}
}
-6
View File
@@ -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()
{
+15
View File
@@ -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
}
}
+131
View File
@@ -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
}
}
-6
View File
@@ -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()
-10
View File
@@ -1,10 +0,0 @@
using NUnit.Framework;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class PageNumberTests
{
}
}
+4 -5
View File
@@ -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>
+31
View File
@@ -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();
}
}
}
+154 -3
View File
@@ -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
}
}
+152
View File
@@ -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
}
}
+3
View File
@@ -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>();
}
}
+2 -29
View File
@@ -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()
+162
View File
@@ -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
}
}
+79 -3
View File
@@ -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
}
}
@@ -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();
});
}
}
}
+85 -35
View File
@@ -1,9 +1,12 @@
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;
using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.TestEngine.Operations;
@@ -12,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; }
@@ -44,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");
@@ -66,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);
@@ -83,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);*/
}
};
}
@@ -113,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);*/
}
};
}
@@ -165,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)
@@ -175,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)
@@ -245,10 +254,51 @@ namespace QuestPDF.UnitTests.TestEngine
public static Element CreateUniqueElement()
{
return new Text
return new Constrained
{
Value = Guid.NewGuid().ToString("N")
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);
}
}
}
+22
View File
@@ -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());
}
}
}
-10
View File
@@ -1,10 +0,0 @@
using NUnit.Framework;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class TextTests
{
}
}
+32
View File
@@ -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();
}
}
}
+100
View File
@@ -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
}
}
-1
View File
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Drawing
+9 -5
View File
@@ -1,14 +1,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Drawing
{
@@ -60,7 +56,10 @@ namespace QuestPDF.Drawing
var spacePlan = content.Measure(Size.Max) as Size;
if (spacePlan == null)
break;
{
canvas.EndDocument();
ThrowLayoutException();
}
try
{
@@ -88,6 +87,11 @@ namespace QuestPDF.Drawing
}
canvas.EndDocument();
void ThrowLayoutException()
{
throw new DocumentLayoutException("Composed layout generates infinite document.");
}
}
}
}
-2
View File
@@ -1,6 +1,4 @@
using System;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Drawing
{
@@ -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
{
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,35 +38,30 @@ 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,
TextAlign = style.Alignment switch
{
HorizontalAlignment.Left => SKTextAlign.Left,
HorizontalAlignment.Center => SKTextAlign.Center,
HorizontalAlignment.Right => SKTextAlign.Right,
_ => SKTextAlign.Left
}
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 TextMeasurement BreakText(this TextStyle style, string text, float availableWidth)
internal static SKFontMetrics ToFontMetrics(this TextStyle style)
{
var index = (int)style.ToPaint().BreakText(text, availableWidth, out var width);
return new TextMeasurement()
{
LineIndex = index,
FragmentWidth = width
};
return FontMetrics.GetOrAdd(style.ToString(), key => style.ToPaint().FontMetrics);
}
}
}
+10
View File
@@ -66,6 +66,16 @@ namespace QuestPDF.Drawing
}
public void Rotate(float angle)
{
}
public void Scale(float scaleX, float scaleY)
{
}
#endregion
}
}
+10 -3
View File
@@ -1,6 +1,3 @@
using System;
using System.Collections.Generic;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
using SkiaSharp;
@@ -54,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);
}
}
}
+13
View File
@@ -0,0 +1,13 @@
namespace QuestPDF.Drawing.SpacePlan
{
internal class TextRender : FullRender
{
public float Ascent { get; set; }
public float Descent { get; set; }
public TextRender(float width, float height) : base(width, height)
{
}
}
}
+1 -1
View File
@@ -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;
+13 -1
View File
@@ -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;
}
}
}
}
+1 -2
View File
@@ -1,5 +1,4 @@
using System;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
-52
View File
@@ -1,52 +0,0 @@
using System;
using System.Text.RegularExpressions;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
using Size = QuestPDF.Infrastructure.Size;
namespace QuestPDF.Elements
{
internal class PageNumber : Element
{
public string TextFormat { get; set; } = "";
private Text TextElement { get; set; } = new Text();
public TextStyle? TextStyle
{
get => TextElement?.Style;
set => TextElement.Style = value;
}
internal override void HandleVisitor(Action<Element?> visit)
{
TextElement?.HandleVisitor(visit);
base.HandleVisitor(visit);
}
internal override ISpacePlan Measure(Size availableSpace)
{
TextElement.Value = GetText();
return TextElement.Measure(availableSpace);
}
internal override void Draw(Size availableSpace)
{
TextElement.Value = GetText();
TextElement.Draw(availableSpace);
}
private string GetText()
{
var result = TextFormat;
// replace known locations
foreach (var location in PageContext.GetRegisteredLocations())
result = result.Replace($"{{pdf:{location}}}", PageContext.GetLocationPage(location).ToString());
// placeholder unknown locations
result = Regex.Replace(result, @"{pdf:[ \w]+}", "123");
return result;
}
}
}
+3 -4
View File
@@ -18,16 +18,15 @@ namespace QuestPDF.Elements
{
container
.Background(Colors.Grey.Lighten2)
.Padding(5)
.AlignMiddle()
.AlignCenter()
.Padding(5)
.MaxHeight(32)
.Element(x =>
{
if (string.IsNullOrWhiteSpace(Text))
x.Image(ImageData, ImageScaling.FitArea);
x.MaxHeight(32).Image(ImageData, ImageScaling.FitArea);
else
x.Text(Text, TextStyle.Default.Size(14).SemiBold());
x.Text(Text, TextStyle.Default.Size(14));
});
}
}
+2 -9
View File
@@ -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 -45
View File
@@ -2,34 +2,35 @@
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Fluent;
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;
}
}
@@ -84,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)
+8 -13
View File
@@ -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());
}
}
}
+10 -15
View File
@@ -1,5 +1,4 @@
using System;
using System.Linq;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
@@ -34,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());
}
}
}
+1 -2
View File
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Fluent;
@@ -92,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)
-117
View File
@@ -1,117 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
using Size = QuestPDF.Infrastructure.Size;
namespace QuestPDF.Elements
{
internal class Text : Element
{
public string? Value { get; set; }
public TextStyle? Style { get; set; } = new TextStyle();
private float LineHeight => Style.Size * Style.LineHeight;
internal override ISpacePlan Measure(Size availableSpace)
{
var lines = BreakLines(availableSpace.Width);
var realWidth = lines
.Select(line => Style.BreakText(line, availableSpace.Width).FragmentWidth)
.DefaultIfEmpty(0)
.Max();
var realHeight = lines.Count * LineHeight;
if (realHeight > availableSpace.Height + Size.Epsilon)
return new Wrap();
return new FullRender(realWidth, realHeight);
}
internal override void Draw(Size availableSpace)
{
var lines = BreakLines(availableSpace.Width);
var offsetTop = 0f;
var offsetLeft = GetLeftOffset();
Canvas.Translate(new Position(0, Style.Size));
foreach (var line in lines)
{
Canvas.DrawText(line, new Position(offsetLeft, offsetTop), Style);
offsetTop += LineHeight;
}
Canvas.Translate(new Position(0, -Style.Size));
float GetLeftOffset()
{
return Style.Alignment switch
{
HorizontalAlignment.Left => 0,
HorizontalAlignment.Center => availableSpace.Width / 2,
HorizontalAlignment.Right => availableSpace.Width,
_ => throw new NotSupportedException()
};
}
}
#region Word Wrap
private List<string> BreakLines(float maxWidth)
{
var lines = new List<string> ();
var remainingText = Value.Trim();
while(true)
{
if (string.IsNullOrEmpty(remainingText))
break;
var breakPoint = BreakLinePoint(remainingText, maxWidth);
if (breakPoint == 0)
break;
var lastLine = remainingText.Substring(0, breakPoint).Trim();
lines.Add(lastLine);
remainingText = remainingText.Substring(breakPoint).Trim();
}
return lines;
}
private int BreakLinePoint(string text, float width)
{
var index = 0;
var lengthBreak = Style.BreakText(text, width).LineIndex;
while (index <= text.Length)
{
var next = text.IndexOfAny (new [] { ' ', '\n' }, index);
if (next <= 0)
return index == 0 || lengthBreak == text.Length ? lengthBreak : index;
if (next > lengthBreak)
return index;
if (text[next] == '\n')
return next;
index = next + 1;
}
return index;
}
#endregion
}
}
@@ -0,0 +1,16 @@
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Calculation
{
internal class TextDrawingRequest
{
public ICanvas Canvas { get; set; }
public IPageContext PageContext { get; set; }
public int StartIndex { get; set; }
public int EndIndex { get; set; }
public float TotalAscent { get; set; }
public Size TextSize { get; set; }
}
}
@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Elements.Text.Items;
namespace QuestPDF.Elements.Text.Calculation
{
internal class TextLine
{
public ICollection<TextLineElement> Elements { get; private set; }
public float TextHeight { get; private set; }
public float LineHeight { get; private set; }
public float Ascent { get; private set; }
public float Descent { get; private set; }
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)
};
}
}
}
@@ -0,0 +1,10 @@
using QuestPDF.Elements.Text.Items;
namespace QuestPDF.Elements.Text.Calculation
{
internal class TextLineElement
{
public ITextBlockItem Item { get; set; }
public TextMeasurementResult Measurement { get; set; }
}
}
@@ -0,0 +1,14 @@
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Calculation
{
internal class TextMeasurementRequest
{
public ICanvas Canvas { get; set; }
public IPageContext PageContext { get; set; }
public int StartIndex { get; set; }
public float AvailableWidth { get; set; }
public bool IsFirstLineElement { get; set; }
}
}
@@ -0,0 +1,22 @@
using System;
namespace QuestPDF.Elements.Text.Calculation
{
internal class TextMeasurementResult
{
public float Width { get; set; }
public float Height => Math.Abs(Descent) + Math.Abs(Ascent);
public float Ascent { get; set; }
public float Descent { get; set; }
public float LineHeight { get; set; }
public int StartIndex { get; set; }
public int EndIndex { get; set; }
public int NextIndex { get; set; }
public int TotalIndex { get; set; }
public bool IsLast => EndIndex == TotalIndex;
}
}
@@ -0,0 +1,10 @@
using QuestPDF.Elements.Text.Calculation;
namespace QuestPDF.Elements.Text.Items
{
internal interface ITextBlockItem
{
TextMeasurementResult? Measure(TextMeasurementRequest request);
void Draw(TextDrawingRequest request);
}
}
@@ -0,0 +1,48 @@
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements.Text.Calculation;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Items
{
internal class TextBlockElement : ITextBlockItem
{
public Element Element { get; set; } = Empty.Instance;
public TextMeasurementResult? Measure(TextMeasurementRequest request)
{
Element.HandleVisitor(x => (x as IStateResettable)?.ResetState());
Element.HandleVisitor(x => x.Initialize(request.PageContext, request.Canvas));
var measurement = Element.Measure(new Size(request.AvailableWidth, Size.Max.Height));
if (measurement is Wrap || measurement is PartialRender)
return null;
var elementSize = measurement as Size;
return new TextMeasurementResult
{
Width = elementSize.Width,
Ascent = -elementSize.Height,
Descent = 0,
LineHeight = 1,
StartIndex = 0,
EndIndex = 0,
TotalIndex = 0
};
}
public void Draw(TextDrawingRequest request)
{
Element.HandleVisitor(x => (x as IStateResettable)?.ResetState());
Element.HandleVisitor(x => x.Initialize(request.PageContext, request.Canvas));
request.Canvas.Translate(new Position(0, request.TotalAscent));
Element.Draw(new Size(request.TextSize.Width, -request.TotalAscent));
request.Canvas.Translate(new Position(0, -request.TotalAscent));
}
}
}
@@ -0,0 +1,35 @@
using QuestPDF.Elements.Text.Calculation;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Items
{
internal class TextBlockExternalLink : ITextBlockItem
{
public TextStyle Style { get; set; } = new TextStyle();
public string Text { get; set; }
public string Url { get; set; }
public TextMeasurementResult? Measure(TextMeasurementRequest request)
{
return GetItem().MeasureWithoutCache(request);
}
public void Draw(TextDrawingRequest request)
{
request.Canvas.Translate(new Position(0, request.TotalAscent));
request.Canvas.DrawExternalLink(Url, new Size(request.TextSize.Width, request.TextSize.Height));
request.Canvas.Translate(new Position(0, -request.TotalAscent));
GetItem().Draw(request);
}
private TextBlockSpan GetItem()
{
return new TextBlockSpan
{
Style = Style,
Text = Text
};
}
}
}
@@ -0,0 +1,35 @@
using QuestPDF.Elements.Text.Calculation;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Items
{
internal class TextBlockInternalLink : ITextBlockItem
{
public TextStyle Style { get; set; } = new TextStyle();
public string Text { get; set; }
public string LocationName { get; set; }
public TextMeasurementResult? Measure(TextMeasurementRequest request)
{
return GetItem().MeasureWithoutCache(request);
}
public void Draw(TextDrawingRequest request)
{
request.Canvas.Translate(new Position(0, request.TotalAscent));
request.Canvas.DrawLocationLink(LocationName, new Size(request.TextSize.Width, request.TextSize.Height));
request.Canvas.Translate(new Position(0, -request.TotalAscent));
GetItem().Draw(request);
}
private TextBlockSpan GetItem()
{
return new TextBlockSpan
{
Style = Style,
Text = Text
};
}
}
}
@@ -0,0 +1,36 @@
using QuestPDF.Elements.Text.Calculation;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Items
{
internal class TextBlockPageNumber : ITextBlockItem
{
public TextStyle Style { get; set; } = new TextStyle();
public string SlotName { get; set; }
public TextMeasurementResult? Measure(TextMeasurementRequest request)
{
return GetItem(request.PageContext).MeasureWithoutCache(request);
}
public void Draw(TextDrawingRequest request)
{
GetItem(request.PageContext).Draw(request);
}
private TextBlockSpan GetItem(IPageContext context)
{
var pageNumberPlaceholder = 123;
var pageNumber = context.GetRegisteredLocations().Contains(SlotName)
? context.GetLocationPage(SlotName)
: pageNumberPlaceholder;
return new TextBlockSpan
{
Style = Style,
Text = pageNumber.ToString()
};
}
}
}
@@ -0,0 +1,131 @@
using System.Collections.Generic;
using QuestPDF.Drawing;
using QuestPDF.Elements.Text.Calculation;
using QuestPDF.Infrastructure;
using Size = QuestPDF.Infrastructure.Size;
namespace QuestPDF.Elements.Text.Items
{
internal class TextBlockSpan : ITextBlockItem
{
public string Text { get; set; }
public TextStyle Style { get; set; } = new TextStyle();
private Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?> MeasureCache =
new Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?>();
public TextMeasurementResult? Measure(TextMeasurementRequest request)
{
var cacheKey = (request.StartIndex, request.AvailableWidth);
if (!MeasureCache.ContainsKey(cacheKey))
MeasureCache[cacheKey] = MeasureWithoutCache(request);
return MeasureCache[cacheKey];
}
internal TextMeasurementResult? MeasureWithoutCache(TextMeasurementRequest request)
{
const char space = ' ';
var paint = Style.ToPaint();
var fontMetrics = Style.ToFontMetrics();
var startIndex = request.StartIndex;
if (request.IsFirstLineElement)
{
while (startIndex + 1 < Text.Length && Text[startIndex] == space)
startIndex++;
}
if (Text.Length == 0)
{
return new TextMeasurementResult
{
Width = 0,
LineHeight = Style.LineHeight,
Ascent = fontMetrics.Ascent,
Descent = fontMetrics.Descent
};
}
// start breaking text from requested position
var text = Text.Substring(startIndex);
var textLength = (int)paint.BreakText(text, request.AvailableWidth);
if (textLength <= 0)
return null;
if (textLength < text.Length && text[textLength] == space)
textLength++;
// break text only on spaces
if (textLength < text.Length)
{
var lastSpaceIndex = text.Substring(0, textLength).LastIndexOf(space) - 1;
if (lastSpaceIndex <= 0)
{
if (!request.IsFirstLineElement)
return null;
}
else
{
textLength = lastSpaceIndex + 1;
}
}
text = text.Substring(0, textLength);
var endIndex = startIndex + textLength;
var nextIndex = endIndex;
while (nextIndex + 1 < Text.Length && Text[nextIndex] == space)
nextIndex++;
// measure final text
var width = paint.MeasureText(text);
return new TextMeasurementResult
{
Width = width,
Ascent = fontMetrics.Ascent,
Descent = fontMetrics.Descent,
LineHeight = Style.LineHeight,
StartIndex = startIndex,
EndIndex = endIndex,
NextIndex = nextIndex,
TotalIndex = Text.Length
};
}
public void Draw(TextDrawingRequest request)
{
var fontMetrics = Style.ToFontMetrics();
var text = Text.Substring(request.StartIndex, request.EndIndex - request.StartIndex);
request.Canvas.DrawRectangle(new Position(0, request.TotalAscent), new Size(request.TextSize.Width, request.TextSize.Height), Style.BackgroundColor);
request.Canvas.DrawText(text, Position.Zero, Style);
// draw underline
if (Style.HasUnderline && fontMetrics.UnderlinePosition.HasValue)
DrawLine(fontMetrics.UnderlinePosition.Value, fontMetrics.UnderlineThickness.Value);
// draw stroke
if (Style.HasStrikethrough && fontMetrics.StrikeoutPosition.HasValue)
DrawLine(fontMetrics.StrikeoutPosition.Value, fontMetrics.StrikeoutThickness.Value);
void DrawLine(float offset, float thickness)
{
request.Canvas.DrawRectangle(new Position(0, offset - thickness / 2f), new Size(request.TextSize.Width, thickness), Style.Color);
}
}
}
}
+199
View File
@@ -0,0 +1,199 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements.Text.Calculation;
using QuestPDF.Elements.Text.Items;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text
{
internal class TextBlock : Element, IStateResettable
{
public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
public List<ITextBlockItem> Children { get; set; } = new List<ITextBlockItem>();
public Queue<ITextBlockItem> RenderingQueue { get; set; }
public int CurrentElementIndex { get; set; }
public void ResetState()
{
RenderingQueue = new Queue<ITextBlockItem>(Children);
CurrentElementIndex = 0;
}
internal override ISpacePlan Measure(Size availableSpace)
{
if (!RenderingQueue.Any())
return new FullRender(Size.Zero);
var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height).ToList();
if (!lines.Any())
return new PartialRender(Size.Zero);
var width = lines.Max(x => x.Width);
var height = lines.Sum(x => x.LineHeight);
if (width > availableSpace.Width + Size.Epsilon || height > availableSpace.Height + Size.Epsilon)
return new Wrap();
var fullyRenderedItemsCount = lines
.SelectMany(x => x.Elements)
.GroupBy(x => x.Item)
.Count(x => x.Any(y => y.Measurement.IsLast));
if (fullyRenderedItemsCount == RenderingQueue.Count)
return new FullRender(width, height);
return new PartialRender(width, height);
}
internal override void Draw(Size availableSpace)
{
var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height).ToList();
if (!lines.Any())
return;
var heightOffset = 0f;
var widthOffset = 0f;
foreach (var line in lines)
{
widthOffset = 0f;
var alignmentOffset = GetAlignmentOffset(line.Width);
Canvas.Translate(new Position(alignmentOffset, 0));
Canvas.Translate(new Position(0, -line.Ascent));
foreach (var item in line.Elements)
{
var textDrawingRequest = new TextDrawingRequest
{
Canvas = Canvas,
PageContext = PageContext,
StartIndex = item.Measurement.StartIndex,
EndIndex = item.Measurement.EndIndex,
TextSize = new Size(item.Measurement.Width, line.LineHeight),
TotalAscent = line.Ascent
};
item.Item.Draw(textDrawingRequest);
Canvas.Translate(new Position(item.Measurement.Width, 0));
widthOffset += item.Measurement.Width;
}
Canvas.Translate(new Position(-alignmentOffset, 0));
Canvas.Translate(new Position(-line.Width, line.Ascent));
Canvas.Translate(new Position(0, line.LineHeight));
heightOffset += line.LineHeight;
}
Canvas.Translate(new Position(0, -heightOffset));
lines
.SelectMany(x => x.Elements)
.GroupBy(x => x.Item)
.Where(x => x.Any(y => y.Measurement.IsLast))
.Select(x => x.Key)
.ToList()
.ForEach(x => RenderingQueue.Dequeue());
var lastElementMeasurement = lines.Last().Elements.Last().Measurement;
CurrentElementIndex = lastElementMeasurement.IsLast ? 0 : lastElementMeasurement.NextIndex;
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)
{
var queue = new Queue<ITextBlockItem>(RenderingQueue);
var currentItemIndex = CurrentElementIndex;
var currentHeight = 0f;
while (queue.Any())
{
var line = GetNextLine();
if (!line.Elements.Any())
yield break;
if (currentHeight + line.LineHeight > availableHeight + Size.Epsilon)
yield break;
currentHeight += line.LineHeight;
yield return line;
}
TextLine GetNextLine()
{
var currentWidth = 0f;
var currentLineElements = new List<TextLineElement>();
while (true)
{
if (!queue.Any())
break;
var currentElement = queue.Peek();
var measurementRequest = new TextMeasurementRequest
{
Canvas = Canvas,
PageContext = PageContext,
StartIndex = currentItemIndex,
AvailableWidth = availableWidth - currentWidth,
IsFirstLineElement = !currentLineElements.Any()
};
var measurementResponse = currentElement.Measure(measurementRequest);
if (measurementResponse == null)
break;
currentLineElements.Add(new TextLineElement
{
Item = currentElement,
Measurement = measurementResponse
});
currentWidth += measurementResponse.Width;
currentItemIndex = measurementResponse.NextIndex;
if (!measurementResponse.IsLast)
break;
currentItemIndex = 0;
queue.Dequeue();
}
return TextLine.From(currentLineElements);
}
}
}
}
+3 -6
View File
@@ -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());
}
}
}
+1 -2
View File
@@ -1,5 +1,4 @@
using System;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
+4 -31
View File
@@ -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);
}
@@ -40,15 +40,6 @@ namespace QuestPDF.Fluent
{
return handler(parent.Container()).Container();
}
public static void PageNumber(this IContainer element, string textFormat = "{pdf:currentPage} / {pdf:totalPages}", TextStyle? style = null)
{
element.Element(new PageNumber
{
TextFormat = textFormat,
TextStyle = style ?? TextStyle.Default
});
}
public static IContainer AspectRatio(this IContainer element, float ratio, AspectRatioOption option = AspectRatioOption.FitWidth)
{
@@ -92,25 +83,7 @@ namespace QuestPDF.Fluent
MinHeight = minHeight
});
}
public static void Text(this IContainer element, object text, TextStyle? style = null)
{
text ??= string.Empty;
style ??= TextStyle.Default;
if (element is Alignment alignment)
{
style = style.Clone();
style.Alignment = alignment.Horizontal;
}
element.Element(new Text
{
Value = text.ToString(),
Style = style
});
}
public static void PageBreak(this IContainer element)
{
element.Element(new PageBreak());
+15 -5
View File
@@ -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)
-2
View File
@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
-2
View File
@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;

Some files were not shown because too many files have changed in this diff Show More