Compare commits

..

2 Commits

Author SHA1 Message Date
Marcin Ziąbek fc33056434 API improvements 2021-10-22 01:38:08 +02:00
Marcin Ziąbek d3e8386418 Dynamic element prototype 2021-10-19 01:40:56 +02:00
129 changed files with 1976 additions and 4715 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));
});
}
}
}
+12 -12
View File
@@ -1,6 +1,4 @@
using System.IO; using NUnit.Framework;
using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Examples.Engine; using QuestPDF.Examples.Engine;
using QuestPDF.Fluent; using QuestPDF.Fluent;
using QuestPDF.Helpers; using QuestPDF.Helpers;
@@ -8,24 +6,26 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Examples namespace QuestPDF.Examples
{ {
[TestFixture]
public class BarcodeExamples public class BarcodeExamples
{ {
[Test] [Test]
public void Example() public void Barcode()
{ {
FontManager.RegisterFontType("LibreBarcode39", File.OpenRead("LibreBarcode39-Regular.ttf"));
RenderingTest RenderingTest
.Create() .Create()
.PageSize(400, 100) .PageSize(300, 300)
.ShowResults() .FileName()
.Render(container => .Render(container =>
{ {
container container
.Background(Colors.White) .Background("#FFF")
.AlignCenter() .Padding(25)
.AlignMiddle() .Stack(stack =>
.Text("*QuestPDF*", TextStyle.Default.FontType("LibreBarcode39").Size(64)); {
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));
});
}); });
} }
} }
-57
View File
@@ -1,57 +0,0 @@
using Microcharts;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Examples
{
public class CanvasExamples
{
[Test]
public void BorderRadius()
{
RenderingTest
.Create()
.PageSize(175, 100)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Background(Colors.Grey.Lighten2)
.Padding(25)
.MinimalBox()
.Layers(layers =>
{
layers.Layer().Canvas((canvas, size) =>
{
DrawRoundedRectangle(Colors.White, false);
DrawRoundedRectangle(Colors.Blue.Darken2, true);
void DrawRoundedRectangle(string color, bool isStroke)
{
using var paint = new SKPaint
{
Color = SKColor.Parse(color),
IsStroke = isStroke,
StrokeWidth = 2,
IsAntialias = true
};
canvas.DrawRoundRect(0, 0, size.Width, size.Height, 20, 20, paint);
}
});
layers
.PrimaryLayer()
.PaddingVertical(10)
.PaddingHorizontal(20)
.Text("Sample text", TextStyle.Default.Size(16).Color(Colors.Blue.Darken2).SemiBold());
});
});
}
}
}
-85
View File
@@ -1,85 +0,0 @@
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using Microcharts;
using SkiaSharp;
namespace QuestPDF.Examples
{
public class ChartExample
{
[Test]
public void MicrochartChart()
{
var entries = new[]
{
new ChartEntry(212)
{
Label = "UWP",
ValueLabel = "112",
Color = SKColor.Parse("#2c3e50")
},
new ChartEntry(248)
{
Label = "Android",
ValueLabel = "648",
Color = SKColor.Parse("#77d065")
},
new ChartEntry(128)
{
Label = "iOS",
ValueLabel = "428",
Color = SKColor.Parse("#b455b6")
},
new ChartEntry(514)
{
Label = "Forms",
ValueLabel = "214",
Color = SKColor.Parse("#3498db")
}
};
RenderingTest
.Create()
.PageSize(400, 600)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Background(Colors.White)
.Padding(25)
.Column(column =>
{
column
.Item()
.PaddingBottom(10)
.Text("Chart example", TextStyle.Default.Size(20).SemiBold().Color(Colors.Blue.Medium));
column
.Item()
.Border(1)
.ExtendHorizontal()
.Height(300)
.Canvas((canvas, size) =>
{
var chart = new BarChart
{
Entries = entries,
LabelOrientation = Orientation.Horizontal,
ValueLabelOrientation = Orientation.Horizontal,
IsAnimated = false,
};
chart.DrawContent(canvas, (int)size.Width, (int)size.Height);
});
});
});
}
}
}
-37
View File
@@ -1,37 +0,0 @@
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class ColumnExamples
{
[Test]
public void Column()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ShowResults()
.ProducePdf()
.Render(container =>
{
container.Column(column =>
{
column.Item().Element(Block);
static void Block(IContainer container)
{
container
.Width(72)
.Height(3.5f)
.Height(1.5f)
.Background(Placeholders.BackgroundColor());
}
});
});
}
}
}
@@ -1,56 +0,0 @@
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class ComplexLayoutBenchmark
{
[Test]
public void ComplexLayout()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ProducePdf()
.ShowResults()
.Render(x => GenerateStructure(x, 16));
}
private void GenerateStructure(IContainer container, int level)
{
if (level <= 0)
{
container.Background(Placeholders.BackgroundColor()).Height(10);
return;
}
level--;
if (level % 3 == 0)
{
container
.Border(level / 4f)
.BorderColor(Colors.Black)
.Row(row =>
{
row.RelativeItem().Element(x => GenerateStructure(x, level));
row.RelativeItem().Element(x => GenerateStructure(x, level));
});
}
else
{
container
.Border(level / 4f)
.BorderColor(Colors.Black)
.Column(column =>
{
column.Item().Element(x => GenerateStructure(x, level));
column.Item().Element(x => GenerateStructure(x, level));
});
}
}
}
}
-61
View File
@@ -1,61 +0,0 @@
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
internal class MyComponent : IComponent
{
public ISlot Header { get; set; }
public ISlot<string> Content { get; set; }
public void Compose(IContainer container)
{
container
.Column(column =>
{
column.Item().Slot(Header);
foreach (var i in Enumerable.Range(1, 10))
column.Item().Slot(Content, i.ToString());
});
}
}
public class ComponentExamples
{
[Test]
public void ComplexLayout()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ProducePdf()
.ShowResults()
.Render(content =>
{
content
.Padding(10)
.Border(1)
.BorderColor(Colors.Grey.Medium)
.Component<MyComponent>(component =>
{
component
.Slot(x => x.Header)
.Text("This is my text");
component.Slot(x => x.Content, (input, container) =>
{
container
.Background(Placeholders.BackgroundColor())
.Padding(5)
.Text(input);
});
});
});
}
}
}
+2 -2
View File
@@ -22,10 +22,10 @@ namespace QuestPDF.Examples
page.Header().Text("Header"); page.Header().Text("Header");
page.Content().PaddingVertical(10).Border(1).Padding(10).Column(column => page.Content().PaddingVertical(10).Border(1).Padding(10).Stack(stack =>
{ {
foreach (var index in Enumerable.Range(1, 100)) foreach (var index in Enumerable.Range(1, 100))
column.Item().Text($"Line {index}", TextStyle.Default.Color(Placeholders.Color())); stack.Item().Text($"Line {index}", TextStyle.Default.Color(Placeholders.Color()));
}); });
page.Footer().Text("Footer"); page.Footer().Text("Footer");
+18 -61
View File
@@ -1,5 +1,4 @@
using NUnit.Framework; using NUnit.Framework;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Examples.Engine; using QuestPDF.Examples.Engine;
using QuestPDF.Fluent; using QuestPDF.Fluent;
using QuestPDF.Helpers; using QuestPDF.Helpers;
@@ -9,68 +8,26 @@ namespace QuestPDF.Examples
public class DebuggingTesting public class DebuggingTesting
{ {
[Test] [Test]
public void Column() public void Stack()
{ {
Assert.Throws<DocumentLayoutException>(() => RenderingTest
{ .Create()
RenderingTest .PageSize(500, 360)
.Create() .FileName()
.PageSize(500, 360) .Render(container =>
.Render(container => {
{ container
container .Background(Colors.White)
.Padding(10) .Padding(15)
.Width(100) .Grid(grid =>
.Background(Colors.Grey.Lighten3) {
.DebugPointer("Example debug pointer") grid.Spacing(15);
.Column(x =>
{
x.Item().Text("Test");
x.Item().Width(150);
});
});
});
}
[Test] grid.Item().Background(Colors.Grey.Medium).Text(Placeholders.LoremIpsum());
public void Simple() grid.Item().DebugPointer("Test").Background(Colors.Grey.Lighten1).Height(1000); // <-- problem
{ grid.Item().Background(Colors.Grey.Lighten2).Height(150);
Assert.Throws<DocumentLayoutException>(() => });
{ });
RenderingTest
.Create()
.PageSize(500, 360)
.Render(container =>
{
container
.Padding(10)
.Width(100)
.Background(Colors.Grey.Lighten3)
.Width(150)
.Text("Test");
});
});
}
[Test]
public void DebugPointer()
{
Assert.Throws<DocumentLayoutException>(() =>
{
RenderingTest
.Create()
.PageSize(500, 360)
.Render(container =>
{
container
.Background(Colors.Grey.Lighten3)
.Padding(10)
.Width(100)
.DebugPointer("Example debug pointer")
.Width(150)
.Text("Example");
});
});
} }
} }
} }
@@ -1,48 +0,0 @@
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class DefaultTextStyleExample
{
[Test]
public void DefaultTextStyle()
{
RenderingTest
.Create()
.ProducePdf()
.ShowResults()
.RenderDocument(container =>
{
container.Page(page =>
{
// all text in this set of pages has size 20
page.DefaultTextStyle(TextStyle.Default.Size(20));
page.Margin(20);
page.Size(PageSizes.A4);
page.Background(Colors.White);
page.Content().Column(column =>
{
column.Item().Text(Placeholders.Sentence());
column.Item().Text(text =>
{
// text in this block is additionally semibold
text.DefaultTextStyle(TextStyle.Default.SemiBold());
text.Line(Placeholders.Sentence());
// this text has size 20 but also semibold and red
text.Span(Placeholders.Sentence(), TextStyle.Default.Color(Colors.Red.Medium));
});
});
});
});
}
}
}
@@ -1,53 +0,0 @@
using System.Drawing;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class DefaultTextStyleExamples
{
[Test]
public void Placeholder()
{
RenderingTest
.Create()
.PageSize(220, 270)
.ProduceImages()
.ShowResults()
.EnableDebugging()
.Render(container =>
{
container
.Padding(10)
.DefaultTextStyle(TextStyle.Default.Bold().Underline())
.Column(column =>
{
column.Item().Text("Default style applies to all children", TextStyle.Default);
column.Item().Text("You can override certain styles", TextStyle.Default.Underline(false).Color(Colors.Green.Darken2));
column.Item().PaddingTop(10).Border(1).Grid(grid =>
{
grid.Columns(4);
foreach (var i in Enumerable.Range(1, 16))
{
grid.Item()
.Border(1)
.BorderColor(Colors.Grey.Lighten1)
.Background(Colors.Grey.Lighten3)
.Width(50)
.Height(50)
.AlignCenter()
.AlignMiddle()
.Text(i, TextStyle.Default.Size(16 + i / 4));
}
});
});
});
}
}
}
@@ -1,55 +0,0 @@
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class DifferentHeaderOnFirstPageExample
{
[Test]
public void Placeholder()
{
RenderingTest
.Create()
.PageSize(PageSizes.A6)
.ProduceImages()
.ShowResults()
.EnableDebugging()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A6);
page.Margin(5);
page.Background(Colors.White);
page.Header().Column(column =>
{
column.Item().ShowOnce().Background(Colors.Blue.Lighten2).Height(60);
column.Item().SkipOnce().Background(Colors.Green.Lighten2).Height(40);
});
page.Content().PaddingVertical(10).Column(column =>
{
column.Spacing(10);
foreach (var _ in Enumerable.Range(0, 13))
column.Item().Background(Colors.Grey.Lighten2).Height(40);
});
page.Footer().AlignCenter().Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.Size(16));
text.CurrentPageNumber();
text.Span(" / ");
text.TotalPages();
});
});
});
}
}
}
+170
View File
@@ -0,0 +1,170 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Elements;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class OrderItem
{
public string ItemName { get; set; } = Placeholders.Label();
public int Price { get; set; } = Placeholders.Random.Next(1, 11) * 10;
public int Count { get; set; } = Placeholders.Random.Next(1, 11);
}
public class OrdersTable : IDynamicComponent
{
private ICollection<OrderItem> Items { get; }
private ICollection<OrderItem> ItemsLeft { get; set; }
public OrdersTable(ICollection<OrderItem> items)
{
Items = items;
}
public void Compose(DynamicContext context, IDynamicContainer container)
{
if (context.Operation == DynamicLayoutOperation.Reset)
{
ItemsLeft = new List<OrderItem>(Items);
return;
}
var header = ComposeHeader(context);
var sampleFooter = ComposeFooter(context, Enumerable.Empty<OrderItem>());
var decorationHeight = header.Size.Height + sampleFooter.Size.Height;
var rows = GetItemsForPage(context, decorationHeight).ToList();
var footer = ComposeFooter(context, rows.Select(x => x.Item));
if (ItemsLeft.Count > rows.Count)
container.HasMoreContent();
if (context.Operation == DynamicLayoutOperation.Draw)
ItemsLeft = ItemsLeft.Skip(rows.Count).ToList();
container.Box().Decoration(decoration =>
{
decoration.Header().Element(header);
decoration.Content().Box().Stack(stack =>
{
foreach (var row in rows)
stack.Item().Element(row.Element);
});
decoration.Footer().Element(footer);
});
}
private IDynamicElement ComposeHeader(DynamicContext context)
{
return context.CreateElement(element =>
{
element
.BorderBottom(1)
.BorderColor(Colors.Grey.Darken2)
.Padding(5)
.Row(row =>
{
var textStyle = TextStyle.Default.SemiBold();
row.ConstantColumn(30).Text("#", textStyle);
row.RelativeColumn().Text("Item name", textStyle);
row.ConstantColumn(50).AlignRight().Text("Count", textStyle);
row.ConstantColumn(50).AlignRight().Text("Price", textStyle);
row.ConstantColumn(50).AlignRight().Text("Total", textStyle);
});
});
}
private IDynamicElement ComposeFooter(DynamicContext context, IEnumerable<OrderItem> items)
{
var total = items.Sum(x => x.Count * x.Price);
return context.CreateElement(element =>
{
element
.Padding(5)
.AlignRight()
.Text($"Subtotal: {total}$", TextStyle.Default.Size(14).SemiBold());
});
}
private IEnumerable<(OrderItem Item, IDynamicElement Element)> GetItemsForPage(DynamicContext context, float decorationHeight)
{
var totalHeight = decorationHeight;
var counter = Items.Count - ItemsLeft.Count + 1;
foreach (var orderItem in ItemsLeft)
{
var element = context.CreateElement(content =>
{
content
.BorderBottom(1)
.BorderColor(Colors.Grey.Lighten2)
.Padding(5)
.Row(row =>
{
row.ConstantColumn(30).Text(counter++);
row.RelativeColumn().Text(orderItem.ItemName);
row.ConstantColumn(50).AlignRight().Text(orderItem.Count);
row.ConstantColumn(50).AlignRight().Text($"{orderItem.Price}$");
row.ConstantColumn(50).AlignRight().Text($"{orderItem.Count*orderItem.Price}$");
});
});
var elementHeight = element.Size.Height;
if (totalHeight + elementHeight > context.AvailableSize.Height)
break;
totalHeight += elementHeight;
yield return (orderItem, element);
}
}
}
public static class DynamicExamples
{
[Test]
public static void Dynamic()
{
RenderingTest
.Create()
.PageSize(PageSizes.A5)
.FileName()
.ShowResults()
.Render(container =>
{
var items = Enumerable.Range(0, 25).Select(x => new OrderItem()).ToList();
container
.Background(Colors.White)
.Padding(25)
.Decoration(decoration =>
{
decoration
.Header()
.PaddingBottom(5)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.SemiBold().Color(Colors.Blue.Darken2).Size(16));
text.Span("Page ");
text.CurrentPageNumber();
text.Span(" of ");
text.TotalPages();
});
decoration
.Content()
.Dynamic(new OrdersTable(items));
});
});
}
}
}
+75 -50
View File
@@ -17,6 +17,7 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(200, 150) .PageSize(200, 150)
.FileName()
.Render(container => .Render(container =>
{ {
container container
@@ -32,6 +33,7 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(300, 300) .PageSize(300, 300)
.FileName()
.Render(container => .Render(container =>
{ {
container container
@@ -40,7 +42,7 @@ namespace QuestPDF.Examples
.Decoration(decoration => .Decoration(decoration =>
{ {
decoration decoration
.Before() .Header()
.Background(Colors.Grey.Medium) .Background(Colors.Grey.Medium)
.Padding(10) .Padding(10)
.Text("Notes", TextStyle.Default.Size(16).Color("#FFF")); .Text("Notes", TextStyle.Default.Size(16).Color("#FFF"));
@@ -61,32 +63,33 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(740, 200) .PageSize(740, 200)
.FileName()
.Render(container => .Render(container =>
{ {
container container
.Background("#FFF") .Background("#FFF")
.Padding(20) .Padding(20)
.Column(column => .Stack(stack =>
{ {
column.Item() stack.Item()
.PaddingBottom(10) .PaddingBottom(10)
.AlignCenter() .AlignCenter()
.Text("This Row element is 700pt wide"); .Text("This Row element is 700pt wide");
column.Item().Row(row => stack.Item().Row(row =>
{ {
row.ConstantItem(100) row.ConstantColumn(100)
.Background(Colors.Grey.Lighten1) .Background(Colors.Grey.Lighten1)
.Padding(10) .Padding(10)
.ExtendVertical() .ExtendVertical()
.Text("This column is 100 pt wide"); .Text("This column is 100 pt wide");
row.RelativeItem() row.RelativeColumn()
.Background(Colors.Grey.Lighten2) .Background(Colors.Grey.Lighten2)
.Padding(10) .Padding(10)
.Text("This column takes 1/3 of the available space (200pt)"); .Text("This column takes 1/3 of the available space (200pt)");
row.RelativeItem(2) row.RelativeColumn(2)
.Background(Colors.Grey.Lighten3) .Background(Colors.Grey.Lighten3)
.Padding(10) .Padding(10)
.Text("This column takes 2/3 of the available space (400pt)"); .Text("This column takes 2/3 of the available space (400pt)");
@@ -101,6 +104,7 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(740, 200) .PageSize(740, 200)
.FileName()
.Render(container => .Render(container =>
{ {
container container
@@ -109,31 +113,32 @@ namespace QuestPDF.Examples
.Row(row => .Row(row =>
{ {
row.Spacing(20); row.Spacing(20);
row.RelativeItem(2).Border(1).Background(Colors.Grey.Lighten1); row.RelativeColumn(2).Border(1).Background(Colors.Grey.Lighten1);
row.RelativeItem(3).Border(1).Background(Colors.Grey.Lighten2); row.RelativeColumn(3).Border(1).Background(Colors.Grey.Lighten2);
row.RelativeItem(4).Border(1).Background(Colors.Grey.Lighten3); row.RelativeColumn(4).Border(1).Background(Colors.Grey.Lighten3);
}); });
}); });
} }
[Test] [Test]
public void column() public void Stack()
{ {
RenderingTest RenderingTest
.Create() .Create()
.PageSize(500, 360) .PageSize(500, 360)
.FileName()
.Render(container => .Render(container =>
{ {
container container
.Background("#FFF") .Background("#FFF")
.Padding(15) .Padding(15)
.Column(column => .Stack(stack =>
{ {
column.Spacing(15); stack.Spacing(15);
column.Item().Background(Colors.Grey.Medium).Height(50); stack.Item().Background(Colors.Grey.Medium).Height(50);
column.Item().Background(Colors.Grey.Lighten1).Height(100); stack.Item().Background(Colors.Grey.Lighten1).Height(100);
column.Item().Background(Colors.Grey.Lighten2).Height(150); stack.Item().Background(Colors.Grey.Lighten2).Height(150);
}); });
}); });
} }
@@ -144,6 +149,7 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(210, 210) .PageSize(210, 210)
.FileName()
.Render(container => .Render(container =>
{ {
container container
@@ -166,6 +172,7 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(300, 200) .PageSize(300, 200)
.FileName()
.Render(container => .Render(container =>
{ {
var text = ""; var text = "";
@@ -188,6 +195,7 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(400, 230) .PageSize(400, 230)
.FileName()
.Render(container => .Render(container =>
{ {
var textStyle = TextStyle.Default.Size(14); var textStyle = TextStyle.Default.Size(14);
@@ -222,6 +230,7 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(300, 200) .PageSize(300, 200)
.FileName()
.Render(container => .Render(container =>
{ {
container container
@@ -251,6 +260,7 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(400, 250) .PageSize(400, 250)
.FileName()
.Render(container => .Render(container =>
{ {
container container
@@ -267,12 +277,12 @@ namespace QuestPDF.Examples
layers layers
.PrimaryLayer() .PrimaryLayer()
.Padding(25) .Padding(25)
.Column(column => .Stack(stack =>
{ {
column.Spacing(5); stack.Spacing(5);
foreach (var _ in Enumerable.Range(0, 7)) foreach (var _ in Enumerable.Range(0, 7))
column.Item().Text(Placeholders.Sentence()); stack.Item().Text(Placeholders.Sentence());
}); });
// layer above the main content // layer above the main content
@@ -304,16 +314,16 @@ namespace QuestPDF.Examples
// { // {
// page.Header().PageNumber("Page {pdf:currentPage}"); // page.Header().PageNumber("Page {pdf:currentPage}");
// //
// page.Content().Height(300).column(content => // page.Content().Height(300).Stack(content =>
// { // {
// content.Item().Height(200).Background(Colors.Grey.Lighten2); // content.Item().Height(200).Background(Colors.Grey.Lighten2);
// //
// content.Item().EnsureSpace(100).column(column => // content.Item().EnsureSpace(100).Stack(stack =>
// { // {
// column.Spacing(10); // stack.Spacing(10);
// //
// foreach (var _ in Enumerable.Range(0, 4)) // foreach (var _ in Enumerable.Range(0, 4))
// column.Item().Height(50).Background(Colors.Green.Lighten1); // stack.Item().Height(50).Background(Colors.Green.Lighten1);
// }); // });
// }); // });
// }); // });
@@ -326,6 +336,7 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(300, 300) .PageSize(300, 300)
.FileName()
.Render(container => .Render(container =>
{ {
container container
@@ -370,6 +381,7 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(450, 150) .PageSize(450, 150)
.FileName()
.Render(container => .Render(container =>
{ {
container container
@@ -378,7 +390,7 @@ namespace QuestPDF.Examples
.Row(row => .Row(row =>
{ {
foreach (var color in colors) foreach (var color in colors)
row.RelativeItem().Background(color); row.RelativeColumn().Background(color);
}); });
}); });
} }
@@ -402,6 +414,7 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(500, 175) .PageSize(500, 175)
.FileName()
.Render(container => .Render(container =>
{ {
container container
@@ -428,6 +441,7 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(300, 300) .PageSize(300, 300)
.FileName()
.Render(container => .Render(container =>
{ {
container container
@@ -437,10 +451,10 @@ namespace QuestPDF.Examples
{ {
layers.Layer().Text("Something else"); layers.Layer().Text("Something else");
layers.PrimaryLayer().Column(column => layers.PrimaryLayer().Stack(stack =>
{ {
column.Item().PaddingTop(20).Text("Text 1"); stack.Item().PaddingTop(20).Text("Text 1");
column.Item().PaddingTop(40).Text("Text 2"); stack.Item().PaddingTop(40).Text("Text 2");
}); });
layers.Layer().Canvas((canvas, size) => layers.Layer().Canvas((canvas, size) =>
@@ -467,6 +481,7 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(300, 150) .PageSize(300, 150)
.FileName()
.Render(container => .Render(container =>
{ {
container container
@@ -474,7 +489,7 @@ namespace QuestPDF.Examples
.Padding(15) .Padding(15)
.Border(4) .Border(4)
.BorderColor(Colors.Blue.Medium) .BorderColor(Colors.Blue.Medium)
//.MinimalBox() //.Box()
.Background(Colors.Grey.Lighten2) .Background(Colors.Grey.Lighten2)
.Padding(15) .Padding(15)
.Text("Test of the \n box element", TextStyle.Default.Size(20)); .Text("Test of the \n box element", TextStyle.Default.Size(20));
@@ -487,6 +502,7 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(300, 175) .PageSize(300, 175)
.FileName()
.Render(container => .Render(container =>
{ {
container container
@@ -501,13 +517,13 @@ namespace QuestPDF.Examples
.SemiBold(); .SemiBold();
decoration decoration
.Before() .Header()
.PaddingBottom(10) .PaddingBottom(10)
.Text("Example: scale component", headerFontStyle); .Text("Example: scale component", headerFontStyle);
decoration decoration
.Content() .Content()
.Column(column => .Stack(stack =>
{ {
var scales = new[] { 0.8f, 0.9f, 1.1f, 1.2f }; var scales = new[] { 0.8f, 0.9f, 1.1f, 1.2f };
@@ -519,7 +535,7 @@ namespace QuestPDF.Examples
var fontStyle = TextStyle.Default.Size(16); var fontStyle = TextStyle.Default.Size(16);
column stack
.Item() .Item()
.Border(1) .Border(1)
.Background(fontColor) .Background(fontColor)
@@ -538,11 +554,12 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(300, 200) .PageSize(300, 200)
.FileName()
.Render(container => .Render(container =>
{ {
container container
.Background("#FFF") .Background("#FFF")
.MinimalBox() .Box()
.Padding(25) .Padding(25)
@@ -565,6 +582,7 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(650, 450) .PageSize(650, 450)
.FileName()
.Render(container => .Render(container =>
{ {
container container
@@ -588,7 +606,7 @@ namespace QuestPDF.Examples
return element; return element;
}) })
.MinimalBox() .Box()
.Background(Colors.White) .Background(Colors.White)
.Padding(10) .Padding(10)
.Text($"Rotated {turns * 90}°", TextStyle.Default.Size(16)); .Text($"Rotated {turns * 90}°", TextStyle.Default.Size(16));
@@ -603,6 +621,7 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(300, 300) .PageSize(300, 300)
.FileName()
.Render(container => .Render(container =>
{ {
container container
@@ -629,6 +648,7 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(300, 300) .PageSize(300, 300)
.FileName()
.Render(container => .Render(container =>
{ {
container container
@@ -660,6 +680,7 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(350, 350) .PageSize(350, 350)
.FileName()
.Render(container => .Render(container =>
{ {
container container
@@ -686,7 +707,7 @@ namespace QuestPDF.Examples
return element; return element;
}) })
.MinimalBox() .Box()
.Background(Colors.White) .Background(Colors.White)
.Padding(10) .Padding(10)
.Text($"Flipped {turns}", TextStyle.Default.Size(16)); .Text($"Flipped {turns}", TextStyle.Default.Size(16));
@@ -701,6 +722,7 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(200, 200) .PageSize(200, 200)
.FileName()
.Render(container => .Render(container =>
{ {
container container
@@ -708,14 +730,14 @@ namespace QuestPDF.Examples
.Border(2) .Border(2)
.Row(row => .Row(row =>
{ {
row.ConstantItem(25) row.ConstantColumn(25)
.Border(1) .Border(1)
.RotateLeft() .RotateLeft()
.AlignCenter() .AlignCenter()
.AlignMiddle() .AlignMiddle()
.Text("Sample text"); .Text("Sample text");
row.RelativeItem().Border(1).Padding(5).Text(Placeholders.Paragraph()); row.RelativeColumn().Border(1).Padding(5).Text(Placeholders.Paragraph());
}); });
}); });
} }
@@ -726,16 +748,17 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(400, 350) .PageSize(400, 350)
.FileName()
.Render(container => .Render(container =>
{ {
container container
.Padding(25) .Padding(25)
.PaddingLeft(75) .PaddingLeft(75)
.Column(column => .Stack(stack =>
{ {
column.Item().Width(300).Height(150).Background(Colors.Blue.Lighten4); stack.Item().Width(300).Height(150).Background(Colors.Blue.Lighten4);
column stack
.Item() .Item()
// creates an infinite space for its child // creates an infinite space for its child
@@ -751,7 +774,7 @@ namespace QuestPDF.Examples
.Background(Colors.Blue.Darken1); .Background(Colors.Blue.Darken1);
column.Item().Width(300).Height(150).Background(Colors.Blue.Lighten3); stack.Item().Width(300).Height(150).Background(Colors.Blue.Lighten3);
}); });
}); });
} }
@@ -762,17 +785,18 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(500, 225) .PageSize(500, 225)
.FileName()
.Render(container => .Render(container =>
{ {
container container
.Padding(25) .Padding(25)
.Column(column => .Stack(stack =>
{ {
column.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeItem().LabelCell("Label 1"); row.RelativeColumn().LabelCell("Label 1");
row.RelativeItem(3).Grid(grid => row.RelativeColumn(3).Grid(grid =>
{ {
grid.Columns(3); grid.Columns(3);
@@ -784,11 +808,11 @@ namespace QuestPDF.Examples
}); });
}); });
column.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeItem().ValueCell().Text("Value 1"); row.RelativeColumn().ValueCell().Text("Value 1");
row.RelativeItem(3).Grid(grid => row.RelativeColumn(3).Grid(grid =>
{ {
grid.Columns(3); grid.Columns(3);
@@ -800,10 +824,10 @@ namespace QuestPDF.Examples
}); });
}); });
column.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeItem().LabelCell("Label 6"); row.RelativeColumn().LabelCell("Label 6");
row.RelativeItem().ValueCell().Text("Value 6"); row.RelativeColumn().ValueCell().Text("Value 6");
}); });
}); });
}); });
@@ -815,6 +839,7 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(600, 310) .PageSize(600, 310)
.FileName()
.Render(container => .Render(container =>
{ {
container container
+7 -43
View File
@@ -3,7 +3,6 @@ using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Fluent; using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
namespace QuestPDF.Examples.Engine namespace QuestPDF.Examples.Engine
@@ -18,10 +17,7 @@ namespace QuestPDF.Examples.Engine
{ {
private string FileNamePrefix = "test"; private string FileNamePrefix = "test";
private Size Size { get; set; } private Size Size { get; set; }
private int? MaxPagesThreshold { get; set; }
private bool ShowResult { get; set; } private bool ShowResult { get; set; }
private bool ApplyCaching { get; set; }
private bool ApplyDebugging { get; set; }
private RenderingTestResult ResultType { get; set; } = RenderingTestResult.Images; private RenderingTestResult ResultType { get; set; } = RenderingTestResult.Images;
private RenderingTest() private RenderingTest()
@@ -29,12 +25,12 @@ namespace QuestPDF.Examples.Engine
} }
public static RenderingTest Create([CallerMemberName] string fileName = "test") public static RenderingTest Create()
{ {
return new RenderingTest().FileName(fileName); return new RenderingTest();
} }
public RenderingTest FileName(string fileName) public RenderingTest FileName([CallerMemberName] string fileName = "test")
{ {
FileNamePrefix = fileName; FileNamePrefix = fileName;
return this; return this;
@@ -69,46 +65,14 @@ namespace QuestPDF.Examples.Engine
return this; return this;
} }
public RenderingTest MaxPages(int value)
{
MaxPagesThreshold = value;
return this;
}
public RenderingTest EnableCaching(bool value = true)
{
ApplyCaching = value;
return this;
}
public RenderingTest EnableDebugging(bool value = true)
{
ApplyDebugging = value;
return this;
}
public void Render(Action<IContainer> content) public void Render(Action<IContainer> content)
{ {
RenderDocument(container => var container = new Container();
{ content(container);
container.Page(page =>
{
page.Size(new PageSize(Size.Width, Size.Height));
page.Content().Container().Background(Colors.White).Element(content);
});
});
}
public void RenderDocument(Action<IDocumentContainer> content) var maxPages = ResultType == RenderingTestResult.Pdf ? 1000 : 10;
{ var document = new SimpleDocument(container, Size, maxPages);
MaxPagesThreshold ??= ResultType == RenderingTestResult.Pdf ? 1000 : 10;
var document = new SimpleDocument(content, MaxPagesThreshold.Value, ApplyCaching, ApplyDebugging);
Render(document);
}
private void Render(IDocument document)
{
if (ResultType == RenderingTestResult.Images) if (ResultType == RenderingTestResult.Images)
{ {
Func<int, string> fileNameSchema = i => $"{FileNamePrefix}-${i}.png"; Func<int, string> fileNameSchema = i => $"{FileNamePrefix}-${i}.png";
+12 -13
View File
@@ -1,5 +1,4 @@
using System; using QuestPDF.Drawing;
using QuestPDF.Drawing;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Fluent; using QuestPDF.Fluent;
using QuestPDF.Helpers; using QuestPDF.Helpers;
@@ -11,17 +10,15 @@ namespace QuestPDF.Examples.Engine
{ {
public const int ImageScalingFactor = 2; public const int ImageScalingFactor = 2;
private Action<IDocumentContainer> Content { get; } private IContainer Container { get; }
private Size Size { get; }
private int MaxPages { get; } private int MaxPages { get; }
private bool ApplyCaching { get; }
private bool ApplyDebugging { get; }
public SimpleDocument(Action<IDocumentContainer> content, int maxPages, bool applyCaching, bool applyDebugging) public SimpleDocument(IContainer container, Size size, int maxPages)
{ {
Content = content; Container = container;
Size = size;
MaxPages = maxPages; MaxPages = maxPages;
ApplyCaching = applyCaching;
ApplyDebugging = applyDebugging;
} }
public DocumentMetadata GetMetadata() public DocumentMetadata GetMetadata()
@@ -29,15 +26,17 @@ namespace QuestPDF.Examples.Engine
return new DocumentMetadata() return new DocumentMetadata()
{ {
RasterDpi = PageSizes.PointsPerInch * ImageScalingFactor, RasterDpi = PageSizes.PointsPerInch * ImageScalingFactor,
DocumentLayoutExceptionThreshold = MaxPages, DocumentLayoutExceptionThreshold = MaxPages
ApplyCaching = ApplyCaching,
ApplyDebugging = ApplyDebugging
}; };
} }
public void Compose(IDocumentContainer container) public void Compose(IDocumentContainer container)
{ {
Content(container); container.Page(page =>
{
page.Size(new PageSize(Size.Width, Size.Height));
page.Content().Container().Background(Colors.White).Element(Container as Container);
});
} }
} }
} }
-53
View File
@@ -1,53 +0,0 @@
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class EnsureSpaceExample
{
[Test]
public void EnsureSpaceWith()
{
RenderingTest
.Create()
.ProduceImages()
.ShowResults()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Margin(20);
page.Size(PageSizes.A7.Landscape());
page.Background(Colors.White);
page.Header().Text("With ensure space", TextStyle.Default.SemiBold());
page.Content().Column(column =>
{
column
.Item()
.ExtendHorizontal()
.Height(75)
.Background(Colors.Grey.Lighten2);
column
.Item()
.EnsureSpace(100)
.Text(Placeholders.LoremIpsum());
});
page.Footer().Text(text =>
{
text.Span("Page ");
text.CurrentPageNumber();
text.Span(" out of ");
text.TotalPages();
});
});
});
}
}
}
+9 -10
View File
@@ -12,14 +12,12 @@ namespace QuestPDF.Examples
{ {
return container return container
.Border(1) .Border(1)
.BorderColor(Colors.Grey.Medium) .Background(dark ? Colors.Grey.Lighten2 : Colors.White)
.Background(dark ? Colors.Grey.Lighten3 : Colors.White) .Padding(10);
.Padding(5);
} }
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text, TextStyle.Default.SemiBold()); 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 static IContainer ValueCell(this IContainer container) => container.Cell(false);
public static void ValueCell(this IContainer container, string text) => container.ValueCell().Text(text, TextStyle.Default);
} }
public class FrameExample public class FrameExample
@@ -30,20 +28,21 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(550, 400) .PageSize(550, 400)
.FileName()
.ShowResults() .ShowResults()
.Render(container => .Render(container =>
{ {
container container
.Background("#FFF") .Background("#FFF")
.Padding(25) .Padding(25)
.Column(column => .Stack(stack =>
{ {
for(var i = 1; i <= 4; i++) for(var i=1; i<=4; i++)
{ {
column.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeItem(2).LabelCell(Placeholders.Label()); row.RelativeColumn(2).LabelCell(Placeholders.Label());
row.RelativeItem(3).ValueCell().Text(Placeholders.Paragraph()); row.RelativeColumn(3).ValueCell().Text(Placeholders.Paragraph());
}); });
} }
}); });
-51
View File
@@ -1,51 +0,0 @@
using System.IO;
using NUnit.Framework;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
namespace QuestPDF.Examples
{
public class ImageExamples
{
[Test]
public void LoadingImage()
{
RenderingTest
.Create()
.PageSize(PageSizes.A5)
.ProducePdf()
.ShowResults()
.Render(page =>
{
page.Padding(25).Column(column =>
{
column.Spacing(25);
column.Item().Image("logo.png");
var binaryData = File.ReadAllBytes("logo.png");
column.Item().Image(binaryData);
using var stream = new FileStream("logo.png", FileMode.Open);
column.Item().Image(stream);
});
});
}
[Test]
public void Exception()
{
Assert.Throws<DocumentComposeException>(() =>
{
RenderingTest
.Create()
.PageSize(PageSizes.A2)
.ProducePdf()
.ShowResults()
.Render(page => page.Image("non_existent.png"));
});
}
}
}
-123
View File
@@ -1,123 +0,0 @@
using System;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class InlinedExamples
{
[Test]
public void Inlined()
{
RenderingTest
.Create()
.PageSize(800, 650)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(25)
.Decoration(decoration =>
{
decoration.Before().Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.Size(20));
text.CurrentPageNumber();
text.Span(" / ");
text.TotalPages();
});
decoration
.Content()
.PaddingTop(25)
//.MinimalBox()
.Border(1)
.Background(Colors.Grey.Lighten2)
.Inlined(inlined =>
{
inlined.Spacing(25);
inlined.AlignSpaceAround();
inlined.BaselineMiddle();
var random = new Random(123);
foreach (var _ in Enumerable.Range(0, 50))
{
var width = random.Next(2, 7);
var height = random.Next(2, 7);
var sizeText = $"{width}×{height}";
inlined
.Item()
.Border(1)
.Width(width * 25)
.Height(height * 25)
.Background(Placeholders.BackgroundColor())
.Layers(layers =>
{
layers.Layer().Grid(grid =>
{
grid.Columns(width);
Enumerable.Range(0, width * height).ToList().ForEach(x => grid.Item().Border(1).BorderColor(Colors.White).Width(25).Height(25));
});
layers
.PrimaryLayer()
.AlignCenter()
.AlignMiddle()
.Text(sizeText, TextStyle.Default.Size(15));
});
}
});
});
});
}
[Test]
public void Inline_AlignLeft_BaselineBottom()
{
RenderingTest
.Create()
.PageSize(800, 600)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(20)
.MinimalBox()
.Border(1)
.Background(Colors.Grey.Lighten4)
.Inlined(inlined =>
{
inlined.VerticalSpacing(50);
inlined.HorizontalSpacing(25);
inlined.AlignRight();
inlined.BaselineMiddle();
foreach (var _ in Enumerable.Range(0, 100))
inlined.Item().Element(RandomBlock);
});
});
void RandomBlock(IContainer container)
{
container
.Width(Placeholders.Random.Next(1, 5) * 20)
.Height(Placeholders.Random.Next(1, 5) * 20)
.Border(1)
.BorderColor(Colors.Grey.Darken2)
.Background(Placeholders.BackgroundColor());
}
}
}
}
-50
View File
@@ -1,50 +0,0 @@
using System.IO;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
namespace QuestPDF.Examples
{
public class LineExamples
{
[Test]
public void LineHorizontal()
{
RenderingTest
.Create()
.PageSize(PageSizes.A5)
.ProducePdf()
.ShowResults()
.Render(container =>
{
container.Padding(25).Column(column =>
{
column.Item().Text("Above text");
column.Item().PaddingVertical(10).LineHorizontal(1).LineColor(Colors.Blue.Accent1);
column.Item().Text("Below text");
});
});
}
[Test]
public void LineVertical()
{
RenderingTest
.Create()
.PageSize(PageSizes.A5)
.ProducePdf()
.ShowResults()
.Render(container =>
{
container.Padding(25).Inlined(inlined =>
{
inlined.Spacing(5);
inlined.Item().Text("Above text");
inlined.Item().LineVertical(1).LineColor(Colors.Blue.Accent1);
inlined.Item().Text("Below text");
});
});
}
}
}
+2 -3
View File
@@ -36,14 +36,13 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(350, 280) .PageSize(350, 280)
.ProducePdf() .FileName()
.ShowResults()
.Render(container => .Render(container =>
{ {
container container
.Background("#FFF") .Background("#FFF")
.Padding(25) .Padding(25)
.Column(column => .Stack(column =>
{ {
column.Spacing(10); column.Spacing(10);
+10 -6
View File
@@ -13,6 +13,7 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(300, 300) .PageSize(300, 300)
.FileName()
.ShowResults() .ShowResults()
.Render(container => .Render(container =>
{ {
@@ -36,6 +37,7 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(200, 150) .PageSize(200, 150)
.FileName()
.Render(container => .Render(container =>
{ {
container container
@@ -60,10 +62,11 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(200, 150) .PageSize(200, 150)
.FileName()
.Render(container => .Render(container =>
{ {
container container
.Column(column => .Stack(column =>
{ {
column column
.Item() .Item()
@@ -110,17 +113,18 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(200, 150) .PageSize(200, 150)
.FileName()
.Render(container => .Render(container =>
{ {
container container
.Column(column => .Stack(column =>
{ {
column column
.Item() .Item()
.Height(150) .Height(150)
.Row(row => .Row(row =>
{ {
row.RelativeItem() row.RelativeColumn()
.Extend() .Extend()
.Background("FFF") .Background("FFF")
@@ -128,7 +132,7 @@ namespace QuestPDF.Examples
.Width(50) .Width(50)
.Background("444"); .Background("444");
row.RelativeItem() row.RelativeColumn()
.Extend() .Extend()
.Background("BBB") .Background("BBB")
@@ -142,7 +146,7 @@ namespace QuestPDF.Examples
.Height(150) .Height(150)
.Row(row => .Row(row =>
{ {
row.RelativeItem() row.RelativeColumn()
.Extend() .Extend()
.Background("BBB") .Background("BBB")
@@ -150,7 +154,7 @@ namespace QuestPDF.Examples
.Width(50) .Width(50)
.Background("444"); .Background("444");
row.RelativeItem() row.RelativeColumn()
.Extend() .Extend()
.Background("BBB") .Background("BBB")
@@ -6,8 +6,6 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
<PackageReference Include="microcharts" Version="0.9.5.9" />
<PackageReference Include="nunit" Version="3.13.2" /> <PackageReference Include="nunit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" /> <PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
@@ -25,9 +23,6 @@
<None Update="LibreBarcode39-Regular.ttf"> <None Update="LibreBarcode39-Regular.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Update="logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup> </ItemGroup>
</Project> </Project>
-93
View File
@@ -1,93 +0,0 @@
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class RowExamples
{
[Test]
public void ItemTypes()
{
RenderingTest
.Create()
.ProducePdf()
.PageSize(650, 300)
.ShowResults()
.Render(container =>
{
container
.Padding(25)
.MinimalBox()
.Border(1)
.Column(column =>
{
column.Item().LabelCell("Total width: 600px");
column.Item().Row(row =>
{
row.ConstantItem(150).ValueCell("150px");
row.ConstantItem(100).ValueCell("100px");
row.RelativeItem(4).ValueCell("200px");
row.RelativeItem(3).ValueCell("150px");
});
column.Item().Row(row =>
{
row.ConstantItem(100).ValueCell("100px");
row.ConstantItem(50).ValueCell("50px");
row.RelativeItem(2).ValueCell("100px");
row.RelativeItem(1).ValueCell("50px");
});
});
});
}
[Test]
public void Stability()
{
// up to version 2021.12, this code would always result with the infinite layout exception
RenderingTest
.Create()
.ProducePdf()
.MaxPages(100)
.PageSize(250, 150)
.ShowResults()
.Render(container =>
{
container
.Padding(25)
.Row(row =>
{
row.RelativeItem().Column(column =>
{
column.Item().ShowOnce().Element(CreateBox).Text("X");
column.Item().Element(CreateBox).Text("1");
column.Item().Element(CreateBox).Text("2");
});
row.RelativeItem().Column(column =>
{
column.Item().Element(CreateBox).Text("1");
column.Item().Element(CreateBox).Text("2");
});
});
});
static IContainer CreateBox(IContainer container)
{
return container
.ExtendHorizontal()
.ExtendVertical()
.Background(Colors.Grey.Lighten4)
.Border(1)
.AlignCenter()
.AlignMiddle()
.ShowOnce();
}
}
}
}
-42
View File
@@ -1,42 +0,0 @@
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class ScaleToFitExamples
{
[Test]
public void ScaleToFit()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ProducePdf()
.ShowResults()
.Render(container =>
{
container.Padding(25).Column(column =>
{
var text = Placeholders.Paragraph();
foreach (var i in Enumerable.Range(0, 16))
{
column
.Item()
.MinimalBox()
.Border(1)
.Padding(5)
.Width(50 + i * 25)
.Height(25 + i * 5)
.ScaleToFit()
.Text(text);
}
});
});
}
}
}
-54
View File
@@ -1,54 +0,0 @@
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class ShowOnceExample
{
[Test]
public void ShowOnce()
{
RenderingTest
.Create()
.ProduceImages()
.ShowResults()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Margin(20);
page.Size(PageSizes.A7.Landscape());
page.Background(Colors.White);
page.Header().Text("With show once", TextStyle.Default.SemiBold());
page.Content().PaddingVertical(5).Row(row =>
{
row.RelativeItem()
.Background(Colors.Grey.Lighten2)
.Border(1)
.Padding(5)
.ShowOnce()
.Text(Placeholders.Label());
row.RelativeItem(2)
.Border(1)
.Padding(5)
.Text(Placeholders.Paragraph());
});
page.Footer().Text(text =>
{
text.Span("Page ");
text.CurrentPageNumber();
text.Span(" out of ");
text.TotalPages();
});
});
});
}
}
}
-47
View File
@@ -1,47 +0,0 @@
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class SkipOnceExample
{
[Test]
public void SkipOnce()
{
RenderingTest
.Create()
.ProduceImages()
.ShowResults()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Margin(20);
page.Size(PageSizes.A7.Landscape());
page.Background(Colors.White);
page.Header().Column(column =>
{
column.Item().ShowOnce().Text("This header is visible on the first page.");
column.Item().SkipOnce().Text("This header is visible on the second page and all following.");
});
page.Content()
.PaddingVertical(10)
.Text(Placeholders.Paragraphs(), TextStyle.Default.Color(Colors.Grey.Medium));
page.Footer().Text(text =>
{
text.Span("Page ");
text.CurrentPageNumber();
text.Span(" out of ");
text.TotalPages();
});
});
});
}
}
}
-465
View File
@@ -1,465 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using IContainer = QuestPDF.Infrastructure.IContainer;
namespace QuestPDF.Examples
{
public class TableExamples
{
[Test]
public void BasicPlacement()
{
RenderingTest
.Create()
.ProduceImages()
.PageSize(220, 220)
.ShowResults()
.Render(container =>
{
container
.Padding(10)
.MinimalBox()
.Border(1)
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
});
// by using custom 'Element' method, we can reuse visual configuration
table.Cell().Row(1).Column(4).Element(Block).Text("A");
table.Cell().Row(2).Column(2).Element(Block).Text("B");
table.Cell().Row(3).Column(3).Element(Block).Text("C");
table.Cell().Row(4).Column(1).Element(Block).Text("D");
});
});
}
[Test]
public void DefaultCellStyle()
{
RenderingTest
.Create()
.ProduceImages()
.PageSize(220, 120)
.ShowResults()
.Render(container =>
{
container
.Padding(10)
.MinimalBox()
.Border(1)
.DefaultTextStyle(TextStyle.Default.Size(16))
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
});
table.Cell().Row(1).Column(1).Element(Block).Text("A");
table.Cell().Row(2).Column(2).Element(Block).Text("B");
table.Cell().Row(1).Column(3).Element(Block).Text("C");
table.Cell().Row(2).Column(4).Element(Block).Text("D");
});
});
}
[Test]
public void ColumnsDefinition()
{
RenderingTest
.Create()
.ProduceImages()
.PageSize(320, 80)
.ShowResults()
.Render(container =>
{
container
.Padding(10)
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.ConstantColumn(50);
columns.ConstantColumn(100);
columns.RelativeColumn(2);
columns.RelativeColumn(3);
});
table.Cell().ColumnSpan(4).LabelCell("Total width: 300px");
table.Cell().ValueCell("50px");
table.Cell().ValueCell("100px");
table.Cell().ValueCell("100px");
table.Cell().ValueCell("150px");
});
});
}
[Test]
public void PartialAutoPlacement()
{
RenderingTest
.Create()
.ProduceImages()
.PageSize(220, 220)
.ShowResults()
.Render(container =>
{
container
.Padding(10)
.MinimalBox()
.Border(1)
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
});
table.Cell().Element(Block).Text("A");
table.Cell().Row(2).Column(2).Element(Block).Text("B");
table.Cell().Element(Block).Text("C");
table.Cell().Row(3).Column(3).Element(Block).Text("D");
table.Cell().ColumnSpan(2).Element(Block).Text("E");
});
});
}
[Test]
public void ExtendLastCellsToTableBottom()
{
RenderingTest
.Create()
.ProduceImages()
.PageSize(220, 170)
.ShowResults()
.Render(container =>
{
container
.Padding(10)
.MinimalBox()
.Border(1)
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
});
table.ExtendLastCellsToTableBottom();
table.Cell().Row(1).Column(1).Element(Block).Text("A");
table.Cell().Row(3).Column(1).Element(Block).Text("B");
table.Cell().Row(2).Column(2).Element(Block).Text("C");
table.Cell().Row(3).Column(3).Element(Block).Text("D");
table.Cell().Row(2).RowSpan(2).Column(4).Element(Block).Text("E");
});
});
}
[Test]
public void Overlapping()
{
RenderingTest
.Create()
.ProduceImages()
.PageSize(170, 170)
.ShowResults()
.Render(container =>
{
container
.Padding(10)
.MinimalBox()
.Border(1)
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
});
table.Cell().Row(1).RowSpan(3).Column(1).ColumnSpan(3).Background(Colors.Grey.Lighten3).MinHeight(150);
table.Cell().Row(1).RowSpan(2).Column(1).ColumnSpan(2).Background(Colors.Grey.Lighten1).MinHeight(100);
table.Cell().Row(3).Column(3).Background(Colors.Grey.Darken1).MinHeight(50);
});
});
}
[Test]
public void Spans()
{
RenderingTest
.Create()
.ProduceImages()
.PageSize(220, 220)
.ShowResults()
.Render(container =>
{
container
.Padding(10)
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
});
table.Cell().RowSpan(2).ColumnSpan(2).Element(Block).Text("1");
table.Cell().ColumnSpan(2).Element(Block).Text("2");
table.Cell().Element(Block).Text("3");
table.Cell().Element(Block).Text("4");
table.Cell().RowSpan(2).Element(Block).Text("5");
table.Cell().ColumnSpan(2).Element(Block).Text("6");
table.Cell().RowSpan(2).Element(Block).Text("7");
table.Cell().Element(Block).Text("8");
table.Cell().Element(Block).Text("9");
});
});
}
[Test]
public void Stability()
{
RenderingTest
.Create()
.ProduceImages()
.PageSize(300, 300)
.ShowResults()
.Render(container =>
{
container
.Padding(10)
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
});
table.Cell().RowSpan(4).Element(Block).Text("1");
table.Cell().RowSpan(2).Element(Block).Text("2");
table.Cell().RowSpan(1).Element(Block).Text("3");
table.Cell().RowSpan(1).Element(Block).Text("4");
table.Cell().RowSpan(2).Element(Block).Text("5");
table.Cell().RowSpan(1).Element(Block).Text("6");
table.Cell().RowSpan(1).Element(Block).Text("7");
});
});
}
[Test]
public void TableHeader()
{
RenderingTest
.Create()
.ProduceImages()
.PageSize(500, 200)
.ShowResults()
.EnableDebugging()
.Render(container =>
{
var pageSizes = new List<(string name, double width, double height)>()
{
("Letter (ANSI A)", 8.5f, 11),
("Legal", 8.5f, 14),
("Ledger (ANSI B)", 11, 17),
("Tabloid (ANSI B)", 17, 11),
("ANSI C", 22, 17),
("ANSI D", 34, 22),
("ANSI E", 44, 34)
};
const int inchesToPoints = 72;
container
.Padding(10)
.MinimalBox()
.Border(1)
.Table(table =>
{
IContainer DefaultCellStyle(IContainer container, string backgroundColor)
{
return container
.Border(1)
.BorderColor(Colors.Grey.Lighten1)
.Background(backgroundColor)
.PaddingVertical(5)
.PaddingHorizontal(10)
.AlignCenter()
.AlignMiddle();
}
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.ConstantColumn(75);
columns.ConstantColumn(75);
columns.ConstantColumn(75);
columns.ConstantColumn(75);
});
table.Header(header =>
{
header.Cell().RowSpan(2).Element(CellStyle).ExtendHorizontal().AlignLeft().Text("Document type");
header.Cell().ColumnSpan(2).Element(CellStyle).Text("Inches");
header.Cell().ColumnSpan(2).Element(CellStyle).Text("Points");
header.Cell().Element(CellStyle).Text("Width");
header.Cell().Element(CellStyle).Text("Height");
header.Cell().Element(CellStyle).Text("Width");
header.Cell().Element(CellStyle).Text("Height");
// you can extend already existing styles by creating additional methods
IContainer CellStyle(IContainer container) => DefaultCellStyle(container, Colors.Grey.Lighten3);
});
foreach (var page in pageSizes)
{
table.Cell().Element(CellStyle).ExtendHorizontal().AlignLeft().Text(page.name);
// inches
table.Cell().Element(CellStyle).Text(page.width);
table.Cell().Element(CellStyle).Text(page.height);
// points
table.Cell().Element(CellStyle).Text(page.width * inchesToPoints);
table.Cell().Element(CellStyle).Text(page.height * inchesToPoints);
IContainer CellStyle(IContainer container) => DefaultCellStyle(container, Colors.White);
}
});
});
}
[Test]
public void PerformanceText_TemperatureReport()
{
RenderingTest
.Create()
.ProducePdf()
.PageSize(PageSizes.A4)
.MaxPages(10_000)
.EnableCaching()
.EnableDebugging(false)
.ShowResults()
.Render(container => GeneratePerformanceStructure(container, 1000));
}
public static void GeneratePerformanceStructure(IContainer container, int repeats)
{
container
.Padding(25)
//.Background(Colors.Blue.Lighten2)
.MinimalBox()
.Border(1)
//.Background(Colors.Red.Lighten2)
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.ConstantColumn(100);
columns.RelativeColumn();
columns.ConstantColumn(100);
columns.RelativeColumn();
});
table.ExtendLastCellsToTableBottom();
foreach (var i in Enumerable.Range(0, repeats))
{
table.Cell().RowSpan(3).LabelCell("Project");
table.Cell().RowSpan(3).ShowEntire().ValueCell(Placeholders.Sentence());
table.Cell().LabelCell("Report number");
table.Cell().ValueCell(i.ToString());
table.Cell().LabelCell("Date");
table.Cell().ValueCell(Placeholders.ShortDate());
table.Cell().LabelCell("Inspector");
table.Cell().ValueCell("Marcin Ziąbek");
table.Cell().ColumnSpan(2).LabelCell("Morning weather");
table.Cell().ColumnSpan(2).LabelCell("Evening weather");
table.Cell().ValueCell("Time");
table.Cell().ValueCell("7:13");
table.Cell().ValueCell("Time");
table.Cell().ValueCell("18:25");
table.Cell().ValueCell("Description");
table.Cell().ValueCell("Sunny");
table.Cell().ValueCell("Description");
table.Cell().ValueCell("Windy");
table.Cell().ValueCell("Wind");
table.Cell().ValueCell("Mild");
table.Cell().ValueCell("Wind");
table.Cell().ValueCell("Strong");
table.Cell().ValueCell("Temperature");
table.Cell().ValueCell("17°C");
table.Cell().ValueCell("Temperature");
table.Cell().ValueCell("32°C");
table.Cell().LabelCell("Remarks");
table.Cell().ColumnSpan(3).ValueCell(Placeholders.Paragraph());
}
});
}
// this method uses a higher order function to define a custom and dynamic style
static IContainer Block(IContainer container)
{
return container
.Border(1)
.Background(Colors.Grey.Lighten3)
.MinWidth(50)
.MinHeight(50)
.AlignCenter()
.AlignMiddle();
}
}
}
+31 -29
View File
@@ -21,6 +21,7 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(PageSizes.A4) .PageSize(PageSizes.A4)
.FileName()
.ProducePdf() .ProducePdf()
.ShowResults() .ShowResults()
.Render(x => ComposeBook(x, chapters)); .Render(x => ComposeBook(x, chapters));
@@ -42,7 +43,8 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(PageSizes.A4) .PageSize(PageSizes.A4)
.ProducePdf() .FileName()
.ProducePdf()
.Render(x => ComposeBook(x, chapters)); .Render(x => ComposeBook(x, chapters));
} }
@@ -117,19 +119,19 @@ namespace QuestPDF.Examples
{ {
decoration decoration
.Content() .Content()
.Column(column => .Stack(stack =>
{ {
column.Item().Element(Title); stack.Item().Element(Title);
column.Item().PageBreak(); stack.Item().PageBreak();
column.Item().Element(TableOfContents); stack.Item().Element(TableOfContents);
column.Item().PageBreak(); stack.Item().PageBreak();
Chapters(column); Chapters(stack);
column.Item().Element(Acknowledgements); stack.Item().Element(Acknowledgements);
}); });
decoration.After().Element(Footer); decoration.Footer().Element(Footer);
}); });
} }
@@ -139,61 +141,61 @@ namespace QuestPDF.Examples
.Extend() .Extend()
.PaddingBottom(200) .PaddingBottom(200)
.AlignBottom() .AlignBottom()
.Column(column => .Stack(stack =>
{ {
column.Item().Text("Quo Vadis", TextStyle.Default.Size(72).Bold().Color(Colors.Blue.Darken2)); stack.Item().Text("Quo Vadis", TextStyle.Default.Size(72).Bold().Color(Colors.Blue.Darken2));
column.Item().Text("Henryk Sienkiewicz", TextStyle.Default.Size(24).Color(Colors.Grey.Darken2)); stack.Item().Text("Henryk Sienkiewicz", TextStyle.Default.Size(24).Color(Colors.Grey.Darken2));
}); });
} }
void TableOfContents(IContainer container) void TableOfContents(IContainer container)
{ {
container.Column(column => container.Stack(stack =>
{ {
SectionTitle(column, "Spis treści"); SectionTitle(stack, "Spis treści");
foreach (var chapter in chapters) foreach (var chapter in chapters)
{ {
column.Item().InternalLink(chapter.Title).Row(row => stack.Item().InternalLink(chapter.Title).Row(row =>
{ {
row.RelativeItem().Text(chapter.Title, normalStyle); row.RelativeColumn().Text(chapter.Title, normalStyle);
row.ConstantItem(100).AlignRight().Text(text => text.PageNumberOfLocation(chapter.Title, normalStyle)); row.ConstantColumn(100).AlignRight().Text(text => text.PageNumberOfLocation(chapter.Title, normalStyle));
}); });
} }
}); });
} }
void Chapters(ColumnDescriptor column) void Chapters(StackDescriptor stack)
{ {
foreach (var chapter in chapters) foreach (var chapter in chapters)
{ {
column.Item().Element(container => Chapter(container, chapter.Title, chapter.Content)); stack.Item().Element(container => Chapter(container, chapter.Title, chapter.Content));
} }
} }
void Chapter(IContainer container, string title, string content) void Chapter(IContainer container, string title, string content)
{ {
container.Column(column => container.Stack(stack =>
{ {
SectionTitle(column, title); SectionTitle(stack, title);
column.Item().Text(text => stack.Item().Text(text =>
{ {
text.ParagraphSpacing(5); text.ParagraphSpacing(5);
text.Span(content, normalStyle); text.Span(content, normalStyle);
}); });
column.Item().PageBreak(); stack.Item().PageBreak();
}); });
} }
void Acknowledgements(IContainer container) void Acknowledgements(IContainer container)
{ {
container.Column(column => container.Stack(stack =>
{ {
SectionTitle(column, "Podziękowania"); SectionTitle(stack, "Podziękowania");
column.Item().Text(text => stack.Item().Text(text =>
{ {
text.DefaultTextStyle(normalStyle); text.DefaultTextStyle(normalStyle);
@@ -204,10 +206,10 @@ namespace QuestPDF.Examples
}); });
} }
void SectionTitle(ColumnDescriptor column, string text) void SectionTitle(StackDescriptor stack, string text)
{ {
column.Item().Location(text).Text(text, subtitleStyle); stack.Item().Location(text).Text(text, subtitleStyle);
column.Item().PaddingTop(10).PaddingBottom(50).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).ExtendHorizontal(); stack.Item().PaddingTop(10).PaddingBottom(50).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).ExtendHorizontal();
} }
void Footer(IContainer container) void Footer(IContainer container)
+15 -9
View File
@@ -17,14 +17,14 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(500, 300) .PageSize(500, 300)
.FileName()
.ProduceImages() .ProduceImages()
.ShowResults() .ShowResults()
.Render(container => .Render(container =>
{ {
container container
.Padding(5) .Padding(5)
.MinimalBox() .Box()
.Border(1) .Border(1)
.Padding(10) .Padding(10)
.Text(text => .Text(text =>
@@ -42,13 +42,14 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(500, 300) .PageSize(500, 300)
.FileName()
.ProduceImages() .ProduceImages()
.ShowResults() .ShowResults()
.Render(container => .Render(container =>
{ {
container container
.Padding(5) .Padding(5)
.MinimalBox() .Box()
.Border(1) .Border(1)
.Padding(10) .Padding(10)
.Text(text => .Text(text =>
@@ -70,13 +71,14 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(500, 200) .PageSize(500, 200)
.FileName()
.ProduceImages() .ProduceImages()
.ShowResults() .ShowResults()
.Render(container => .Render(container =>
{ {
container container
.Padding(5) .Padding(5)
.MinimalBox() .Box()
.Border(1) .Border(1)
.Padding(10) .Padding(10)
.Text(text => .Text(text =>
@@ -101,6 +103,7 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(PageSizes.A4) .PageSize(PageSizes.A4)
.FileName()
.ProducePdf() .ProducePdf()
.ShowResults() .ShowResults()
.Render(container => .Render(container =>
@@ -108,7 +111,7 @@ namespace QuestPDF.Examples
container container
.Padding(20) .Padding(20)
.Padding(10) .Padding(10)
.MinimalBox() .Box()
.Border(1) .Border(1)
.Padding(5) .Padding(5)
.Padding(10) .Padding(10)
@@ -126,11 +129,12 @@ namespace QuestPDF.Examples
} }
[Test] [Test]
public void Textcolumn() public void TextStack()
{ {
RenderingTest RenderingTest
.Create() .Create()
.PageSize(PageSizes.A4) .PageSize(PageSizes.A4)
.FileName()
.ProducePdf() .ProducePdf()
.ShowResults() .ShowResults()
.Render(container => .Render(container =>
@@ -138,7 +142,7 @@ namespace QuestPDF.Examples
container container
.Padding(20) .Padding(20)
.Padding(10) .Padding(10)
.MinimalBox() .Box()
.Border(1) .Border(1)
.Padding(5) .Padding(5)
.Padding(10) .Padding(10)
@@ -160,6 +164,7 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(PageSizes.A4) .PageSize(PageSizes.A4)
.FileName()
.ProducePdf() .ProducePdf()
.ShowResults() .ShowResults()
.Render(container => .Render(container =>
@@ -167,7 +172,7 @@ namespace QuestPDF.Examples
container container
.Padding(20) .Padding(20)
.Padding(10) .Padding(10)
.MinimalBox() .Box()
.Border(1) .Border(1)
.Padding(5) .Padding(5)
.Padding(10) .Padding(10)
@@ -223,6 +228,7 @@ namespace QuestPDF.Examples
RenderingTest RenderingTest
.Create() .Create()
.PageSize(PageSizes.A4) .PageSize(PageSizes.A4)
.FileName()
.ProducePdf() .ProducePdf()
.ShowResults() .ShowResults()
.Render(container => .Render(container =>
@@ -230,7 +236,7 @@ namespace QuestPDF.Examples
container container
.Padding(20) .Padding(20)
.Padding(10) .Padding(10)
.MinimalBox() .Box()
.Border(1) .Border(1)
.Padding(5) .Padding(5)
.Padding(10) .Padding(10)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

+14 -2
View File
@@ -7,6 +7,9 @@ namespace QuestPDF.ReportSample
{ {
public static class DataSource public static class DataSource
{ {
public static int SectionCounter { get; set; }
public static int FieldCounter { get; set; }
public static ReportModel GetReport() public static ReportModel GetReport()
{ {
return new ReportModel return new ReportModel
@@ -86,6 +89,7 @@ namespace QuestPDF.ReportSample
return new ReportSectionMap return new ReportSectionMap
{ {
Label = "Location", Label = "Location",
ImageSource = Placeholders.Image,
Location = Helpers.RandomLocation() Location = Helpers.RandomLocation()
}; };
} }
@@ -95,7 +99,10 @@ namespace QuestPDF.ReportSample
return new ReportSectionPhotos return new ReportSectionPhotos
{ {
Label = "Photos", Label = "Photos",
PhotoCount = Helpers.Random.Next(1, 15) Photos = Enumerable
.Range(0, Helpers.Random.Next(1, 15))
.Select(x => Placeholders.Image(400, 300))
.ToList()
}; };
} }
@@ -103,9 +110,14 @@ namespace QuestPDF.ReportSample
{ {
return new ReportPhoto() return new ReportPhoto()
{ {
PhotoData = Placeholders.Image(800, 600),
Comments = Placeholders.Sentence(), Comments = Placeholders.Sentence(),
Date = DateTime.Now - TimeSpan.FromDays(Helpers.Random.NextDouble() * 100), Date = DateTime.Now - TimeSpan.FromDays(Helpers.Random.NextDouble() * 100),
Location = Helpers.RandomLocation() Location = Helpers.RandomLocation(),
MapContextSource = x => Placeholders.Image(400, 300),
MapDetailsSource = x => Placeholders.Image(400, 300)
}; };
} }
} }
@@ -1,85 +0,0 @@
using QuestPDF.Drawing;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.ReportSample.Layouts
{
public class DifferentHeadersTemplate : IDocument
{
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public void Compose(IDocumentContainer container)
{
container
.Page(page =>
{
page.Margin(40);
page.Size(PageSizes.A4);
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().Element(ComposeFooter);
});
}
private void ComposeHeader(IContainer container)
{
container.Background(Colors.Grey.Lighten3).Border(1).Column(column =>
{
column.Item().ShowOnce().Padding(5).AlignMiddle().Row(row =>
{
row.RelativeItem(2).AlignMiddle().Text("PRIMARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
row.RelativeItem(1).AlignRight().MinimalBox().AlignMiddle().Background(Colors.Blue.Darken2).Padding(30);
});
column.Item().SkipOnce().Padding(5).Row(row =>
{
row.RelativeItem(2).Text("SECONDARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
row.RelativeItem(1).AlignRight().MinimalBox().Background(Colors.Blue.Lighten4).Padding(15);
});
});
}
private void ComposeContent(IContainer container)
{
container.Column(column =>
{
column.Item().PaddingVertical(80).Text("First");
column.Item().PageBreak();
column.Item().PaddingVertical(80).Text("Second");
column.Item().PageBreak();
column.Item().PaddingVertical(80).Text("Third");
column.Item().PageBreak();
});
}
private void ComposeFooter(IContainer container)
{
container.Background(Colors.Grey.Lighten3).Column(column =>
{
column.Item().ShowOnce().Background(Colors.Grey.Lighten3).Row(row =>
{
row.RelativeItem().Text(x =>
{
x.CurrentPageNumber();
x.Span(" / ");
x.TotalPages();
});
row.RelativeItem().AlignRight().Text("Footer for header");
});
column.Item().SkipOnce().Background(Colors.Grey.Lighten3).Row(row =>
{
row.RelativeItem().Text(x =>
{
x.CurrentPageNumber();
x.Span(" / ");
x.TotalPages();
});
row.RelativeItem().AlignRight().Text("Footer for every page except header");
});
});
}
}
}
@@ -1,20 +0,0 @@
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.ReportSample.Layouts
{
public class ImagePlaceholder : IComponent
{
public static bool Solid { get; set; } = false;
public void Compose(IContainer container)
{
if (Solid)
container.Background(Placeholders.Color());
else
container.Image(Placeholders.Image);
}
}
}
@@ -0,0 +1,31 @@
using System;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
namespace QuestPDF.ReportSample.Layouts
{
public class ImageTemplate : IComponent
{
private Func<Size, byte[]> Source { get; }
private float AspectRatio { get; }
public ImageTemplate(byte[] source, float aspectRatio = 1.333333f) : this(_ => source, aspectRatio)
{
}
public ImageTemplate(Func<Size, byte[]> source, float aspectRatio = 1.333333f)
{
Source = source;
AspectRatio = aspectRatio;
}
public void Compose(IContainer container)
{
container
.AspectRatio(AspectRatio)
.Background("#EEEEEE")
.Image(Source(Size.Zero));
}
}
}
+15 -15
View File
@@ -17,11 +17,11 @@ namespace QuestPDF.ReportSample.Layouts
{ {
container container
.ShowEntire() .ShowEntire()
.Column(column => .Stack(stack =>
{ {
column.Spacing(5); stack.Spacing(5);
column.Item().Element(PhotoWithMaps); stack.Item().Element(PhotoWithMaps);
column.Item().Element(PhotoDetails); stack.Item().Element(PhotoDetails);
}); });
} }
@@ -30,14 +30,14 @@ namespace QuestPDF.ReportSample.Layouts
container container
.Row(row => .Row(row =>
{ {
row.RelativeItem(2).AspectRatio(4 / 3f).Component<ImagePlaceholder>(); row.RelativeColumn(2).AspectRatio(4 / 3f).Background(Colors.Grey.Lighten3);
row.RelativeItem().PaddingLeft(5).Column(column => row.RelativeColumn().PaddingLeft(5).Stack(stack =>
{ {
column.Spacing(7f); stack.Spacing(7f);
column.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>(); stack.Item().AspectRatio(4 / 3f).Background(Colors.Grey.Lighten3);
column.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>(); stack.Item().AspectRatio(4 / 3f).Background(Colors.Grey.Lighten3);
}); });
}); });
} }
@@ -48,13 +48,13 @@ namespace QuestPDF.ReportSample.Layouts
{ {
grid.Columns(6); grid.Columns(6);
grid.Item().LabelCell().Text("Date"); grid.Item().LabelCell().Text("Date", Typography.Normal);
grid.Item(2).ValueCell().Text(Model.Date?.ToString("g") ?? string.Empty); grid.Item(2).ValueCell().Text(Model.Date?.ToString("g") ?? string.Empty, Typography.Normal);
grid.Item().LabelCell().Text("Location"); grid.Item().LabelCell().Text("Location", Typography.Normal);
grid.Item(2).ValueCell().Text(Model.Location.Format()); grid.Item(2).ValueCell().Text(Model.Location.Format(), Typography.Normal);
grid.Item().LabelCell().Text("Comments"); grid.Item().LabelCell().Text("Comments", Typography.Normal);
grid.Item(5).ValueCell().Text(Model.Comments); grid.Item(5).ValueCell().Text(Model.Comments, Typography.Normal);
}); });
} }
} }
@@ -1,4 +1,3 @@
using System.Linq;
using QuestPDF.Fluent; using QuestPDF.Fluent;
using QuestPDF.Helpers; using QuestPDF.Helpers;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
@@ -21,21 +20,21 @@ namespace QuestPDF.ReportSample.Layouts
.Decoration(decoration => .Decoration(decoration =>
{ {
decoration decoration
.Before() .Header()
.PaddingBottom(5) .PaddingBottom(5)
.Text(Model.Title, Typography.Headline); .Text(Model.Title, Typography.Headline);
decoration.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Column(column => decoration.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Stack(stack =>
{ {
foreach (var part in Model.Parts) foreach (var part in Model.Parts)
{ {
column.Item().EnsureSpace(25).Row(row => stack.Item().EnsureSpace(25).Row(row =>
{ {
row.ConstantItem(150).LabelCell().Text(part.Label); row.ConstantColumn(150).LabelCell().Text(part.Label, Typography.Normal);
var frame = row.RelativeItem().ValueCell(); var frame = row.RelativeColumn().ValueCell();
if (part is ReportSectionText text) if (part is ReportSectionText text)
frame.ShowEntire().Text(text.Text); frame.ShowEntire().Text(text.Text, Typography.Normal);
if (part is ReportSectionMap map) if (part is ReportSectionMap map)
frame.Element(x => MapElement(x, map)); frame.Element(x => MapElement(x, map));
@@ -50,24 +49,24 @@ namespace QuestPDF.ReportSample.Layouts
void MapElement(IContainer container, ReportSectionMap model) void MapElement(IContainer container, ReportSectionMap model)
{ {
if (model.Location == null) if (model.ImageSource == null || model.Location == null)
{ {
container.Text("No location provided"); container.Text("No location provided", Typography.Normal);
return; return;
} }
container.ShowEntire().Column(column => container.ShowEntire().Stack(stack =>
{ {
column.Spacing(5); stack.Spacing(5);
column.Item().MaxWidth(250).AspectRatio(4 / 3f).Component<ImagePlaceholder>(); stack.Item().MaxWidth(250).AspectRatio(4 / 3f).Background(Colors.Grey.Lighten3);
column.Item().Text(model.Location.Format()); stack.Item().Text(model.Location.Format(), Typography.Normal);
}); });
} }
void PhotosElement(IContainer container, ReportSectionPhotos model) void PhotosElement(IContainer container, ReportSectionPhotos model)
{ {
if (model.PhotoCount == 0) if (model.Photos.Count == 0)
{ {
container.Text("No photos", Typography.Normal); container.Text("No photos", Typography.Normal);
return; return;
@@ -78,10 +77,7 @@ namespace QuestPDF.ReportSample.Layouts
grid.Spacing(5); grid.Spacing(5);
grid.Columns(3); grid.Columns(3);
Enumerable model.Photos.ForEach(x => grid.Item().AspectRatio(4 / 3f).Background(Colors.Grey.Lighten3));
.Range(0, model.PhotoCount)
.ToList()
.ForEach(x => grid.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>());
}); });
} }
} }
+18 -18
View File
@@ -27,8 +27,6 @@ namespace QuestPDF.ReportSample.Layouts
container container
.Page(page => .Page(page =>
{ {
page.DefaultTextStyle(Typography.Normal);
page.MarginVertical(40); page.MarginVertical(40);
page.MarginHorizontal(50); page.MarginHorizontal(50);
@@ -39,6 +37,8 @@ namespace QuestPDF.ReportSample.Layouts
page.Footer().AlignCenter().Text(text => page.Footer().AlignCenter().Text(text =>
{ {
text.DefaultTextStyle(Typography.Normal);
text.CurrentPageNumber(); text.CurrentPageNumber();
text.Span(" / "); text.Span(" / ");
text.TotalPages(); text.TotalPages();
@@ -48,19 +48,19 @@ namespace QuestPDF.ReportSample.Layouts
private void ComposeHeader(IContainer container) private void ComposeHeader(IContainer container)
{ {
container.Column(column => container.Stack(stack =>
{ {
column.Item().Row(row => stack.Item().Row(row =>
{ {
row.Spacing(50); row.Spacing(50);
row.RelativeItem().PaddingTop(-10).Text(Model.Title, Typography.Title); row.RelativeColumn().PaddingTop(-10).Text(Model.Title, Typography.Title);
row.ConstantItem(90).ExternalLink("https://www.questpdf.com").MaxHeight(30).Component<ImagePlaceholder>(); row.ConstantColumn(150).ExternalLink("https://www.questpdf.com").Background(Colors.Grey.Lighten3);
}); });
column.Item().ShowOnce().PaddingVertical(15).Border(1f).BorderColor(Colors.Grey.Lighten1).ExtendHorizontal(); stack.Item().ShowOnce().PaddingVertical(15).Border(1f).BorderColor(Colors.Grey.Lighten1).ExtendHorizontal();
column.Item().ShowOnce().Grid(grid => stack.Item().ShowOnce().Grid(grid =>
{ {
grid.Columns(2); grid.Columns(2);
grid.Spacing(5); grid.Spacing(5);
@@ -69,8 +69,8 @@ namespace QuestPDF.ReportSample.Layouts
{ {
grid.Item().Text(text => grid.Item().Text(text =>
{ {
text.Span($"{field.Label}: ", TextStyle.Default.SemiBold()); text.Span($"{field.Label}: ", Typography.Normal.SemiBold());
text.Span(field.Value); text.Span(field.Value, Typography.Normal);
}); });
} }
}); });
@@ -79,22 +79,22 @@ namespace QuestPDF.ReportSample.Layouts
void ComposeContent(IContainer container) void ComposeContent(IContainer container)
{ {
container.PaddingVertical(20).Column(column => container.PaddingVertical(20).Stack(stack =>
{ {
column.Spacing(20); stack.Spacing(20);
column.Item().Component(new TableOfContentsTemplate(Model.Sections)); stack.Item().Component(new TableOfContentsTemplate(Model.Sections));
column.Item().PageBreak(); stack.Item().PageBreak();
foreach (var section in Model.Sections) foreach (var section in Model.Sections)
column.Item().Location(section.Title).Component(new SectionTemplate(section)); stack.Item().Location(section.Title).Component(new SectionTemplate(section));
column.Item().PageBreak(); stack.Item().PageBreak();
column.Item().Location("Photos"); stack.Item().Location("Photos");
foreach (var photo in Model.Photos) foreach (var photo in Model.Photos)
column.Item().Component(new PhotoTemplate(photo)); stack.Item().Component(new PhotoTemplate(photo));
}); });
} }
} }
@@ -19,18 +19,18 @@ namespace QuestPDF.ReportSample.Layouts
.Decoration(decoration => .Decoration(decoration =>
{ {
decoration decoration
.Before() .Header()
.PaddingBottom(5) .PaddingBottom(5)
.Text("Table of contents", Typography.Headline); .Text("Table of contents", Typography.Headline);
decoration.Content().Column(column => decoration.Content().Stack(stack =>
{ {
column.Spacing(5); stack.Spacing(5);
for (var i = 0; i < Sections.Count; i++) for (var i = 0; i < Sections.Count; i++)
column.Item().Element(c => DrawLink(c, i+1, Sections[i].Title)); stack.Item().Element(c => DrawLink(c, i+1, Sections[i].Title));
column.Item().Element(c => DrawLink(c, Sections.Count+1, "Photos")); stack.Item().Element(c => DrawLink(c, Sections.Count+1, "Photos"));
}); });
}); });
} }
@@ -41,9 +41,9 @@ namespace QuestPDF.ReportSample.Layouts
.InternalLink(locationName) .InternalLink(locationName)
.Row(row => .Row(row =>
{ {
row.ConstantItem(25).Text($"{number}."); row.ConstantColumn(25).Text($"{number}.", Typography.Normal);
row.RelativeItem().Text(locationName); row.RelativeColumn().Text(locationName, Typography.Normal);
row.ConstantItem(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName)); row.ConstantColumn(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName, Typography.Normal));
}); });
} }
} }
+7 -1
View File
@@ -44,16 +44,22 @@ namespace QuestPDF.ReportSample
public class ReportSectionMap : ReportSectionElement public class ReportSectionMap : ReportSectionElement
{ {
public Func<Size, byte[]> ImageSource { get; set; }
public Location Location { get; set; } public Location Location { get; set; }
} }
public class ReportSectionPhotos : ReportSectionElement public class ReportSectionPhotos : ReportSectionElement
{ {
public int PhotoCount { get; set; } public List<byte[]> Photos { get; set; }
} }
public class ReportPhoto public class ReportPhoto
{ {
public byte[] PhotoData { get; set; }
public Func<Size, byte[]> MapDetailsSource { get; set; }
public Func<Size, byte[]> MapContextSource { get; set; }
public Location Location { get; set; } public Location Location { get; set; }
public DateTime? Date { get; set; } public DateTime? Date { get; set; }
+2 -7
View File
@@ -11,13 +11,12 @@ using NUnit.Framework;
using QuestPDF.Drawing; using QuestPDF.Drawing;
using QuestPDF.Drawing.Proxy; using QuestPDF.Drawing.Proxy;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using QuestPDF.ReportSample.Layouts; using QuestPDF.ReportSample.Layouts;
namespace QuestPDF.ReportSample namespace QuestPDF.ReportSample
{ {
[SimpleJob(RunStrategy.Monitoring, launchCount: 0, warmupCount: 1, targetCount: 256)] [SimpleJob(RunStrategy.Monitoring, launchCount: 0, warmupCount: 1, targetCount: 100)]
[MinColumn, MaxColumn, MeanColumn, MedianColumn] [MinColumn, MaxColumn, MeanColumn, MedianColumn]
public class PerformanceTests public class PerformanceTests
{ {
@@ -28,8 +27,6 @@ namespace QuestPDF.ReportSample
[Test] [Test]
public void Run() public void Run()
{ {
ImagePlaceholder.Solid = true;
var configuration = ManualConfig var configuration = ManualConfig
.Create(DefaultConfig.Instance) .Create(DefaultConfig.Instance)
.WithOptions(ConfigOptions.DisableOptimizationsValidator); .WithOptions(ConfigOptions.DisableOptimizationsValidator);
@@ -40,8 +37,6 @@ namespace QuestPDF.ReportSample
[IterationSetup] [IterationSetup]
public void GenerateReportData() public void GenerateReportData()
{ {
ImagePlaceholder.Solid = true;
var model = DataSource.GetReport(); var model = DataSource.GetReport();
var report = new StandardReport(model); var report = new StandardReport(model);
Metadata = report.GetMetadata(); Metadata = report.GetMetadata();
@@ -56,7 +51,7 @@ namespace QuestPDF.ReportSample
var sw = new Stopwatch(); var sw = new Stopwatch();
sw.Start(); sw.Start();
Content.VisitChildren(x => Content.HandleVisitor(x =>
{ {
if (x is ICacheable) if (x is ICacheable)
x.CreateProxy(y => new CacheProxy(y)); x.CreateProxy(y => new CacheProxy(y));
+1 -12
View File
@@ -22,28 +22,17 @@ namespace QuestPDF.ReportSample
} }
[Test] [Test]
public void GenerateAndShowPdf() public void GenerateAndShow()
{ {
//ImagePlaceholder.Solid = true;
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.pdf"); var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.pdf");
Report.GeneratePdf(path); Report.GeneratePdf(path);
Process.Start("explorer.exe", path);
}
[Test]
public void GenerateAndShowXps()
{
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.xps");
Report.GenerateXps(path);
Process.Start("explorer.exe", path); Process.Start("explorer.exe", path);
} }
[Test] [Test]
public void Profile() public void Profile()
{ {
ImagePlaceholder.Solid = true;
var container = new DocumentContainer(); var container = new DocumentContainer();
Report.Compose(container); Report.Compose(container);
var content = container.Compose(); var content = container.Compose();
+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 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 Headline => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Medium).Size(16).SemiBold();
public static TextStyle Normal => TextStyle.Default.FontType(Fonts.Verdana).Color(Colors.Black).Size(10).LineHeight(1.2f); public static TextStyle Normal => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Black).Size(11).LineHeight(1.1f);
} }
} }
+4 -4
View File
@@ -10,13 +10,13 @@ namespace QuestPDF.UnitTests
public class BoxTests public class BoxTests
{ {
[Test] [Test]
public void Measure() => SimpleContainerTests.Measure<MinimalBox>(); public void Measure() => SimpleContainerTests.Measure<Box>();
[Test] [Test]
public void Draw_Wrap() public void Draw_Wrap()
{ {
TestPlan TestPlan
.For(x => new MinimalBox .For(x => new Box
{ {
Child = x.CreateChild() Child = x.CreateChild()
}) })
@@ -29,7 +29,7 @@ namespace QuestPDF.UnitTests
public void Measure_PartialRender() public void Measure_PartialRender()
{ {
TestPlan TestPlan
.For(x => new MinimalBox .For(x => new Box
{ {
Child = x.CreateChild() Child = x.CreateChild()
}) })
@@ -43,7 +43,7 @@ namespace QuestPDF.UnitTests
public void Measure_FullRender() public void Measure_FullRender()
{ {
TestPlan TestPlan
.For(x => new MinimalBox .For(x => new Box
{ {
Child = x.CreateChild() Child = x.CreateChild()
}) })
-88
View File
@@ -9,8 +9,6 @@ namespace QuestPDF.UnitTests
[TestFixture] [TestFixture]
public class ConstrainedTests public class ConstrainedTests
{ {
#region Height
[Test] [Test]
public void Measure_MinHeight_ExpectWrap() public void Measure_MinHeight_ExpectWrap()
{ {
@@ -90,91 +88,5 @@ namespace QuestPDF.UnitTests
.ExpectChildMeasure(new Size(400, 100), SpacePlan.Wrap()) .ExpectChildMeasure(new Size(400, 100), SpacePlan.Wrap())
.CheckMeasureResult(SpacePlan.Wrap()); .CheckMeasureResult(SpacePlan.Wrap());
} }
#endregion
#region Width
[Test]
public void Measure_MinWidth_ExpectWrap()
{
TestPlan
.For(x => new Constrained
{
MinWidth = 100
})
.MeasureElement(new Size(50, 400))
.CheckMeasureResult(SpacePlan.Wrap());
}
[Test]
public void Measure_MinWidth_ExtendHeight()
{
TestPlan
.For(x => new Constrained
{
MinWidth = 100,
Child = x.CreateChild()
})
.MeasureElement(new Size(200, 400))
.ExpectChildMeasure(new Size(200, 400), SpacePlan.FullRender(50, 400))
.CheckMeasureResult(SpacePlan.FullRender(100, 400));
}
[Test]
public void Measure_MinWidth_PassHeight()
{
TestPlan
.For(x => new Constrained
{
MinWidth = 100,
Child = x.CreateChild()
})
.MeasureElement(new Size(200, 400))
.ExpectChildMeasure(new Size(200, 400), SpacePlan.FullRender(150, 400))
.CheckMeasureResult(SpacePlan.FullRender(150, 400));
}
[Test]
public void Measure_MaxWidth_Empty()
{
TestPlan
.For(x => new Constrained
{
MaxWidth = 100
})
.MeasureElement(new Size(150, 400))
.CheckMeasureResult(SpacePlan.FullRender(0, 0));
}
[Test]
public void Measure_MaxWidth_PartialRender()
{
TestPlan
.For(x => new Constrained
{
MaxWidth = 100,
Child = x.CreateChild()
})
.MeasureElement(new Size(200, 400))
.ExpectChildMeasure(new Size(100, 400), SpacePlan.PartialRender(75, 400))
.CheckMeasureResult(SpacePlan.PartialRender(75, 400));
}
[Test]
public void Measure_MaxWidth_ExpectWrap()
{
TestPlan
.For(x => new Constrained
{
MaxWidth = 100,
Child = x.CreateChild()
})
.MeasureElement(new Size(200, 400))
.ExpectChildMeasure(new Size(100, 400), SpacePlan.Wrap())
.CheckMeasureResult(SpacePlan.Wrap());
}
#endregion
} }
} }
+80 -83
View File
@@ -9,27 +9,35 @@ namespace QuestPDF.UnitTests
[TestFixture] [TestFixture]
public class DecorationTests public class DecorationTests
{ {
private Decoration CreateDecoration(TestPlan testPlan)
{
return new Decoration
{
Before = testPlan.CreateChild("before"),
Content = testPlan.CreateChild("content"),
After = testPlan.CreateChild("after"),
};
}
#region Measure #region Measure
[Test] [Test]
public void Measure_ReturnsWrap_WhenBeforeReturnsWrap() public void Measure_ReturnsWrap_WhenDecorationReturnsWrap()
{ {
TestPlan TestPlan
.For(CreateDecoration) .For(x => new BinaryDecoration
{
Type = DecorationType.Append,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.MeasureElement(new Size(400, 300)) .MeasureElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.Wrap()) .ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.Wrap())
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50)) .CheckMeasureResult(SpacePlan.Wrap());
.ExpectChildMeasure("content", new Size(400, 250), SpacePlan.FullRender(100, 100)) }
[Test]
public void Measure_ReturnsWrap_WhenDecorationReturnsPartialRender()
{
TestPlan
.For(x => new BinaryDecoration
{
Type = DecorationType.Append,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.PartialRender(300, 200))
.CheckMeasureResult(SpacePlan.Wrap()); .CheckMeasureResult(SpacePlan.Wrap());
} }
@@ -37,100 +45,89 @@ namespace QuestPDF.UnitTests
public void Measure_ReturnsWrap_WhenContentReturnsWrap() public void Measure_ReturnsWrap_WhenContentReturnsWrap()
{ {
TestPlan TestPlan
.For(CreateDecoration) .For(x => new BinaryDecoration
{
Type = DecorationType.Append,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.MeasureElement(new Size(400, 300)) .MeasureElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(100, 50)) .ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.Wrap()) .ExpectChildMeasure("content", new Size(400, 200), SpacePlan.Wrap())
.CheckMeasureResult(SpacePlan.Wrap()); .CheckMeasureResult(SpacePlan.Wrap());
} }
[Test] [Test]
public void Measure_ReturnsWrap_WhenAfterReturnsWrap() public void Measure_ReturnsPartialRender_WhenContentReturnsPartialRender()
{ {
TestPlan TestPlan
.For(CreateDecoration) .For(x => new BinaryDecoration
{
Type = DecorationType.Append,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.MeasureElement(new Size(400, 300)) .MeasureElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(100, 50)) .ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.Wrap()) .ExpectChildMeasure("content", new Size(400, 200), SpacePlan.PartialRender(200, 150))
.ExpectChildMeasure("content", new Size(400, 250), SpacePlan.FullRender(100, 100)) .CheckMeasureResult(SpacePlan.PartialRender(400, 250));
.CheckMeasureResult(SpacePlan.Wrap());
} }
[Test] [Test]
public void Measure_ReturnsWrap_WhenBeforeReturnsPartialRender() public void Measure_ReturnsFullRender_WhenContentReturnsFullRender()
{ {
TestPlan TestPlan
.For(CreateDecoration) .For(x => new BinaryDecoration
{
Type = DecorationType.Append,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.MeasureElement(new Size(400, 300)) .MeasureElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.PartialRender(100, 50)) .ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50)) .ExpectChildMeasure("content", new Size(400, 200), SpacePlan.FullRender(200, 150))
.ExpectChildMeasure("content", new Size(400, 250), SpacePlan.FullRender(100, 100)) .CheckMeasureResult(SpacePlan.FullRender(400, 250));
.CheckMeasureResult(SpacePlan.Wrap());
}
[Test]
public void Measure_ReturnsWrap_WhenAfterReturnsPartialRender()
{
TestPlan
.For(CreateDecoration)
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.PartialRender(100, 50))
.ExpectChildMeasure("content", new Size(400, 250), SpacePlan.FullRender(100, 100))
.CheckMeasureResult(SpacePlan.Wrap());
}
[Test]
public void Measure_ReturnsWrap_WhenContentReturnsPartialRender()
{
TestPlan
.For(CreateDecoration)
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.PartialRender(150, 100))
.CheckMeasureResult(SpacePlan.PartialRender(150, 200));
}
[Test]
public void Measure_ReturnsWrap_WhenContentReturnsFullRender()
{
TestPlan
.For(CreateDecoration)
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.FullRender(150, 100))
.CheckMeasureResult(SpacePlan.FullRender(150, 200));
} }
#endregion #endregion
#region Draw #region Draw
[Test]
public void Draw_Prepend()
{
TestPlan
.For(x => new BinaryDecoration
{
Type = DecorationType.Prepend,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100))
.ExpectChildDraw("decoration", new Size(400, 100))
.ExpectCanvasTranslate(0, 100)
.ExpectChildDraw("content", new Size(400, 200))
.ExpectCanvasTranslate(0, -100)
.CheckDrawResult();
}
[Test] [Test]
public void Draw_Append() public void Draw_Append()
{ {
TestPlan TestPlan
.For(CreateDecoration) .For(x => new BinaryDecoration
{
Type = DecorationType.Append,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.DrawElement(new Size(400, 300)) .DrawElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(200, 40)) .ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(200, 60)) .ExpectChildDraw("content", new Size(400, 200))
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.FullRender(300, 100)) .ExpectCanvasTranslate(0, 200)
.ExpectChildDraw("decoration", new Size(400, 100))
.ExpectCanvasTranslate(0, 0) .ExpectCanvasTranslate(0, -200)
.ExpectChildDraw("before", new Size(300, 40))
.ExpectCanvasTranslate(0, 0)
.ExpectCanvasTranslate(0, 40)
.ExpectChildDraw("content", new Size(300, 100))
.ExpectCanvasTranslate(0, -40)
.ExpectCanvasTranslate(0, 140)
.ExpectChildDraw("after", new Size(300, 60))
.ExpectCanvasTranslate(0, -140)
.CheckDrawResult(); .CheckDrawResult();
} }
+1 -1
View File
@@ -66,7 +66,7 @@ namespace QuestPDF.UnitTests
.ExpectCanvasDrawImage(Position.Zero, new Size(400, 300)) .ExpectCanvasDrawImage(Position.Zero, new Size(400, 300))
.CheckDrawResult(); .CheckDrawResult();
passedSize.Should().BeEquivalentTo(new Size(400, 300)); passedSize.Should().Be(new Size(400, 300));
} }
byte[] GenerateImage(Size size) byte[] GenerateImage(Size size)
+54 -54
View File
@@ -39,26 +39,26 @@ namespace QuestPDF.UnitTests
// assert // assert
var expected = new Container(); var expected = new Container();
expected.Column(column => expected.Stack(stack =>
{ {
column.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeItem(6).Element(childA); row.RelativeColumn(6).Element(childA);
row.RelativeItem(4).Element(childB); row.RelativeColumn(4).Element(childB);
row.RelativeItem(2); row.RelativeColumn(2);
}); });
column.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeItem(4).Element(childC); row.RelativeColumn(4).Element(childC);
row.RelativeItem(2).Element(childD); row.RelativeColumn(2).Element(childD);
row.RelativeItem(6); row.RelativeColumn(6);
}); });
column.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeItem(8).Element(childE); row.RelativeColumn(8).Element(childE);
row.RelativeItem(4); row.RelativeColumn(4);
}); });
}); });
@@ -93,29 +93,29 @@ namespace QuestPDF.UnitTests
// assert // assert
var expected = new Container(); var expected = new Container();
expected.Column(column => expected.Stack(stack =>
{ {
column.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeItem(1); row.RelativeColumn(1);
row.RelativeItem(6).Element(childA); row.RelativeColumn(6).Element(childA);
row.RelativeItem(4).Element(childB); row.RelativeColumn(4).Element(childB);
row.RelativeItem(1); row.RelativeColumn(1);
}); });
column.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeItem(3); row.RelativeColumn(3);
row.RelativeItem(4).Element(childC); row.RelativeColumn(4).Element(childC);
row.RelativeItem(2).Element(childD); row.RelativeColumn(2).Element(childD);
row.RelativeItem(3); row.RelativeColumn(3);
}); });
column.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeItem(2); row.RelativeColumn(2);
row.RelativeItem(8).Element(childE); row.RelativeColumn(8).Element(childE);
row.RelativeItem(2); row.RelativeColumn(2);
}); });
}); });
@@ -150,26 +150,26 @@ namespace QuestPDF.UnitTests
// assert // assert
var expected = new Container(); var expected = new Container();
expected.Column(column => expected.Stack(stack =>
{ {
column.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeItem(2); row.RelativeColumn(2);
row.RelativeItem(6).Element(childA); row.RelativeColumn(6).Element(childA);
row.RelativeItem(4).Element(childB); row.RelativeColumn(4).Element(childB);
}); });
column.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeItem(6); row.RelativeColumn(6);
row.RelativeItem(4).Element(childC); row.RelativeColumn(4).Element(childC);
row.RelativeItem(2).Element(childD); row.RelativeColumn(2).Element(childD);
}); });
column.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeItem(4); row.RelativeColumn(4);
row.RelativeItem(8).Element(childE); row.RelativeColumn(8).Element(childE);
}); });
}); });
@@ -210,36 +210,36 @@ namespace QuestPDF.UnitTests
// assert // assert
var expected = new Container(); var expected = new Container();
expected.Column(column => expected.Stack(stack =>
{ {
column.Spacing(20); stack.Spacing(20);
column.Item().Row(row => stack.Item().Row(row =>
{ {
row.Spacing(30); row.Spacing(30);
row.RelativeItem(3); row.RelativeColumn(3);
row.RelativeItem(5).Element(childA); row.RelativeColumn(5).Element(childA);
row.RelativeItem(5).Element(childB); row.RelativeColumn(5).Element(childB);
row.RelativeItem(3); row.RelativeColumn(3);
}); });
column.Item().Row(row => stack.Item().Row(row =>
{ {
row.Spacing(30); row.Spacing(30);
row.RelativeItem(3); row.RelativeColumn(3);
row.RelativeItem(10).Element(childC); row.RelativeColumn(10).Element(childC);
row.RelativeItem(3); row.RelativeColumn(3);
}); });
column.Item().Row(row => stack.Item().Row(row =>
{ {
row.Spacing(30); row.Spacing(30);
row.RelativeItem(2); row.RelativeColumn(2);
row.RelativeItem(12).Element(childD); row.RelativeColumn(12).Element(childD);
row.RelativeItem(2); row.RelativeColumn(2);
}); });
}); });
+267
View File
@@ -0,0 +1,267 @@
using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class RowTests
{
#region Measure
[Test]
public void Measure_ReturnsWrap_WhenLeftChildReturnsWrap()
{
TestPlan
.For(x => new BinaryRow
{
Left = x.CreateChild("left"),
Right = x.CreateChild("right")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.Wrap())
.CheckMeasureResult(SpacePlan.Wrap());
}
[Test]
public void Measure_ReturnsWrap_WhenRightChildReturnsWrap()
{
TestPlan
.For(x => new BinaryRow
{
Left = x.CreateChild("left"),
Right = x.CreateChild("right")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.FullRender(250, 150))
.ExpectChildMeasure("right", new Size(150, 300), SpacePlan.Wrap())
.CheckMeasureResult(SpacePlan.Wrap());
}
[Test]
public void Measure_ReturnsPartialRender_WhenLeftChildReturnsPartialRender()
{
TestPlan
.For(x => new BinaryRow
{
Left = x.CreateChild("left"),
Right = x.CreateChild("right")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.PartialRender(250, 150))
.ExpectChildMeasure("right", new Size(150, 300), SpacePlan.FullRender(100, 100))
.CheckMeasureResult(SpacePlan.PartialRender(350, 150));
}
[Test]
public void Measure_ReturnsPartialRender_WhenRightChildReturnsPartialRender()
{
TestPlan
.For(x => new BinaryRow
{
Left = x.CreateChild("left"),
Right = x.CreateChild("right")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.FullRender(250, 150))
.ExpectChildMeasure("right", new Size(150, 300), SpacePlan.PartialRender(100, 100))
.CheckMeasureResult(SpacePlan.PartialRender(350, 150));
}
[Test]
public void Measure_ReturnsFullRender_WhenBothChildrenReturnFullRender()
{
TestPlan
.For(x => new BinaryRow
{
Left = x.CreateChild("left"),
Right = x.CreateChild("right")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.FullRender(200, 150))
.ExpectChildMeasure("right", new Size(200, 300), SpacePlan.FullRender(100, 100))
.CheckMeasureResult(SpacePlan.FullRender(300, 150));
}
#endregion
#region Draw
[Test]
public void Draw()
{
TestPlan
.For(x => new BinaryRow
{
Left = x.CreateChild("left"),
Right = x.CreateChild("right")
})
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.FullRender(250, 150))
.ExpectChildDraw("left", new Size(250, 300))
.ExpectCanvasTranslate(250, 0)
.ExpectChildDraw("right", new Size(150, 300))
.ExpectCanvasTranslate(-250, 0)
.CheckDrawResult();
}
#endregion
#region Structure
[Test]
public void Structure_RelativeColumnsHandling()
{
// arrange
var childA = TestPlan.CreateUniqueElement();
var childB = TestPlan.CreateUniqueElement();
var childC = TestPlan.CreateUniqueElement();
var childD = TestPlan.CreateUniqueElement();
var childE = TestPlan.CreateUniqueElement();
const int spacing = 25;
var availableSpace = new Size(1100, 400);
// act
var value = new Container();
value.Row(row =>
{
row.Spacing(spacing);
row.ConstantColumn(150).Element(childA);
row.ConstantColumn(250).Element(childB);
row.RelativeColumn(1).Element(childC);
row.RelativeColumn(2).Element(childD);
row.RelativeColumn(3).Element(childE);
});
// assert
var expected = new Container();
expected.Row(row =>
{
row.Spacing(spacing);
row.ConstantColumn(150).Element(childA);
row.ConstantColumn(250).Element(childB);
row.ConstantColumn(100).Element(childC);
row.ConstantColumn(200).Element(childD);
row.ConstantColumn(300).Element(childE);
});
TestPlan.CompareOperations(value, expected, availableSpace);
}
[Test]
public void Structure_Tree()
{
// arrange
var childA = TestPlan.CreateUniqueElement();
var childB = TestPlan.CreateUniqueElement();
var childC = TestPlan.CreateUniqueElement();
var childD = TestPlan.CreateUniqueElement();
var childE = TestPlan.CreateUniqueElement();
const int spacing = 25;
var availableSpace = new Size(1200, 400);
// act
var value = new Container();
value.Row(row =>
{
row.Spacing(spacing);
row.ConstantColumn(150).Element(childA);
row.ConstantColumn(200).Element(childB);
row.ConstantColumn(250).Element(childC);
row.RelativeColumn(2).Element(childD);
row.RelativeColumn(3).Element(childE);
});
// assert
var expected = new BinaryRow
{
Left = new BinaryRow
{
Left = new BinaryRow
{
Left = new Constrained
{
MinWidth = 150,
MaxWidth = 150,
Child = childA
},
Right = new Constrained
{
MinWidth = 25,
MaxWidth = 25
}
},
Right = new BinaryRow
{
Left = new Constrained
{
MinWidth = 200,
MaxWidth = 200,
Child = childB
},
Right = new Constrained
{
MinWidth = 25,
MaxWidth = 25
}
}
},
Right = new BinaryRow
{
Left = new BinaryRow
{
Left = new Constrained
{
MinWidth = 250,
MaxWidth = 250,
Child = childC
},
Right = new Constrained
{
MinWidth = 25,
MaxWidth = 25
}
},
Right = new BinaryRow
{
Left = new Constrained
{
MinWidth = 200,
MaxWidth = 200,
Child = childD
},
Right = new BinaryRow
{
Left = new Constrained
{
MinWidth = 25,
MaxWidth = 25
},
Right = new Constrained
{
MinWidth = 300,
MaxWidth = 300,
Child = childE
}
}
}
}
};
TestPlan.CompareOperations(value, expected, availableSpace);
}
#endregion
}
}
@@ -1,5 +1,4 @@
using System.Linq; using NUnit.Framework;
using NUnit.Framework;
using QuestPDF.Drawing; using QuestPDF.Drawing;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Fluent; using QuestPDF.Fluent;
@@ -9,40 +8,19 @@ using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests namespace QuestPDF.UnitTests
{ {
[TestFixture] [TestFixture]
public class ColumnTests public class StackTests
{ {
private Column CreateColumnWithTwoItems(TestPlan testPlan)
{
return new Column
{
Items =
{
new ColumnItem
{
Child = testPlan.CreateChild("first")
},
new ColumnItem
{
Child = testPlan.CreateChild("second")
}
}
};
}
private Column CreateColumnWithTwoItemsWhereFirstIsFullyRendered(TestPlan testPlan)
{
var column = CreateColumnWithTwoItems(testPlan);
column.Items.First().IsRendered = true;
return column;
}
#region Measure #region Measure
[Test] [Test]
public void Measure_ReturnsWrap_WhenFirstChildWraps() public void Measure_ReturnsWrap_WhenFirstChildWraps()
{ {
TestPlan TestPlan
.For(CreateColumnWithTwoItems) .For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.MeasureElement(new Size(400, 300)) .MeasureElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.Wrap()) .ExpectChildMeasure("first", new Size(400, 300), SpacePlan.Wrap())
.CheckMeasureResult(SpacePlan.Wrap()); .CheckMeasureResult(SpacePlan.Wrap());
@@ -52,7 +30,11 @@ namespace QuestPDF.UnitTests
public void Measure_ReturnsPartialRender_WhenFirstChildReturnsPartialRender() public void Measure_ReturnsPartialRender_WhenFirstChildReturnsPartialRender()
{ {
TestPlan TestPlan
.For(CreateColumnWithTwoItems) .For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.MeasureElement(new Size(400, 300)) .MeasureElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.PartialRender(300, 200)) .ExpectChildMeasure("first", new Size(400, 300), SpacePlan.PartialRender(300, 200))
.CheckMeasureResult(SpacePlan.PartialRender(300, 200)); .CheckMeasureResult(SpacePlan.PartialRender(300, 200));
@@ -62,7 +44,11 @@ namespace QuestPDF.UnitTests
public void Measure_ReturnsPartialRender_WhenSecondChildWraps() public void Measure_ReturnsPartialRender_WhenSecondChildWraps()
{ {
TestPlan TestPlan
.For(CreateColumnWithTwoItems) .For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.MeasureElement(new Size(400, 300)) .MeasureElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100)) .ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.Wrap()) .ExpectChildMeasure("second", new Size(400, 200), SpacePlan.Wrap())
@@ -73,7 +59,11 @@ namespace QuestPDF.UnitTests
public void Measure_ReturnsPartialRender_WhenSecondChildReturnsPartialRender() public void Measure_ReturnsPartialRender_WhenSecondChildReturnsPartialRender()
{ {
TestPlan TestPlan
.For(CreateColumnWithTwoItems) .For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.MeasureElement(new Size(400, 300)) .MeasureElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100)) .ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.PartialRender(300, 150)) .ExpectChildMeasure("second", new Size(400, 200), SpacePlan.PartialRender(300, 150))
@@ -84,13 +74,33 @@ namespace QuestPDF.UnitTests
public void Measure_ReturnsFullRender_WhenSecondChildReturnsFullRender() public void Measure_ReturnsFullRender_WhenSecondChildReturnsFullRender()
{ {
TestPlan TestPlan
.For(CreateColumnWithTwoItems) .For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.MeasureElement(new Size(400, 300)) .MeasureElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100)) .ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.FullRender(100, 50)) .ExpectChildMeasure("second", new Size(400, 200), SpacePlan.FullRender(100, 50))
.CheckMeasureResult(SpacePlan.FullRender(200, 150)); .CheckMeasureResult(SpacePlan.FullRender(200, 150));
} }
[Test]
public void Measure_UsesEmpty_WhenFirstChildIsRendered()
{
TestPlan
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second"),
IsFirstRendered = true
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("second", new Size(400, 300), SpacePlan.FullRender(200, 300))
.CheckMeasureResult(SpacePlan.FullRender(200, 300));
}
#endregion #endregion
#region Draw #region Draw
@@ -99,7 +109,11 @@ namespace QuestPDF.UnitTests
public void Draw_WhenFirstChildWraps() public void Draw_WhenFirstChildWraps()
{ {
TestPlan TestPlan
.For(CreateColumnWithTwoItems) .For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.DrawElement(new Size(400, 300)) .DrawElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.Wrap()) .ExpectChildMeasure("first", new Size(400, 300), SpacePlan.Wrap())
.CheckDrawResult(); .CheckDrawResult();
@@ -109,12 +123,14 @@ namespace QuestPDF.UnitTests
public void Draw_WhenFirstChildPartiallyRenders() public void Draw_WhenFirstChildPartiallyRenders()
{ {
TestPlan TestPlan
.For(CreateColumnWithTwoItems) .For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.DrawElement(new Size(400, 300)) .DrawElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.PartialRender(200, 100)) .ExpectChildMeasure("first", new Size(400, 300), SpacePlan.PartialRender(200, 100))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("first", new Size(400, 100)) .ExpectChildDraw("first", new Size(400, 100))
.ExpectCanvasTranslate(0, 0)
.CheckDrawResult(); .CheckDrawResult();
} }
@@ -122,13 +138,15 @@ namespace QuestPDF.UnitTests
public void Draw_WhenFirstChildFullyRenders_AndSecondChildWraps() public void Draw_WhenFirstChildFullyRenders_AndSecondChildWraps()
{ {
TestPlan TestPlan
.For(CreateColumnWithTwoItems) .For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.DrawElement(new Size(400, 300)) .DrawElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100)) .ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.Wrap())
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("first", new Size(400, 100)) .ExpectChildDraw("first", new Size(400, 100))
.ExpectCanvasTranslate(0, 0) .ExpectChildMeasure("second", new Size(400, 200), SpacePlan.Wrap())
.CheckDrawResult(); .CheckDrawResult();
} }
@@ -136,13 +154,15 @@ namespace QuestPDF.UnitTests
public void Draw_WhenFirstChildFullyRenders_AndSecondChildPartiallyRenders() public void Draw_WhenFirstChildFullyRenders_AndSecondChildPartiallyRenders()
{ {
TestPlan TestPlan
.For(CreateColumnWithTwoItems) .For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.DrawElement(new Size(400, 300)) .DrawElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100)) .ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.PartialRender(250, 150))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("first", new Size(400, 100)) .ExpectChildDraw("first", new Size(400, 100))
.ExpectCanvasTranslate(0, 0) .ExpectChildMeasure("second", new Size(400, 200), SpacePlan.PartialRender(250, 150))
.ExpectCanvasTranslate(0, 100) .ExpectCanvasTranslate(0, 100)
.ExpectChildDraw("second", new Size(400, 150)) .ExpectChildDraw("second", new Size(400, 150))
.ExpectCanvasTranslate(0, -100) .ExpectCanvasTranslate(0, -100)
@@ -153,13 +173,15 @@ namespace QuestPDF.UnitTests
public void Draw_WhenFirstChildFullyRenders_AndSecondChildFullyRenders() public void Draw_WhenFirstChildFullyRenders_AndSecondChildFullyRenders()
{ {
TestPlan TestPlan
.For(CreateColumnWithTwoItems) .For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.DrawElement(new Size(400, 300)) .DrawElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100)) .ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.FullRender(250, 150))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("first", new Size(400, 100)) .ExpectChildDraw("first", new Size(400, 100))
.ExpectCanvasTranslate(0, 0) .ExpectChildMeasure("second", new Size(400, 200), SpacePlan.FullRender(250, 150))
.ExpectCanvasTranslate(0, 100) .ExpectCanvasTranslate(0, 100)
.ExpectChildDraw("second", new Size(400, 150)) .ExpectChildDraw("second", new Size(400, 150))
.ExpectCanvasTranslate(0, -100) .ExpectCanvasTranslate(0, -100)
@@ -170,13 +192,19 @@ namespace QuestPDF.UnitTests
public void Draw_UsesEmpty_WhenFirstChildIsRendered() public void Draw_UsesEmpty_WhenFirstChildIsRendered()
{ {
TestPlan TestPlan
.For(CreateColumnWithTwoItemsWhereFirstIsFullyRendered) .For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second"),
IsFirstRendered = true
})
.DrawElement(new Size(400, 300)) .DrawElement(new Size(400, 300))
.ExpectChildMeasure("second", new Size(400, 300), SpacePlan.PartialRender(200, 300)) .ExpectChildMeasure("second", new Size(400, 300), SpacePlan.PartialRender(200, 300))
.ExpectCanvasTranslate(0, 0) .ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("second", new Size(400, 300)) .ExpectChildDraw("second", new Size(400, 300))
.ExpectCanvasTranslate(0, 0) .ExpectCanvasTranslate(0, 0)
.CheckState<Column>(x => x.Items.First().IsRendered) .CheckState<BinaryStack>(x => x.IsFirstRendered)
.CheckDrawResult(); .CheckDrawResult();
} }
@@ -184,14 +212,97 @@ namespace QuestPDF.UnitTests
public void Draw_TogglesFirstRenderedFlag_WhenSecondFullyRenders() public void Draw_TogglesFirstRenderedFlag_WhenSecondFullyRenders()
{ {
TestPlan TestPlan
.For(CreateColumnWithTwoItemsWhereFirstIsFullyRendered) .For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second"),
IsFirstRendered = true
})
.DrawElement(new Size(400, 300)) .DrawElement(new Size(400, 300))
.ExpectChildMeasure("second", new Size(400, 300), SpacePlan.FullRender(200, 300)) .ExpectChildMeasure("second", new Size(400, 300), SpacePlan.FullRender(200, 300))
.ExpectCanvasTranslate(0, 0) .ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("second", new Size(400, 300)) .ExpectChildDraw("second", new Size(400, 300))
.ExpectCanvasTranslate(0, 0) .ExpectCanvasTranslate(0, 0)
.CheckDrawResult() .CheckDrawResult()
.CheckState<Column>(x => !x.Items.First().IsRendered); .CheckState<BinaryStack>(x => !x.IsFirstRendered);
}
#endregion
#region Structure
[Test]
public void Structure_Simple()
{
// arrange
var childA = TestPlan.CreateUniqueElement();
var childB = TestPlan.CreateUniqueElement();
var childC = TestPlan.CreateUniqueElement();
var childD = TestPlan.CreateUniqueElement();
var childE = TestPlan.CreateUniqueElement();
const int spacing = 20;
// act
var structure = new Container();
structure.Stack(stack =>
{
stack.Spacing(spacing);
stack.Item().Element(childA);
stack.Item().Element(childB);
stack.Item().Element(childC);
stack.Item().Element(childD);
stack.Item().Element(childE);
});
// assert
var expected = new Padding
{
Bottom = -spacing,
Child = new BinaryStack
{
First = new BinaryStack
{
First = new Padding
{
Bottom = spacing,
Child = childA
},
Second = new Padding
{
Bottom = spacing,
Child = childB
}
},
Second = new BinaryStack
{
First = new Padding
{
Bottom = spacing,
Child = childC
},
Second = new BinaryStack
{
First = new Padding
{
Bottom = spacing,
Child = childD
},
Second = new Padding
{
Bottom = spacing,
Child = childE
}
}
}
}
};
TestPlan.CompareOperations(structure, expected);
} }
#endregion #endregion
+6 -6
View File
@@ -213,7 +213,7 @@ namespace QuestPDF.UnitTests.TestEngine
public TestPlan CheckMeasureResult(SpacePlan expected) public TestPlan CheckMeasureResult(SpacePlan expected)
{ {
Element.VisitChildren(x => x?.Initialize(null, Canvas)); Element.HandleVisitor(x => x?.Initialize(null, Canvas));
var actual = Element.Measure(OperationInput); var actual = Element.Measure(OperationInput);
@@ -228,7 +228,7 @@ namespace QuestPDF.UnitTests.TestEngine
public TestPlan CheckDrawResult() public TestPlan CheckDrawResult()
{ {
Element.VisitChildren(x => x?.Initialize(null, Canvas)); Element.HandleVisitor(x => x?.Initialize(null, Canvas));
Element.Draw(OperationInput); Element.Draw(OperationInput);
return this; return this;
} }
@@ -271,10 +271,10 @@ namespace QuestPDF.UnitTests.TestEngine
availableSpace ??= new Size(400, 300); availableSpace ??= new Size(400, 300);
var canvas = new FreeCanvas(); var canvas = new FreeCanvas();
value.VisitChildren(x => x.Initialize(null, canvas)); value.HandleVisitor(x => x.Initialize(null, canvas));
var valueMeasure = value.Measure(availableSpace.Value); var valueMeasure = value.Measure(availableSpace.Value);
expected.VisitChildren(x => x.Initialize(null, canvas)); expected.HandleVisitor(x => x.Initialize(null, canvas));
var expectedMeasure = expected.Measure(availableSpace.Value); var expectedMeasure = expected.Measure(availableSpace.Value);
valueMeasure.Should().BeEquivalentTo(expectedMeasure); valueMeasure.Should().BeEquivalentTo(expectedMeasure);
@@ -285,11 +285,11 @@ namespace QuestPDF.UnitTests.TestEngine
availableSpace ??= new Size(400, 300); availableSpace ??= new Size(400, 300);
var valueCanvas = new OperationRecordingCanvas(); var valueCanvas = new OperationRecordingCanvas();
value.VisitChildren(x => x.Initialize(null, valueCanvas)); value.HandleVisitor(x => x.Initialize(null, valueCanvas));
value.Draw(availableSpace.Value); value.Draw(availableSpace.Value);
var expectedCanvas = new OperationRecordingCanvas(); var expectedCanvas = new OperationRecordingCanvas();
expected.VisitChildren(x => x.Initialize(null, expectedCanvas)); expected.HandleVisitor(x => x.Initialize(null, expectedCanvas));
expected.Draw(availableSpace.Value); expected.Draw(availableSpace.Value);
valueCanvas.Operations.Should().BeEquivalentTo(expectedCanvas.Operations); valueCanvas.Operations.Should().BeEquivalentTo(expectedCanvas.Operations);
+3 -3
View File
@@ -16,13 +16,13 @@ namespace QuestPDF.Drawing
var container = new Container(); var container = new Container();
container container
.Column(column => .Stack(stack =>
{ {
Pages Pages
.SelectMany(x => new List<Action>() .SelectMany(x => new List<Action>()
{ {
() => column.Item().PageBreak(), () => stack.Item().PageBreak(),
() => column.Item().Component(x) () => stack.Item().Component(x)
}) })
.Skip(1) .Skip(1)
.ToList() .ToList()
+14 -63
View File
@@ -1,14 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using QuestPDF.Drawing.Exceptions; using QuestPDF.Drawing.Exceptions;
using QuestPDF.Drawing.Proxy; using QuestPDF.Drawing.Proxy;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Elements.Text;
using QuestPDF.Elements.Text.Items;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
namespace QuestPDF.Drawing namespace QuestPDF.Drawing
@@ -18,16 +13,7 @@ namespace QuestPDF.Drawing
internal static void GeneratePdf(Stream stream, IDocument document) internal static void GeneratePdf(Stream stream, IDocument document)
{ {
var metadata = document.GetMetadata(); var metadata = document.GetMetadata();
var writeOnlyStream = new WriteOnlyStream(stream); var canvas = new PdfCanvas(stream, metadata);
var canvas = new PdfCanvas(writeOnlyStream, metadata);
RenderDocument(canvas, document);
}
internal static void GenerateXps(Stream stream, IDocument document)
{
var metadata = document.GetMetadata();
var writeOnlyStream = new WriteOnlyStream(stream);
var canvas = new XpsCanvas(writeOnlyStream, metadata);
RenderDocument(canvas, document); RenderDocument(canvas, document);
} }
@@ -46,15 +32,20 @@ namespace QuestPDF.Drawing
var container = new DocumentContainer(); var container = new DocumentContainer();
document.Compose(container); document.Compose(container);
var content = container.Compose(); var content = container.Compose();
ApplyDefaultTextStyle(content, TextStyle.LibraryDefault);
var metadata = document.GetMetadata(); var metadata = document.GetMetadata();
var pageContext = new PageContext(); var pageContext = new PageContext();
var debuggingState = metadata.ApplyDebugging ? ApplyDebugging(content) : null; DebuggingState debuggingState = null;
if (metadata.ApplyCaching) if (System.Diagnostics.Debugger.IsAttached)
{
debuggingState = ApplyDebugging(content);
}
else
{
ApplyCaching(content); ApplyCaching(content);
}
RenderPass(pageContext, new FreeCanvas(), content, metadata, debuggingState); RenderPass(pageContext, new FreeCanvas(), content, metadata, debuggingState);
RenderPass(pageContext, canvas, content, metadata, debuggingState); RenderPass(pageContext, canvas, content, metadata, debuggingState);
@@ -63,8 +54,8 @@ namespace QuestPDF.Drawing
internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DocumentMetadata documentMetadata, DebuggingState? debuggingState) internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DocumentMetadata documentMetadata, DebuggingState? debuggingState)
where TCanvas : ICanvas, IRenderingCanvas where TCanvas : ICanvas, IRenderingCanvas
{ {
content.VisitChildren(x => x?.Initialize(pageContext, canvas)); content.HandleVisitor(x => x?.Initialize(pageContext, canvas));
content.VisitChildren(x => (x as IStateResettable)?.ResetState()); content.HandleVisitor(x => (x as IStateResettable)?.ResetState());
canvas.BeginDocument(); canvas.BeginDocument();
@@ -112,13 +103,7 @@ namespace QuestPDF.Drawing
void ThrowLayoutException() void ThrowLayoutException()
{ {
var message = $"Composed layout generates infinite document. This may happen in two cases. " + throw new DocumentLayoutException("Composed layout generates infinite document.")
$"1) Your document and its layout configuration is correct but the content takes more than {documentMetadata.DocumentLayoutExceptionThreshold} pages. " +
$"In this case, please increase the value {nameof(DocumentMetadata)}.{nameof(DocumentMetadata.DocumentLayoutExceptionThreshold)} property configured in the {nameof(IDocument.GetMetadata)} method. " +
$"2) The layout configuration of your document is invalid. Some of the elements require more space than is provided." +
$"Please analyze your documents structure to detect this element and fix its size constraints.";
throw new DocumentLayoutException(message)
{ {
ElementTrace = debuggingState?.BuildTrace() ?? "Debug trace is available only in the DEBUG mode." ElementTrace = debuggingState?.BuildTrace() ?? "Debug trace is available only in the DEBUG mode."
}; };
@@ -127,7 +112,7 @@ namespace QuestPDF.Drawing
private static void ApplyCaching(Container content) private static void ApplyCaching(Container content)
{ {
content.VisitChildren(x => content.HandleVisitor(x =>
{ {
if (x is ICacheable) if (x is ICacheable)
x.CreateProxy(y => new CacheProxy(y)); x.CreateProxy(y => new CacheProxy(y));
@@ -138,46 +123,12 @@ namespace QuestPDF.Drawing
{ {
var debuggingState = new DebuggingState(); var debuggingState = new DebuggingState();
content.VisitChildren(x => content.HandleVisitor(x =>
{ {
x.CreateProxy(y => new DebuggingProxy(debuggingState, y)); x.CreateProxy(y => new DebuggingProxy(debuggingState, y));
}); });
return debuggingState; return debuggingState;
} }
private static void ApplyDefaultTextStyle(this Element? content, TextStyle documentDefaultTextStyle)
{
if (content == null)
return;
if (content is TextBlock textBlock)
{
foreach (var textBlockItem in textBlock.Items)
{
if (textBlockItem is TextBlockSpan textSpan)
{
textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle);
}
else if (textBlockItem is TextBlockElement textElement)
{
ApplyDefaultTextStyle(textElement.Element, documentDefaultTextStyle);
}
}
return;
}
var targetTextStyle = documentDefaultTextStyle;
if (content is DefaultTextStyle defaultTextStyleElement)
{
defaultTextStyleElement.TextStyle.ApplyParentStyle(documentDefaultTextStyle);
targetTextStyle = defaultTextStyleElement.TextStyle;
}
foreach (var child in content.GetChildren())
ApplyDefaultTextStyle(child, targetTextStyle);
}
} }
} }
-3
View File
@@ -24,9 +24,6 @@ namespace QuestPDF.Drawing
/// </summary> /// </summary>
public int DocumentLayoutExceptionThreshold { get; set; } = 250; public int DocumentLayoutExceptionThreshold { get; set; } = 250;
public bool ApplyCaching { get; set; } = !System.Diagnostics.Debugger.IsAttached;
public bool ApplyDebugging { get; set; } = System.Diagnostics.Debugger.IsAttached;
public static DocumentMetadata Default => new DocumentMetadata(); public static DocumentMetadata Default => new DocumentMetadata();
} }
} }
+5 -5
View File
@@ -33,7 +33,7 @@ namespace QuestPDF.Drawing
internal static SKPaint ToPaint(this TextStyle style) internal static SKPaint ToPaint(this TextStyle style)
{ {
return Paints.GetOrAdd(style.Key, key => Convert(style)); return Paints.GetOrAdd(style.ToString(), key => Convert(style));
static SKPaint Convert(TextStyle style) static SKPaint Convert(TextStyle style)
{ {
@@ -41,7 +41,7 @@ namespace QuestPDF.Drawing
{ {
Color = SKColor.Parse(style.Color), Color = SKColor.Parse(style.Color),
Typeface = GetTypeface(style), Typeface = GetTypeface(style),
TextSize = (style.Size ?? 12), TextSize = style.Size,
TextEncoding = SKTextEncoding.Utf32 TextEncoding = SKTextEncoding.Utf32
}; };
} }
@@ -51,16 +51,16 @@ namespace QuestPDF.Drawing
if (Typefaces.TryGetValue(style.FontType, out var result)) if (Typefaces.TryGetValue(style.FontType, out var result))
return result; return result;
var slant = (style.IsItalic ?? false) ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright; var slant = style.IsItalic ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
return SKTypeface.FromFamilyName(style.FontType, (int)(style.FontWeight ?? FontWeight.Normal), (int)SKFontStyleWidth.Normal, slant) return SKTypeface.FromFamilyName(style.FontType, (int)style.FontWeight, (int)SKFontStyleWidth.Normal, slant)
?? throw new ArgumentException($"The typeface {style.FontType} could not be found."); ?? throw new ArgumentException($"The typeface {style.FontType} could not be found.");
} }
} }
internal static SKFontMetrics ToFontMetrics(this TextStyle style) internal static SKFontMetrics ToFontMetrics(this TextStyle style)
{ {
return FontMetrics.GetOrAdd(style.Key, key => style.ToPaint().FontMetrics); return FontMetrics.GetOrAdd(style.ToString(), key => style.ToPaint().FontMetrics);
} }
} }
} }
+33 -3
View File
@@ -1,17 +1,47 @@
using System.IO; using System.IO;
using QuestPDF.Helpers; using QuestPDF.Infrastructure;
using SkiaSharp; using SkiaSharp;
namespace QuestPDF.Drawing namespace QuestPDF.Drawing
{ {
internal class PdfCanvas : SkiaDocumentCanvasBase internal class PdfCanvas : SkiaCanvasBase
{ {
private SKDocument Document { get; }
public PdfCanvas(Stream stream, DocumentMetadata documentMetadata) public PdfCanvas(Stream stream, DocumentMetadata documentMetadata)
: base(SKDocument.CreatePdf(stream, MapMetadata(documentMetadata))) {
Document = SKDocument.CreatePdf(stream, MapMetadata(documentMetadata));
}
~PdfCanvas()
{
Document.Dispose();
}
public override void BeginDocument()
{ {
} }
public override void EndDocument()
{
Canvas?.Dispose();
Document?.Close();
Document?.Dispose();
}
public override void BeginPage(Size size)
{
Canvas = Document.BeginPage(size.Width, size.Height);
}
public override void EndPage()
{
Document.EndPage();
Canvas.Dispose();
}
private static SKDocumentPdfMetadata MapMetadata(DocumentMetadata metadata) private static SKDocumentPdfMetadata MapMetadata(DocumentMetadata metadata)
{ {
return new SKDocumentPdfMetadata return new SKDocumentPdfMetadata
+2 -2
View File
@@ -76,7 +76,7 @@ namespace QuestPDF.Drawing.Proxy
title += debugPointer.Highlight ? " 🌟" : string.Empty; title += debugPointer.Highlight ? " 🌟" : string.Empty;
} }
if (item.SpacePlan.Type != SpacePlanType.FullRender) if (item.SpacePlan.Type == SpacePlanType.Wrap)
title = "🔥 " + title; title = "🔥 " + title;
builder.AppendLine(indent + title); builder.AppendLine(indent + title);
@@ -109,7 +109,7 @@ namespace QuestPDF.Drawing.Proxy
Value = x.GetValue(element) Value = x.GetValue(element)
}) })
.Where(x => !(x.Value is IElement)) .Where(x => !(x.Value is IElement))
.Where(x => x.Value is string || !(x.Value is IEnumerable)) .Where(x => !(x.Value is IEnumerable<IElement>))
.Where(x => !(x.Value is TextStyle)) .Where(x => !(x.Value is TextStyle))
.Select(x => $"{x.Property}: {FormatValue(x.Value)}"); .Select(x => $"{x.Property}: {FormatValue(x.Value)}");
@@ -1,45 +0,0 @@
using System.IO;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Drawing
{
internal class SkiaDocumentCanvasBase : SkiaCanvasBase
{
private SKDocument Document { get; }
public SkiaDocumentCanvasBase(SKDocument document)
{
Document = document;
}
~SkiaDocumentCanvasBase()
{
Document.Dispose();
}
public override void BeginDocument()
{
}
public override void EndDocument()
{
Canvas.Dispose();
Document.Close();
Document.Dispose();
}
public override void BeginPage(Size size)
{
Canvas = Document.BeginPage(size.Width, size.Height);
}
public override void EndPage()
{
Document.EndPage();
Canvas.Dispose();
}
}
}
+1 -1
View File
@@ -31,7 +31,7 @@ namespace QuestPDF.Drawing
if (Type == SpacePlanType.Wrap) if (Type == SpacePlanType.Wrap)
return Type.ToString(); return Type.ToString();
return $"{Type} (Width: {Width:N3}, Height: {Height:N3})"; return $"{Type} | width {Width:N2} | height {Height:N2}";
} }
public static implicit operator Size(SpacePlan spacePlan) public static implicit operator Size(SpacePlan spacePlan)
-15
View File
@@ -1,15 +0,0 @@
using System.IO;
using QuestPDF.Helpers;
using SkiaSharp;
namespace QuestPDF.Drawing
{
internal class XpsCanvas : SkiaDocumentCanvasBase
{
public XpsCanvas(Stream stream, DocumentMetadata documentMetadata)
: base(SKDocument.CreateXps(stream, documentMetadata.RasterDpi))
{
}
}
}
@@ -3,7 +3,7 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements namespace QuestPDF.Elements
{ {
internal class MinimalBox : ContainerElement internal class Box : ContainerElement
{ {
internal override void Draw(Size availableSpace) internal override void Draw(Size availableSpace)
{ {
+2 -2
View File
@@ -22,9 +22,9 @@ namespace QuestPDF.Elements
if (Handler == null || skiaCanvas == null) if (Handler == null || skiaCanvas == null)
return; return;
var originalMatrix = skiaCanvas.TotalMatrix; var currentMatrix = skiaCanvas.TotalMatrix;
Handler.Invoke(skiaCanvas, availableSpace); Handler.Invoke(skiaCanvas, availableSpace);
skiaCanvas.SetMatrix(originalMatrix); skiaCanvas.SetMatrix(currentMatrix);
} }
} }
} }
-120
View File
@@ -1,120 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class ColumnItem : Container
{
public bool IsRendered { get; set; }
}
internal class ColumnItemRenderingCommand
{
public ColumnItem ColumnItem { get; set; }
public SpacePlan Measurement { get; set; }
public Size Size { get; set; }
public Position Offset { get; set; }
}
internal class Column : Element, ICacheable, IStateResettable
{
internal List<ColumnItem> Items { get; } = new();
internal float Spacing { get; set; }
public void ResetState()
{
Items.ForEach(x => x.IsRendered = false);
}
internal override IEnumerable<Element?> GetChildren()
{
return Items;
}
internal override void CreateProxy(Func<Element?, Element?> create)
{
Items.ForEach(x => x.Child = create(x.Child));
}
internal override SpacePlan Measure(Size availableSpace)
{
var renderingCommands = PlanLayout(availableSpace);
if (!renderingCommands.Any())
return SpacePlan.Wrap();
var width = renderingCommands.Max(x => x.Size.Width);
var height = renderingCommands.Last().Offset.Y + renderingCommands.Last().Size.Height;
var size = new Size(width, height);
if (width > availableSpace.Width + Size.Epsilon || height > availableSpace.Height + Size.Epsilon)
return SpacePlan.Wrap();
var totalRenderedItems = Items.Count(x => x.IsRendered) + renderingCommands.Count(x => x.Measurement.Type == SpacePlanType.FullRender);
var willBeFullyRendered = totalRenderedItems == Items.Count;
return willBeFullyRendered
? SpacePlan.FullRender(size)
: SpacePlan.PartialRender(size);
}
internal override void Draw(Size availableSpace)
{
var renderingCommands = PlanLayout(availableSpace);
foreach (var command in renderingCommands)
{
if (command.Measurement.Type == SpacePlanType.FullRender)
command.ColumnItem.IsRendered = true;
var targetSize = new Size(availableSpace.Width, command.Size.Height);
Canvas.Translate(command.Offset);
command.ColumnItem.Draw(targetSize);
Canvas.Translate(command.Offset.Reverse());
}
if (Items.All(x => x.IsRendered))
ResetState();
}
private ICollection<ColumnItemRenderingCommand> PlanLayout(Size availableSpace)
{
var topOffset = 0f;
var commands = new List<ColumnItemRenderingCommand>();
foreach (var item in Items)
{
if (item.IsRendered)
continue;
var itemSpace = new Size(availableSpace.Width, availableSpace.Height - topOffset);
var measurement = item.Measure(itemSpace);
if (measurement.Type == SpacePlanType.Wrap)
break;
commands.Add(new ColumnItemRenderingCommand
{
ColumnItem = item,
Size = measurement,
Measurement = measurement,
Offset = new Position(0, topOffset)
});
if (measurement.Type == SpacePlanType.PartialRender)
break;
topOffset += measurement.Height + Spacing;
}
var targetWidth = commands.Select(x => x.Size.Width).DefaultIfEmpty(0).Max();
commands.ForEach(x => x.Size = new Size(targetWidth, x.Size.Height));
return commands;
}
}
}
+11 -11
View File
@@ -22,8 +22,8 @@ namespace QuestPDF.Elements
return SpacePlan.Wrap(); return SpacePlan.Wrap();
var available = new Size( var available = new Size(
Min(MaxWidth, availableSpace.Width), LimitMin(availableSpace.Width, MaxWidth),
Min(MaxHeight, availableSpace.Height)); LimitMin(availableSpace.Height, MaxHeight));
var measurement = base.Measure(available); var measurement = base.Measure(available);
@@ -31,8 +31,8 @@ namespace QuestPDF.Elements
return SpacePlan.Wrap(); return SpacePlan.Wrap();
var actualSize = new Size( var actualSize = new Size(
Max(MinWidth, measurement.Width), LimitMax(measurement.Width, MinWidth),
Max(MinHeight, measurement.Height)); LimitMax(measurement.Height, MinHeight));
if (measurement.Type == SpacePlanType.FullRender) if (measurement.Type == SpacePlanType.FullRender)
return SpacePlan.FullRender(actualSize); return SpacePlan.FullRender(actualSize);
@@ -46,20 +46,20 @@ namespace QuestPDF.Elements
internal override void Draw(Size availableSpace) internal override void Draw(Size availableSpace)
{ {
var available = new Size( var available = new Size(
Min(MaxWidth, availableSpace.Width), LimitMin(availableSpace.Width, MaxWidth),
Min(MaxHeight, availableSpace.Height)); LimitMin(availableSpace.Height, MaxHeight));
Child?.Draw(available); base.Draw(available);
} }
private static float Min(float? x, float y) private float LimitMin(float value, float? limit)
{ {
return x.HasValue ? Math.Min(x.Value, y) : y; return limit.HasValue ? Math.Min(value, limit.Value) : value;
} }
private static float Max(float? x, float y) private float LimitMax(float value, float? limit)
{ {
return x.HasValue ? Math.Max(x.Value, y) : y; return limit.HasValue ? Math.Max(value, limit.Value) : value;
} }
} }
} }
+1 -1
View File
@@ -27,7 +27,7 @@ namespace QuestPDF.Elements
.Layer() .Layer()
.ShowIf(!string.IsNullOrWhiteSpace(Text)) .ShowIf(!string.IsNullOrWhiteSpace(Text))
.AlignCenter() .AlignCenter()
.MinimalBox() .Box()
.Background(Colors.White) .Background(Colors.White)
.Padding(2) .Padding(2)
.Text(Text, TextStyle.Default.Color(Color).FontType(Fonts.Consolas).Size(8)); .Text(Text, TextStyle.Default.Color(Color).FontType(Fonts.Consolas).Size(8));
+59 -72
View File
@@ -1,112 +1,99 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing; using QuestPDF.Drawing;
using QuestPDF.Fluent; using QuestPDF.Fluent;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
namespace QuestPDF.Elements namespace QuestPDF.Elements
{ {
internal class DecorationItemRenderingCommand internal enum DecorationType
{ {
public Element Element { get; set; } Prepend,
public SpacePlan Measurement { get; set; } Append
public Position Offset { get; set; }
} }
internal class Decoration : Element, ICacheable internal class BinaryDecoration : Element, ICacheable
{ {
internal Element Before { get; set; } = new Empty(); public Element DecorationElement { get; set; } = Empty.Instance;
internal Element Content { get; set; } = new Empty(); public Element ContentElement { get; set; } = Empty.Instance;
internal Element After { get; set; } = new Empty(); public DecorationType Type { get; set; }
internal override IEnumerable<Element?> GetChildren() internal override void HandleVisitor(Action<Element?> visit)
{ {
yield return Before; DecorationElement?.HandleVisitor(visit);
yield return Content; ContentElement?.HandleVisitor(visit);
yield return After;
base.HandleVisitor(visit);
} }
internal override void CreateProxy(Func<Element?, Element?> create) internal override void CreateProxy(Func<Element, Element> create)
{ {
Before = create(Before); DecorationElement = create(DecorationElement);
Content = create(Content); ContentElement = create(ContentElement);
After = create(After);
} }
internal override SpacePlan Measure(Size availableSpace) internal override SpacePlan Measure(Size availableSpace)
{ {
var renderingCommands = PlanLayout(availableSpace).ToList(); var decorationMeasure = DecorationElement.Measure(availableSpace);
if (renderingCommands.Any(x => x.Measurement.Type == SpacePlanType.Wrap)) if (decorationMeasure.Type == SpacePlanType.Wrap || decorationMeasure.Type == SpacePlanType.PartialRender)
return SpacePlan.Wrap(); return SpacePlan.Wrap();
var width = renderingCommands.Max(x => x.Measurement.Width); var decorationSize = decorationMeasure;
var height = renderingCommands.Sum(x => x.Measurement.Height); var contentMeasure = ContentElement.Measure(new Size(availableSpace.Width, availableSpace.Height - decorationSize.Height));
var size = new Size(width, height);
if (width > availableSpace.Width + Size.Epsilon || height > availableSpace.Height + Size.Epsilon) if (contentMeasure.Type == SpacePlanType.Wrap)
return SpacePlan.Wrap(); return SpacePlan.Wrap();
var willBeFullyRendered = renderingCommands.All(x => x.Measurement.Type == SpacePlanType.FullRender); var contentSize = contentMeasure;
var resultSize = new Size(availableSpace.Width, decorationSize.Height + contentSize.Height);
return willBeFullyRendered if (contentSize.Type == SpacePlanType.PartialRender)
? SpacePlan.FullRender(size) return SpacePlan.PartialRender(resultSize);
: SpacePlan.PartialRender(size);
if (contentSize.Type == SpacePlanType.FullRender)
return SpacePlan.FullRender(resultSize);
throw new NotSupportedException();
} }
internal override void Draw(Size availableSpace) internal override void Draw(Size availableSpace)
{ {
var renderingCommands = PlanLayout(availableSpace).ToList(); var decorationSize = DecorationElement.Measure(availableSpace);
var width = renderingCommands.Max(x => x.Measurement.Width); var contentSize = new Size(availableSpace.Width, availableSpace.Height - decorationSize.Height);
foreach (var command in renderingCommands) var translateHeight = Type == DecorationType.Prepend ? decorationSize.Height : contentSize.Height;
{ Action drawDecoration = () => DecorationElement?.Draw(new Size(availableSpace.Width, decorationSize.Height));
var elementSize = new Size(width, command.Measurement.Height); Action drawContent = () => ContentElement?.Draw(new Size (availableSpace.Width, contentSize.Height));
Canvas.Translate(command.Offset); var first = Type == DecorationType.Prepend ? drawDecoration : drawContent;
command.Element.Draw(elementSize); var second = Type == DecorationType.Prepend ? drawContent : drawDecoration;
Canvas.Translate(command.Offset.Reverse());
} first();
Canvas.Translate(new Position(0, translateHeight));
second();
Canvas.Translate(new Position(0, -translateHeight));
} }
}
private IEnumerable<DecorationItemRenderingCommand> PlanLayout(Size availableSpace) internal class Decoration : IComponent
{
public Element Header { get; set; } = Empty.Instance;
public Element Content { get; set; } = Empty.Instance;
public Element Footer { get; set; } = Empty.Instance;
public void Compose(IContainer container)
{ {
SpacePlan GetDecorationMeasurement(Element element) container.Element(new BinaryDecoration
{ {
var measurement = element.Measure(availableSpace); Type = DecorationType.Prepend,
DecorationElement = Header,
return measurement.Type == SpacePlanType.FullRender ContentElement = new BinaryDecoration
? measurement {
: SpacePlan.Wrap(); Type = DecorationType.Append,
} ContentElement = Content,
DecorationElement = Footer
var beforeMeasurement = GetDecorationMeasurement(Before); }
var afterMeasurement = GetDecorationMeasurement(After); });
var contentSpace = new Size(availableSpace.Width, availableSpace.Height - beforeMeasurement.Height - afterMeasurement.Height);
var contentMeasurement = Content.Measure(contentSpace);
yield return new DecorationItemRenderingCommand
{
Element = Before,
Measurement = beforeMeasurement,
Offset = Position.Zero
};
yield return new DecorationItemRenderingCommand
{
Element = Content,
Measurement = contentMeasurement,
Offset = new Position(0, beforeMeasurement.Height)
};
yield return new DecorationItemRenderingCommand
{
Element = After,
Measurement = afterMeasurement,
Offset = new Position(0, beforeMeasurement.Height + contentMeasurement.Height)
};
} }
} }
} }
-10
View File
@@ -1,10 +0,0 @@
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class DefaultTextStyle : ContainerElement
{
public TextStyle TextStyle { get; set; } = TextStyle.Default;
}
}
+120
View File
@@ -0,0 +1,120 @@
using System;
using QuestPDF.Drawing;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class DynamicHost : Element, IStateResettable
{
private IDynamicComponent Child { get; }
public DynamicHost(IDynamicComponent child)
{
Child = child;
}
public void ResetState()
{
GetContent(Size.Zero, DynamicLayoutOperation.Reset);
}
internal override SpacePlan Measure(Size availableSpace)
{
var content = GetContent(availableSpace, DynamicLayoutOperation.Measure);
var measurement = content.Measure(availableSpace);
if (measurement.Type != SpacePlanType.FullRender)
throw new DocumentLayoutException("Dynamic component generated content that does not fit on a single page.");
return content.HasMoreContent
? SpacePlan.PartialRender(measurement)
: SpacePlan.FullRender(measurement);
}
internal override void Draw(Size availableSpace)
{
GetContent(availableSpace, DynamicLayoutOperation.Draw).Draw(availableSpace);
}
DynamicContainer GetContent(Size availableSize, DynamicLayoutOperation operation)
{
var context = new DynamicContext
{
PageNumber = PageContext.GetLocationPage(Infrastructure.PageContext.CurrentPageSlot),
PageContext = PageContext,
Canvas = Canvas,
AvailableSize = availableSize,
Operation = operation
};
var container = new DynamicContainer();
Child.Compose(context, container);
container.HandleVisitor(x => x?.Initialize(PageContext, Canvas));
container.HandleVisitor(x => (x as IStateResettable)?.ResetState());
return container;
}
}
public enum DynamicLayoutOperation
{
Reset,
Measure,
Draw
}
public class DynamicContext
{
internal IPageContext PageContext { get; set; }
internal ICanvas Canvas { get; set; }
public int PageNumber { get; internal set; }
public Size AvailableSize { get; internal set; }
public DynamicLayoutOperation Operation { get; internal set; }
public IDynamicElement CreateElement(Action<IContainer> content)
{
var container = new DynamicElement();
content(container);
container.HandleVisitor(x => x?.Initialize(PageContext, Canvas));
container.HandleVisitor(x => (x as IStateResettable)?.ResetState());
container.Size = container.Measure(AvailableSize);
return container;
}
}
public interface IDynamicContainer : IContainer
{
}
internal class DynamicContainer : Container, IDynamicContainer
{
internal bool HasMoreContent { get; set; }
}
public static class DynamicContainerExtensions
{
public static IDynamicContainer HasMoreContent(this IDynamicContainer container)
{
(container as DynamicContainer).HasMoreContent = true;
return container;
}
}
public interface IDynamicElement : IElement
{
Size Size { get; }
}
internal class DynamicElement : ContainerElement, IDynamicElement
{
public Size Size { get; internal set; }
}
}
+11 -10
View File
@@ -2,6 +2,7 @@
using System.Linq; using System.Linq;
using QuestPDF.Fluent; using QuestPDF.Fluent;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using static QuestPDF.Infrastructure.HorizontalAlignment;
namespace QuestPDF.Elements namespace QuestPDF.Elements
{ {
@@ -19,7 +20,7 @@ namespace QuestPDF.Elements
public Queue<GridElement> ChildrenQueue { get; set; } = new Queue<GridElement>(); public Queue<GridElement> ChildrenQueue { get; set; } = new Queue<GridElement>();
public int ColumnsCount { get; set; } = DefaultColumnsCount; public int ColumnsCount { get; set; } = DefaultColumnsCount;
public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left; public HorizontalAlignment Alignment { get; set; } = Left;
public float VerticalSpacing { get; set; } = 0; public float VerticalSpacing { get; set; } = 0;
public float HorizontalSpacing { get; set; } = 0; public float HorizontalSpacing { get; set; } = 0;
@@ -27,12 +28,12 @@ namespace QuestPDF.Elements
{ {
ChildrenQueue = new Queue<GridElement>(Children); ChildrenQueue = new Queue<GridElement>(Children);
container.Column(column => container.Stack(stack =>
{ {
column.Spacing(VerticalSpacing); stack.Spacing(VerticalSpacing);
while (ChildrenQueue.Any()) while (ChildrenQueue.Any())
column.Item().Row(BuildRow); stack.Item().Row(BuildRow);
}); });
} }
@@ -61,16 +62,16 @@ namespace QuestPDF.Elements
var emptySpace = ColumnsCount - columnsWidth; var emptySpace = ColumnsCount - columnsWidth;
var hasEmptySpace = emptySpace >= Size.Epsilon; var hasEmptySpace = emptySpace >= Size.Epsilon;
if (Alignment == HorizontalAlignment.Center) if (Alignment == Center)
emptySpace /= 2; emptySpace /= 2;
if (hasEmptySpace && Alignment != HorizontalAlignment.Left) if (hasEmptySpace && Alignment != Left)
row.RelativeItem(emptySpace); row.RelativeColumn(emptySpace);
elements.ForEach(x => row.RelativeItem(x.Columns).Element(x.Child)); elements.ForEach(x => row.RelativeColumn(x.Columns).Element(x.Child));
if (hasEmptySpace && Alignment != HorizontalAlignment.Right) if (hasEmptySpace && Alignment != Right)
row.RelativeItem(emptySpace); row.RelativeColumn(emptySpace);
} }
} }
} }
-248
View File
@@ -1,248 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class InlinedElement : Container
{
}
internal enum InlinedAlignment
{
Left,
Center,
Right,
Justify,
SpaceAround
}
internal class Inlined : Element, IStateResettable
{
public List<InlinedElement> Elements { get; internal set; } = new List<InlinedElement>();
private Queue<InlinedElement> ChildrenQueue { get; set; }
internal float VerticalSpacing { get; set; }
internal float HorizontalSpacing { get; set; }
internal InlinedAlignment ElementsAlignment { get; set; }
internal VerticalAlignment BaselineAlignment { get; set; }
public void ResetState()
{
ChildrenQueue = new Queue<InlinedElement>(Elements);
}
internal override IEnumerable<Element?> GetChildren()
{
return Elements;
}
internal override SpacePlan Measure(Size availableSpace)
{
if (!ChildrenQueue.Any())
return SpacePlan.FullRender(Size.Zero);
var lines = Compose(availableSpace);
if (!lines.Any())
return SpacePlan.Wrap();
var lineSizes = lines
.Select(line =>
{
var size = GetLineSize(line);
var widthWithSpacing = size.Width + (line.Count - 1) * HorizontalSpacing;
return new Size(widthWithSpacing, size.Height);
})
.ToList();
var width = lineSizes.Max(x => x.Width);
var height = lineSizes.Sum(x => x.Height) + (lines.Count - 1) * VerticalSpacing;
var targetSize = new Size(width, height);
var isPartiallyRendered = lines.Sum(x => x.Count) != ChildrenQueue.Count;
if (isPartiallyRendered)
return SpacePlan.PartialRender(targetSize);
return SpacePlan.FullRender(targetSize);
}
internal override void Draw(Size availableSpace)
{
var lines = Compose(availableSpace);
var topOffset = 0f;
foreach (var line in lines)
{
var height = line
.Select(x => x.Measure(Size.Max))
.Where(x => x.Type != SpacePlanType.Wrap)
.Max(x => x.Height);
DrawLine(line);
topOffset += height + VerticalSpacing;
Canvas.Translate(new Position(0, height + VerticalSpacing));
}
Canvas.Translate(new Position(0, -topOffset));
lines.SelectMany(x => x).ToList().ForEach(x => ChildrenQueue.Dequeue());
void DrawLine(ICollection<InlinedElement> elements)
{
var lineSize = GetLineSize(elements);
var elementOffset = ElementOffset();
var leftOffset = AlignOffset();
Canvas.Translate(new Position(leftOffset, 0));
foreach (var element in elements)
{
var size = (Size)element.Measure(Size.Max);
var baselineOffset = BaselineOffset(size, lineSize.Height);
if (size.Height == 0)
size = new Size(size.Width, lineSize.Height);
Canvas.Translate(new Position(0, baselineOffset));
element.Draw(size);
Canvas.Translate(new Position(0, -baselineOffset));
leftOffset += size.Width + elementOffset;
Canvas.Translate(new Position(size.Width + elementOffset, 0));
}
Canvas.Translate(new Position(-leftOffset, 0));
float ElementOffset()
{
var difference = availableSpace.Width - lineSize.Width;
if (elements.Count == 1)
return 0;
return ElementsAlignment switch
{
InlinedAlignment.Justify => difference / (elements.Count - 1),
InlinedAlignment.SpaceAround => difference / (elements.Count + 1),
_ => HorizontalSpacing
};
}
float AlignOffset()
{
var difference = availableSpace.Width - lineSize.Width - (elements.Count - 1) * HorizontalSpacing;
return ElementsAlignment switch
{
InlinedAlignment.Left => 0,
InlinedAlignment.Justify => 0,
InlinedAlignment.SpaceAround => elementOffset,
InlinedAlignment.Center => difference / 2,
InlinedAlignment.Right => difference,
_ => 0
};
}
float BaselineOffset(Size elementSize, float lineHeight)
{
var difference = lineHeight - elementSize.Height;
return BaselineAlignment switch
{
VerticalAlignment.Top => 0,
VerticalAlignment.Middle => difference / 2,
_ => difference
};
}
}
}
Size GetLineSize(ICollection<InlinedElement> elements)
{
var sizes = elements
.Select(x => x.Measure(Size.Max))
.Where(x => x.Type != SpacePlanType.Wrap)
.ToList();
var width = sizes.Sum(x => x.Width);
var height = sizes.Max(x => x.Height);
return new Size(width, height);
}
// list of lines, each line is a list of elements
private ICollection<ICollection<InlinedElement>> Compose(Size availableSize)
{
var queue = new Queue<InlinedElement>(ChildrenQueue);
var result = new List<ICollection<InlinedElement>>();
var topOffset = 0f;
while (true)
{
var line = GetNextLine();
if (!line.Any())
break;
var height = line
.Select(x => x.Measure(availableSize))
.Where(x => x.Type != SpacePlanType.Wrap)
.Max(x => x.Height);
if (topOffset + height > availableSize.Height + Size.Epsilon)
break;
topOffset += height + VerticalSpacing;
result.Add(line);
}
return result;
ICollection<InlinedElement> GetNextLine()
{
var result = new List<InlinedElement>();
var leftOffset = GetInitialAlignmentOffset();
while (true)
{
if (!queue.Any())
break;
var element = queue.Peek();
var size = element.Measure(Size.Max);
if (size.Type == SpacePlanType.Wrap)
break;
if (leftOffset + size.Width > availableSize.Width + Size.Epsilon)
break;
queue.Dequeue();
leftOffset += size.Width + HorizontalSpacing;
result.Add(element);
}
return result;
}
float GetInitialAlignmentOffset()
{
// this method makes sure that the spacing between elements is no lesser than configured
return ElementsAlignment switch
{
InlinedAlignment.SpaceAround => HorizontalSpacing * 2,
_ => 0
};
}
}
}
}
+3 -2
View File
@@ -15,9 +15,10 @@ namespace QuestPDF.Elements
{ {
public List<Layer> Children { get; set; } = new List<Layer>(); public List<Layer> Children { get; set; } = new List<Layer>();
internal override IEnumerable<Element?> GetChildren() internal override void HandleVisitor(Action<Element?> visit)
{ {
return Children; Children.ForEach(x => x.HandleVisitor(visit));
base.HandleVisitor(visit);
} }
internal override SpacePlan Measure(Size availableSpace) internal override SpacePlan Measure(Size availableSpace)
-46
View File
@@ -1,46 +0,0 @@
using QuestPDF.Drawing;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
public interface ILine
{
}
internal enum LineType
{
Vertical,
Horizontal
}
internal class Line : Element, ILine, ICacheable
{
public LineType Type { get; set; } = LineType.Vertical;
public string Color { get; set; } = Colors.Black;
public float Size { get; set; } = 1;
internal override SpacePlan Measure(Size availableSpace)
{
return Type switch
{
LineType.Vertical when availableSpace.Width >= Size => SpacePlan.FullRender(Size, 0),
LineType.Horizontal when availableSpace.Height >= Size => SpacePlan.FullRender(0, Size),
_ => SpacePlan.Wrap()
};
}
internal override void Draw(Size availableSpace)
{
if (Type == LineType.Vertical)
{
Canvas.DrawRectangle(new Position(-Size/2, 0), new Size(Size, availableSpace.Height), Color);
}
else if (Type == LineType.Horizontal)
{
Canvas.DrawRectangle(new Position(0, -Size/2), new Size(availableSpace.Width, Size), Color);
}
}
}
}
+3 -11
View File
@@ -1,5 +1,4 @@
using System; using System;
using QuestPDF.Drawing;
using QuestPDF.Fluent; using QuestPDF.Fluent;
using QuestPDF.Helpers; using QuestPDF.Helpers;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
@@ -8,8 +7,6 @@ namespace QuestPDF.Elements
{ {
internal class Page : IComponent internal class Page : IComponent
{ {
public TextStyle DefaultTextStyle { get; set; } = new TextStyle();
public Size MinSize { get; set; } = PageSizes.A4; public Size MinSize { get; set; } = PageSizes.A4;
public Size MaxSize { get; set; } = PageSizes.A4; public Size MaxSize { get; set; } = PageSizes.A4;
@@ -18,8 +15,6 @@ namespace QuestPDF.Elements
public float MarginTop { get; set; } public float MarginTop { get; set; }
public float MarginBottom { get; set; } public float MarginBottom { get; set; }
public string BackgroundColor { get; set; } = Colors.Transparent;
public Element Header { get; set; } = Empty.Instance; public Element Header { get; set; } = Empty.Instance;
public Element Content { get; set; } = Empty.Instance; public Element Content { get; set; } = Empty.Instance;
public Element Footer { get; set; } = Empty.Instance; public Element Footer { get; set; } = Empty.Instance;
@@ -27,25 +22,22 @@ namespace QuestPDF.Elements
public void Compose(IContainer container) public void Compose(IContainer container)
{ {
container container
.MinWidth(MinSize.Width) .MinWidth(MinSize.Width)
.MinHeight(MinSize.Height) .MinHeight(MinSize.Height)
.MaxWidth(MaxSize.Width) .MaxWidth(MaxSize.Width)
.MaxHeight(MaxSize.Height) .MaxHeight(MaxSize.Height)
.Background(BackgroundColor)
.PaddingLeft(MarginLeft) .PaddingLeft(MarginLeft)
.PaddingRight(MarginRight) .PaddingRight(MarginRight)
.PaddingTop(MarginTop) .PaddingTop(MarginTop)
.PaddingBottom(MarginBottom) .PaddingBottom(MarginBottom)
.DefaultTextStyle(DefaultTextStyle)
.Decoration(decoration => .Decoration(decoration =>
{ {
decoration decoration
.Before() .Header()
.DebugPointer("Page header") .DebugPointer("Page header")
.Element(Header); .Element(Header);
@@ -57,7 +49,7 @@ namespace QuestPDF.Elements
.Element(Content); .Element(Content);
decoration decoration
.After() .Footer()
.DebugPointer("Page footer") .DebugPointer("Page footer")
.Element(Footer); .Element(Footer);
}); });
+143 -120
View File
@@ -1,5 +1,4 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using QuestPDF.Drawing; using QuestPDF.Drawing;
@@ -7,152 +6,176 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements namespace QuestPDF.Elements
{ {
internal enum RowItemType internal abstract class RowElement : Constrained
{ {
Auto, public float Size { get; protected set; }
Constant,
Relative
}
internal class RowItem : Container public void SetWidth(float width)
{
public bool IsRendered { get; set; }
public float Width { get; set; }
public RowItemType Type { get; set; }
public float Size { get; set; }
}
internal class RowItemRenderingCommand
{
public RowItem RowItem { get; set; }
public SpacePlan Measurement { get; set; }
public Size Size { get; set; }
public Position Offset { get; set; }
}
internal class Row : Element, ICacheable, IStateResettable
{
internal List<RowItem> Items { get; } = new();
internal float Spacing { get; set; }
public void ResetState()
{ {
Items.ForEach(x => x.IsRendered = false); MinWidth = width;
MaxWidth = width;
} }
}
internal override IEnumerable<Element?> GetChildren() internal class ConstantRowElement : RowElement
{
public ConstantRowElement(float size)
{ {
return Items; Size = size;
SetWidth(size);
}
}
internal class RelativeRowElement : RowElement
{
public RelativeRowElement(float size)
{
Size = size;
}
}
internal class BinaryRow : Element, ICacheable
{
internal Element Left { get; set; }
internal Element Right { get; set; }
internal override void HandleVisitor(Action<Element?> visit)
{
Left.HandleVisitor(visit);
Right.HandleVisitor(visit);
base.HandleVisitor(visit);
} }
internal override void CreateProxy(Func<Element?, Element?> create) internal override void CreateProxy(Func<Element?, Element?> create)
{ {
Items.ForEach(x => x.Child = create(x.Child)); Left = create(Left);
Right = create(Right);
} }
internal override SpacePlan Measure(Size availableSpace) internal override SpacePlan Measure(Size availableSpace)
{ {
UpdateItemsWidth(availableSpace.Width); var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height));
var renderingCommands = PlanLayout(availableSpace);
if (renderingCommands.Any(x => !x.RowItem.IsRendered && x.Measurement.Type == SpacePlanType.Wrap)) if (leftMeasurement.Type == SpacePlanType.Wrap)
return SpacePlan.Wrap(); return SpacePlan.Wrap();
var width = renderingCommands.Last().Offset.X + renderingCommands.Last().Size.Width; var rightMeasurement = Right.Measure(new Size(availableSpace.Width - leftMeasurement.Width, availableSpace.Height));
var height = renderingCommands.Max(x => x.Size.Height);
var size = new Size(width, height);
if (width > availableSpace.Width + Size.Epsilon || height > availableSpace.Height + Size.Epsilon) if (rightMeasurement.Type == SpacePlanType.Wrap)
return SpacePlan.Wrap(); return SpacePlan.Wrap();
if (renderingCommands.Any(x => !x.RowItem.IsRendered && x.Measurement.Type == SpacePlanType.PartialRender)) var totalWidth = leftMeasurement.Width + rightMeasurement.Width;
return SpacePlan.PartialRender(size); var totalHeight = Math.Max(leftMeasurement.Height, rightMeasurement.Height);
return SpacePlan.FullRender(size); var targetSize = new Size(totalWidth, totalHeight);
if (leftMeasurement.Type == SpacePlanType.PartialRender || rightMeasurement.Type == SpacePlanType.PartialRender)
return SpacePlan.PartialRender(targetSize);
return SpacePlan.FullRender(targetSize);
} }
internal override void Draw(Size availableSpace) internal override void Draw(Size availableSpace)
{ {
UpdateItemsWidth(availableSpace.Width); var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height));
var renderingCommands = PlanLayout(availableSpace); var leftWidth = leftMeasurement.Width;
foreach (var command in renderingCommands) Left.Draw(new Size(leftWidth, availableSpace.Height));
{
if (command.Measurement.Type == SpacePlanType.FullRender)
command.RowItem.IsRendered = true;
if (command.Measurement.Type == SpacePlanType.Wrap) Canvas.Translate(new Position(leftWidth, 0));
continue; Right.Draw(new Size(availableSpace.Width - leftWidth, availableSpace.Height));
Canvas.Translate(new Position(-leftWidth, 0));
Canvas.Translate(command.Offset);
command.RowItem.Draw(command.Size);
Canvas.Translate(command.Offset.Reverse());
}
if (Items.All(x => x.IsRendered))
ResetState();
}
private void UpdateItemsWidth(float availableWidth)
{
HandleItemsWithAutoWidth();
var constantWidth = Items.Where(x => x.Type == RowItemType.Constant).Sum(x => x.Size);
var relativeWidth = Items.Where(x => x.Type == RowItemType.Relative).Sum(x => x.Size);
var spacingWidth = (Items.Count - 1) * Spacing;
foreach (var item in Items.Where(x => x.Type == RowItemType.Constant))
item.Width = item.Size;
if (relativeWidth <= 0)
return;
var widthPerRelativeUnit = (availableWidth - constantWidth - spacingWidth) / relativeWidth;
foreach (var item in Items.Where(x => x.Type == RowItemType.Relative))
item.Width = item.Size * widthPerRelativeUnit;
}
private void HandleItemsWithAutoWidth()
{
foreach (var rowItem in Items.Where(x => x.Type == RowItemType.Auto))
{
rowItem.Size = rowItem.Measure(Size.Max).Width;
rowItem.Type = RowItemType.Constant;
}
}
private ICollection<RowItemRenderingCommand> PlanLayout(Size availableSpace)
{
var leftOffset = 0f;
var renderingCommands = new List<RowItemRenderingCommand>();
foreach (var item in Items)
{
var itemSpace = new Size(item.Width, availableSpace.Height);
var command = new RowItemRenderingCommand
{
RowItem = item,
Size = itemSpace,
Measurement = item.Measure(itemSpace),
Offset = new Position(leftOffset, 0)
};
renderingCommands.Add(command);
leftOffset += item.Width + Spacing;
}
var rowHeight = renderingCommands.Where(x => !x.RowItem.IsRendered).Max(x => x.Measurement.Height);
foreach (var command in renderingCommands)
{
command.Size = new Size(command.Size.Width, rowHeight);
command.Measurement = command.RowItem.Measure(command.Size);
}
return renderingCommands;
} }
} }
internal class Row : Element
{
public float Spacing { get; set; } = 0;
public ICollection<RowElement> Children { get; internal set; } = new List<RowElement>();
private Element? RootElement { get; set; }
internal override void HandleVisitor(Action<Element?> visit)
{
if (RootElement == null)
ComposeTree();
RootElement.HandleVisitor(visit);
base.HandleVisitor(visit);
}
internal override SpacePlan Measure(Size availableSpace)
{
UpdateElementsWidth(availableSpace.Width);
return RootElement.Measure(availableSpace);
}
internal override void Draw(Size availableSpace)
{
UpdateElementsWidth(availableSpace.Width);
RootElement.Draw(availableSpace);
}
#region structure
private void ComposeTree()
{
Children = AddSpacing(Children, Spacing);
var elements = Children.Cast<Element>().ToArray();
RootElement = BuildTree(elements);
}
private void UpdateElementsWidth(float availableWidth)
{
var constantWidth = Children
.Where(x => x is ConstantRowElement)
.Cast<ConstantRowElement>()
.Sum(x => x.Size);
var relativeWidth = Children
.Where(x => x is RelativeRowElement)
.Cast<RelativeRowElement>()
.Sum(x => x.Size);
var widthPerRelativeUnit = (availableWidth - constantWidth) / relativeWidth;
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)
{
if (spacing < Size.Epsilon)
return elements;
return elements
.SelectMany(x => new[] { new ConstantRowElement(spacing), x })
.Skip(1)
.ToList();
}
private static Element BuildTree(Span<Element> elements)
{
if (elements.IsEmpty)
return Empty.Instance;
if (elements.Length == 1)
return elements[0];
var half = elements.Length / 2;
return new BinaryRow
{
Left = BuildTree(elements.Slice(0, half)),
Right = BuildTree(elements.Slice(half))
};
}
#endregion
}
} }
-78
View File
@@ -1,78 +0,0 @@
using System;
using System.Linq;
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class ScaleToFit : ContainerElement
{
internal override SpacePlan Measure(Size availableSpace)
{
if (Child == null)
return SpacePlan.FullRender(Size.Zero);
var perfectScale = FindPerfectScale(Child, availableSpace);
if (perfectScale == null)
return SpacePlan.Wrap();
var targetSpace = ScaleSize(availableSpace, perfectScale.Value);
return SpacePlan.FullRender(targetSpace);
}
internal override void Draw(Size availableSpace)
{
var perfectScale = FindPerfectScale(Child, availableSpace);
if (!perfectScale.HasValue)
return;
var targetScale = perfectScale.Value;
var targetSpace = ScaleSize(availableSpace, 1 / targetScale);
Canvas.Scale(targetScale, targetScale);
Child?.Draw(targetSpace);
Canvas.Scale(1 / targetScale, 1 / targetScale);
}
private static Size ScaleSize(Size size, float factor)
{
return new Size(size.Width * factor, size.Height * factor);
}
private static float? FindPerfectScale(Element child, Size availableSpace)
{
if (ChildFits(1))
return 1;
var maxScale = 1f;
var minScale = Size.Epsilon;
var lastWorkingScale = (float?)null;
foreach (var _ in Enumerable.Range(0, 8))
{
var halfScale = (maxScale + minScale) / 2;
if (ChildFits(halfScale))
{
minScale = halfScale;
lastWorkingScale = halfScale;
}
else
{
maxScale = halfScale;
}
}
return lastWorkingScale;
bool ChildFits(float scale)
{
var scaledSpace = ScaleSize(availableSpace, 1 / scale);
return child.Measure(scaledSpace).Type == SpacePlanType.FullRender;
}
}
}
}
-34
View File
@@ -1,34 +0,0 @@
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class SkipOnce : ContainerElement, IStateResettable
{
private bool FirstPageWasSkipped { get; set; }
public void ResetState()
{
FirstPageWasSkipped = false;
}
internal override SpacePlan Measure(Size availableSpace)
{
if (Child == null || !FirstPageWasSkipped)
return SpacePlan.FullRender(Size.Zero);
return Child.Measure(availableSpace);
}
internal override void Draw(Size availableSpace)
{
if (Child == null)
return;
if (FirstPageWasSkipped)
Child.Draw(availableSpace);
FirstPageWasSkipped = true;
}
}
}
+145
View File
@@ -0,0 +1,145 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
using IComponent = QuestPDF.Infrastructure.IComponent;
using IContainer = QuestPDF.Infrastructure.IContainer;
namespace QuestPDF.Elements
{
internal class BinaryStack : Element, IStateResettable, ICacheable
{
internal Element First { get; set; } = Empty.Instance;
internal Element Second { get; set; } = Empty.Instance;
internal bool IsFirstRendered { get; set; } = false;
internal override void HandleVisitor(Action<Element?> visit)
{
First?.HandleVisitor(visit);
Second?.HandleVisitor(visit);
base.HandleVisitor(visit);
}
public void ResetState()
{
IsFirstRendered = false;
}
internal override void CreateProxy(Func<Element?, Element?> create)
{
First = create(First);
Second = create(Second);
}
internal override SpacePlan Measure(Size availableSpace)
{
var firstElement = IsFirstRendered ? Empty.Instance : First;
var firstSize = firstElement.Measure(availableSpace);
if (firstSize.Type == SpacePlanType.Wrap)
return SpacePlan.Wrap();
if (firstSize.Type == SpacePlanType.PartialRender)
return firstSize;
var spaceForSecond = new Size(availableSpace.Width, availableSpace.Height - firstSize.Height);
var secondSize = Second.Measure(spaceForSecond);
if (secondSize.Type == SpacePlanType.Wrap)
return SpacePlan.PartialRender(firstSize);
var totalWidth = Math.Max(firstSize.Width, secondSize.Width);
var totalHeight = firstSize.Height + secondSize.Height;
var targetSize = new Size(totalWidth, totalHeight);
if (secondSize.Type == SpacePlanType.PartialRender)
return SpacePlan.PartialRender(targetSize);
return SpacePlan.FullRender(targetSize);
}
internal override void Draw(Size availableSpace)
{
var firstElement = IsFirstRendered ? Empty.Instance : First;
var firstMeasurement = firstElement.Measure(availableSpace);
if (firstMeasurement.Type == SpacePlanType.FullRender)
IsFirstRendered = true;
var firstSize = firstMeasurement;
if (firstSize.Type != SpacePlanType.Wrap)
firstElement.Draw(new Size(availableSpace.Width, firstSize.Height));
if (firstMeasurement.Type == SpacePlanType.Wrap || firstMeasurement.Type == SpacePlanType.PartialRender)
return;
var firstHeight = firstSize.Height;
var spaceForSecond = new Size(availableSpace.Width, availableSpace.Height - firstHeight);
var secondMeasurement = Second.Measure(spaceForSecond);
if (secondMeasurement.Type == SpacePlanType.Wrap)
return;
Canvas.Translate(new Position(0, firstHeight));
Second.Draw(new Size(availableSpace.Width, secondMeasurement.Height));
Canvas.Translate(new Position(0, -firstHeight));
if (secondMeasurement.Type == SpacePlanType.FullRender)
IsFirstRendered = false;
}
}
internal class Stack : IComponent
{
public ICollection<Element> Children { get; } = new List<Element>();
public float Spacing { get; set; } = 0;
public void Compose(IContainer container)
{
var elements = AddSpacing(Spacing, Children);
container
.PaddingBottom(-Spacing)
.Element(BuildTree(elements.ToArray()));
}
static ICollection<Element> AddSpacing(float spacing, ICollection<Element> elements)
{
if (spacing < Size.Epsilon)
return elements;
return elements
.Where(x => !(x is Empty))
.Select(x => new Padding
{
Bottom = spacing,
Child = x
})
.Cast<Element>()
.ToList();
}
static Element BuildTree(Span<Element> elements)
{
if (elements.IsEmpty)
return Empty.Instance;
if (elements.Length == 1)
return elements[0];
var half = elements.Length / 2;
return new BinaryStack
{
First = BuildTree(elements.Slice(0, half)),
Second = BuildTree(elements.Slice(half))
};
}
}
}
-25
View File
@@ -1,25 +0,0 @@
using System;
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class StopPaging : ContainerElement
{
internal override SpacePlan Measure(Size availableSpace)
{
if (Child == null)
return SpacePlan.FullRender(Size.Zero);
var measurement = Child.Measure(availableSpace);
return measurement.Type switch
{
SpacePlanType.Wrap => SpacePlan.FullRender(Size.Zero),
SpacePlanType.PartialRender => SpacePlan.FullRender(measurement),
SpacePlanType.FullRender => measurement,
_ => throw new ArgumentOutOfRangeException()
};
}
}
}
@@ -1,33 +0,0 @@
using System.Collections.Generic;
using System.Linq;
namespace QuestPDF.Elements.Table
{
/// <summary>
/// This dictionary allows to access key that does not exist.
/// Instead of throwing an exception, it returns a default value.
/// </summary>
internal class DynamicDictionary<TKey, TValue>
{
private TValue Default { get; }
private IDictionary<TKey, TValue> Dictionary { get; } = new Dictionary<TKey, TValue>();
public DynamicDictionary()
{
}
public DynamicDictionary(TValue defaultValue)
{
Default = defaultValue;
}
public TValue this[TKey key]
{
get => Dictionary.TryGetValue(key, out var value) ? value : Default;
set => Dictionary[key] = value;
}
public List<KeyValuePair<TKey, TValue>> Items => Dictionary.ToList();
}
}
@@ -1,9 +0,0 @@
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Table
{
public interface ITableCellContainer : IContainer
{
}
}
-277
View File
@@ -1,277 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using QuestPDF.Drawing;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Table
{
internal class Table : Element, IStateResettable
{
public List<TableColumnDefinition> Columns { get; set; } = new List<TableColumnDefinition>();
public List<TableCell> Cells { get; set; } = new List<TableCell>();
public bool ExtendLastCellsToTableBottom { get; set; }
// cache for efficient cell finding
// index of first array - number of row
// nested array - collection of all cells starting at given row
private TableCell[][] OrderedCells { get; set; }
private int StartingRowsCount { get; set; }
private int RowsCount { get; set; }
private int CurrentRow { get; set; }
internal override IEnumerable<Element?> GetChildren()
{
return Cells;
}
public void ResetState()
{
if (StartingRowsCount == default)
StartingRowsCount = Cells.Select(x => x.Row).DefaultIfEmpty(0).Max();
if (RowsCount == default)
RowsCount = Cells.Select(x => x.Row + x.RowSpan - 1).DefaultIfEmpty(0).Max();
if (OrderedCells == default)
{
var groups = Cells
.GroupBy(x => x.Row)
.ToDictionary(x => x.Key, x => x.OrderBy(y => y.Column).ToArray());
OrderedCells = Enumerable
.Range(0, RowsCount + 1)
.Select(x => groups.TryGetValue(x, out var output) ? output : Array.Empty<TableCell>())
.ToArray();
}
Cells.ForEach(x => x.IsRendered = false);
CurrentRow = 1;
}
internal override SpacePlan Measure(Size availableSpace)
{
if (!Cells.Any())
return SpacePlan.FullRender(Size.Zero);
UpdateColumnsWidth(availableSpace.Width);
var renderingCommands = PlanLayout(availableSpace);
if (!renderingCommands.Any())
return SpacePlan.Wrap();
var width = Columns.Sum(x => x.Width);
var height = renderingCommands.Max(x => x.Offset.Y + x.Size.Height);
var tableSize = new Size(width, height);
if (tableSize.Width > availableSpace.Width + Size.Epsilon)
return SpacePlan.Wrap();
return FindLastRenderedRow(renderingCommands) == StartingRowsCount
? SpacePlan.FullRender(tableSize)
: SpacePlan.PartialRender(tableSize);
}
internal override void Draw(Size availableSpace)
{
UpdateColumnsWidth(availableSpace.Width);
var renderingCommands = PlanLayout(availableSpace);
foreach (var command in renderingCommands)
{
if (command.Measurement.Type == SpacePlanType.FullRender)
command.Cell.IsRendered = true;
Canvas.Translate(command.Offset);
command.Cell.Draw(command.Size);
Canvas.Translate(command.Offset.Reverse());
}
CurrentRow = FindLastRenderedRow(renderingCommands) + 1;
var isFullRender = FindLastRenderedRow(renderingCommands) == StartingRowsCount;
if (isFullRender)
ResetState();
}
private int FindLastRenderedRow(ICollection<TableCellRenderingCommand> commands)
{
return commands
.GroupBy(x => x.Cell.Row)
.Where(x => x.All(y => y.Measurement.Type == SpacePlanType.FullRender))
.Select(x => x.Key)
.DefaultIfEmpty(0)
.Max();
}
private void UpdateColumnsWidth(float availableWidth)
{
var constantWidth = Columns.Sum(x => x.ConstantSize);
var relativeWidth = Columns.Sum(x => x.RelativeSize);
var widthPerRelativeUnit = (relativeWidth > 0) ? (availableWidth - constantWidth) / relativeWidth : 0;
foreach (var column in Columns)
{
column.Width = column.ConstantSize + column.RelativeSize * widthPerRelativeUnit;
}
}
private ICollection<TableCellRenderingCommand> PlanLayout(Size availableSpace)
{
var columnOffsets = GetColumnLeftOffsets(Columns);
var commands = GetRenderingCommands();
if (!commands.Any())
return commands;
var tableHeight = commands.Max(cell => cell.Offset.Y + cell.Size.Height);
if (ExtendLastCellsToTableBottom)
AdjustLastCellSizes(tableHeight, commands);
return commands;
static float[] GetColumnLeftOffsets(IList<TableColumnDefinition> columns)
{
var cellOffsets = new float[columns.Count + 1];
cellOffsets[0] = 0;
foreach (var column in Enumerable.Range(1, cellOffsets.Length - 1))
cellOffsets[column] = columns[column - 1].Width + cellOffsets[column - 1];
return cellOffsets;
}
ICollection<TableCellRenderingCommand> GetRenderingCommands()
{
var rowBottomOffsets = new DynamicDictionary<int, float>();
var cellsToTry = Enumerable
.Range(CurrentRow, RowsCount - CurrentRow + 1)
.SelectMany(x => OrderedCells[x]);
var currentRow = CurrentRow;
var maxRenderingRow = RowsCount;
var commands = new List<TableCellRenderingCommand>();
foreach (var cell in cellsToTry)
{
// update position of previous row
if (cell.Row > currentRow)
{
rowBottomOffsets[currentRow] = Math.Max(rowBottomOffsets[currentRow], rowBottomOffsets[currentRow - 1]);
if (rowBottomOffsets[currentRow - 1] > availableSpace.Height + Size.Epsilon)
break;
foreach (var row in Enumerable.Range(cell.Row, cell.Row - currentRow))
rowBottomOffsets[row] = Math.Max(rowBottomOffsets[row - 1], rowBottomOffsets[row]);
currentRow = cell.Row;
}
// cell visibility optimizations
if (cell.Row > maxRenderingRow)
break;
if (cell.IsRendered)
continue;
// calculate cell position / size
var topOffset = rowBottomOffsets[cell.Row - 1];
var availableWidth = GetCellWidth(cell);
var availableHeight = availableSpace.Height - topOffset;
var availableCellSize = new Size(availableWidth, availableHeight);
var cellSize = cell.Measure(availableCellSize);
// corner case: if cell within the row is not fully rendered, do not attempt to render next row
if (cellSize.Type == SpacePlanType.PartialRender)
{
maxRenderingRow = Math.Min(maxRenderingRow, cell.Row + cell.RowSpan - 1);
}
// corner case: if cell within the row want to wrap to the next page, do not attempt to render this row
if (cellSize.Type == SpacePlanType.Wrap)
{
maxRenderingRow = Math.Min(maxRenderingRow, cell.Row - 1);
continue;
}
// update position of the last row that cell occupies
var bottomRow = cell.Row + cell.RowSpan - 1;
rowBottomOffsets[bottomRow] = Math.Max(rowBottomOffsets[bottomRow], topOffset + cellSize.Height);
// accept cell to be rendered
commands.Add(new TableCellRenderingCommand()
{
Cell = cell,
Measurement = cellSize,
Size = new Size(availableWidth, cellSize.Height),
Offset = new Position(columnOffsets[cell.Column - 1], topOffset)
});
}
if (!commands.Any())
return commands;
var maxRow = commands.Select(x => x.Cell).Max(x => x.Row + x.RowSpan);
foreach (var row in Enumerable.Range(CurrentRow, maxRow - CurrentRow))
rowBottomOffsets[row] = Math.Max(rowBottomOffsets[row - 1], rowBottomOffsets[row]);
AdjustCellSizes(commands, rowBottomOffsets);
// corner case: reject cell if other cells within the same row are rejected
return commands.Where(x => x.Cell.Row <= maxRenderingRow).ToList();
}
// corner sase: if two cells end up on the same row (a.Row + a.RowSpan = b.Row + b.RowSpan),
// bottom edges of their bounding boxes should be at the same level
static void AdjustCellSizes(ICollection<TableCellRenderingCommand> commands, DynamicDictionary<int, float> rowBottomOffsets)
{
foreach (var command in commands)
{
var lastRow = command.Cell.Row + command.Cell.RowSpan - 1;
var height = rowBottomOffsets[lastRow] - command.Offset.Y;
command.Size = new Size(command.Size.Width, height);
command.Offset = new Position(command.Offset.X, rowBottomOffsets[command.Cell.Row - 1]);
}
}
// corner sase: all cells, that are last ones in their respective columns, should take all remaining space
static void AdjustLastCellSizes(float tableHeight, ICollection<TableCellRenderingCommand> commands)
{
var columnsCount = commands.Select(x => x.Cell).Max(x => x.Column + x.ColumnSpan - 1);
foreach (var column in Enumerable.Range(1, columnsCount))
{
var lastCellInColumn = commands
.Where(x => x.Cell.Column <= column && column < x.Cell.Column + x.Cell.ColumnSpan)
.OrderByDescending(x => x.Cell.Row + x.Cell.RowSpan)
.FirstOrDefault();
if (lastCellInColumn == null)
continue;
lastCellInColumn.Size = new Size(lastCellInColumn.Size.Width, tableHeight - lastCellInColumn.Offset.Y);
}
}
float GetCellWidth(TableCell cell)
{
return columnOffsets[cell.Column + cell.ColumnSpan - 1] - columnOffsets[cell.Column - 1];
}
}
}
}
-13
View File
@@ -1,13 +0,0 @@
namespace QuestPDF.Elements.Table
{
internal class TableCell : Container, ITableCellContainer
{
public int Row { get; set; } = 0;
public int RowSpan { get; set; } = 1;
public int Column { get; set; } = 0;
public int ColumnSpan { get; set; } = 1;
public bool IsRendered { get; set; }
}
}
@@ -1,13 +0,0 @@
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Table
{
internal class TableCellRenderingCommand
{
public TableCell Cell { get; set; }
public SpacePlan Measurement { get; set; }
public Size Size { get; set; }
public Position Offset { get; set; }
}
}
@@ -1,16 +0,0 @@
namespace QuestPDF.Elements.Table
{
internal class TableColumnDefinition
{
public float ConstantSize { get; }
public float RelativeSize { get; }
internal float Width { get; set; }
public TableColumnDefinition(float constantSize, float relativeSize)
{
ConstantSize = constantSize;
RelativeSize = relativeSize;
}
}
}
@@ -1,99 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Fluent;
namespace QuestPDF.Elements.Table
{
static class TableLayoutValidator
{
public static void PlanCellPositions(this Table table)
{
PlanCellPositions(table.Columns.Count, table.Cells);
}
private static void PlanCellPositions(int columnsCount, ICollection<TableCell> cells)
{
var cellsWindow = new List<TableCell>();
(int x, int y) currentLocation = (1, 1);
foreach (var cell in cells)
{
if (cellsWindow.Count > Math.Max(columnsCount, 16))
{
cellsWindow = cellsWindow
.Where(x => x.Row + x.RowSpan > currentLocation.y)
.ToList();
}
SetPartialLocation(cell);
if (cell.HasLocation())
{
cellsWindow.Add(cell);
currentLocation = (cell.Column, cell.Row);
continue;
}
foreach (var location in GenerateCoordinates(columnsCount, currentLocation))
{
if (location.x + cell.ColumnSpan - 1 > columnsCount)
continue;
cell.Column = location.x;
cell.Row = location.y;
if (cell.CollidesWithAnyOf(cellsWindow))
continue;
cellsWindow.Add(cell);
currentLocation = (cell.Column, cell.Row);
break;
}
}
}
private static IEnumerable<(int x, int y)> GenerateCoordinates(int columnsCount, (int x, int y) startPosition)
{
if (startPosition.x > columnsCount)
throw new ArgumentException();
foreach (var x in Enumerable.Range(startPosition.x, columnsCount - startPosition.x + 1))
yield return (x, startPosition.y);
foreach (var y in Enumerable.Range(startPosition.y + 1, 1_000_000))
foreach (var x in Enumerable.Range(1, columnsCount))
yield return (x, y);
}
private static bool CollidesWith(this TableCell cell, TableCell neighbour)
{
return cell.Column < neighbour.Column + neighbour.ColumnSpan &&
cell.Column + cell.ColumnSpan > neighbour.Column &&
cell.Row < neighbour.Row + neighbour.RowSpan &&
cell.RowSpan + cell.Row > neighbour.Row;
}
private static bool CollidesWithAnyOf(this TableCell cell, ICollection<TableCell> neighbours)
{
return neighbours.Any(cell.CollidesWith);
}
private static void SetPartialLocation(this TableCell cell)
{
if (cell.Row == default && cell.Column == default)
return;
if (cell.Row == default)
cell.Row = 1;
if (cell.Column == default)
cell.Column = 1;
}
private static bool HasLocation(this TableCell cell)
{
return cell.Row != 0 && cell.Column != 0;
}
}
}
@@ -1,41 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Fluent;
namespace QuestPDF.Elements.Table
{
static class TableLayoutPlanner
{
public static void ValidateCellPositions(this Table table)
{
ValidateCellPositions(table.Columns.Count, table.Cells);
}
private static void ValidateCellPositions(int columnsCount, ICollection<TableCell> cells)
{
const string prefix = "Detected issue in table cells configuration.";
foreach (var cell in cells)
{
if (cell.Column < 1)
throw new DocumentComposeException($"{prefix} A cell column position should be greater or equal to 1. Got {cell.Column}.");
if (cell.Row < 1)
throw new DocumentComposeException($"{prefix} A cell row position should be greater or equal to 1. Got {cell.Row}.");
if (cell.Column > columnsCount)
throw new DocumentComposeException($"{prefix} Cell starts at column that does not exist. Cell details: {GetCellDetails(cell)}.");
if (cell.Column + cell.ColumnSpan - 1 > columnsCount)
throw new DocumentComposeException($"{prefix} Table cell location is incorrect. Cell spans over columns that do not exist. Cell details: {GetCellDetails(cell)}.");
}
string GetCellDetails(TableCell cell)
{
return $"Row {cell.Row}, Column {cell.Column}, RowSpan {cell.RowSpan}, ColumnSpan {cell.ColumnSpan}";
}
}
}
}
@@ -1,5 +1,4 @@
using QuestPDF.Elements.Text.Calculation; using QuestPDF.Elements.Text.Calculation;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Items namespace QuestPDF.Elements.Text.Items
{ {
@@ -1,6 +1,5 @@
using QuestPDF.Drawing; using QuestPDF.Drawing;
using QuestPDF.Elements.Text.Calculation; using QuestPDF.Elements.Text.Calculation;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Items namespace QuestPDF.Elements.Text.Items
@@ -11,8 +10,8 @@ namespace QuestPDF.Elements.Text.Items
public TextMeasurementResult? Measure(TextMeasurementRequest request) public TextMeasurementResult? Measure(TextMeasurementRequest request)
{ {
Element.VisitChildren(x => (x as IStateResettable)?.ResetState()); Element.HandleVisitor(x => (x as IStateResettable)?.ResetState());
Element.VisitChildren(x => x.Initialize(request.PageContext, request.Canvas)); Element.HandleVisitor(x => x.Initialize(request.PageContext, request.Canvas));
var measurement = Element.Measure(new Size(request.AvailableWidth, Size.Max.Height)); var measurement = Element.Measure(new Size(request.AvailableWidth, Size.Max.Height));
@@ -36,8 +35,8 @@ namespace QuestPDF.Elements.Text.Items
public void Draw(TextDrawingRequest request) public void Draw(TextDrawingRequest request)
{ {
Element.VisitChildren(x => (x as IStateResettable)?.ResetState()); Element.HandleVisitor(x => (x as IStateResettable)?.ResetState());
Element.VisitChildren(x => x.Initialize(request.PageContext, request.Canvas)); Element.HandleVisitor(x => x.Initialize(request.PageContext, request.Canvas));
request.Canvas.Translate(new Position(0, request.TotalAscent)); request.Canvas.Translate(new Position(0, request.TotalAscent));
Element.Draw(new Size(request.TextSize.Width, -request.TotalAscent)); Element.Draw(new Size(request.TextSize.Width, -request.TotalAscent));
@@ -3,22 +3,33 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Items namespace QuestPDF.Elements.Text.Items
{ {
internal class TextBlockExternalLink : TextBlockSpan internal class TextBlockExternalLink : ITextBlockItem
{ {
public TextStyle Style { get; set; } = new TextStyle();
public string Text { get; set; }
public string Url { get; set; } public string Url { get; set; }
public override TextMeasurementResult? Measure(TextMeasurementRequest request) public TextMeasurementResult? Measure(TextMeasurementRequest request)
{ {
return MeasureWithoutCache(request); return GetItem().MeasureWithoutCache(request);
} }
public override void Draw(TextDrawingRequest request) public void Draw(TextDrawingRequest request)
{ {
request.Canvas.Translate(new Position(0, request.TotalAscent)); request.Canvas.Translate(new Position(0, request.TotalAscent));
request.Canvas.DrawExternalLink(Url, new Size(request.TextSize.Width, request.TextSize.Height)); request.Canvas.DrawExternalLink(Url, new Size(request.TextSize.Width, request.TextSize.Height));
request.Canvas.Translate(new Position(0, -request.TotalAscent)); request.Canvas.Translate(new Position(0, -request.TotalAscent));
base.Draw(request); GetItem().Draw(request);
}
private TextBlockSpan GetItem()
{
return new TextBlockSpan
{
Style = Style,
Text = Text
};
} }
} }
} }
@@ -3,22 +3,33 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Items namespace QuestPDF.Elements.Text.Items
{ {
internal class TextBlockInternalLink : TextBlockSpan internal class TextBlockInternalLink : ITextBlockItem
{ {
public TextStyle Style { get; set; } = new TextStyle();
public string Text { get; set; }
public string LocationName { get; set; } public string LocationName { get; set; }
public override TextMeasurementResult? Measure(TextMeasurementRequest request) public TextMeasurementResult? Measure(TextMeasurementRequest request)
{ {
return MeasureWithoutCache(request); return GetItem().MeasureWithoutCache(request);
} }
public override void Draw(TextDrawingRequest request) public void Draw(TextDrawingRequest request)
{ {
request.Canvas.Translate(new Position(0, request.TotalAscent)); request.Canvas.Translate(new Position(0, request.TotalAscent));
request.Canvas.DrawLocationLink(LocationName, new Size(request.TextSize.Width, request.TextSize.Height)); request.Canvas.DrawLocationLink(LocationName, new Size(request.TextSize.Width, request.TextSize.Height));
request.Canvas.Translate(new Position(0, -request.TotalAscent)); request.Canvas.Translate(new Position(0, -request.TotalAscent));
base.Draw(request); GetItem().Draw(request);
}
private TextBlockSpan GetItem()
{
return new TextBlockSpan
{
Style = Style,
Text = Text
};
} }
} }
} }
@@ -3,23 +3,22 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Items namespace QuestPDF.Elements.Text.Items
{ {
internal class TextBlockPageNumber : TextBlockSpan internal class TextBlockPageNumber : ITextBlockItem
{ {
public TextStyle Style { get; set; } = new TextStyle();
public string SlotName { get; set; } public string SlotName { get; set; }
public override TextMeasurementResult? Measure(TextMeasurementRequest request) public TextMeasurementResult? Measure(TextMeasurementRequest request)
{ {
SetPageNumber(request.PageContext); return GetItem(request.PageContext).MeasureWithoutCache(request);
return MeasureWithoutCache(request);
} }
public override void Draw(TextDrawingRequest request) public void Draw(TextDrawingRequest request)
{ {
SetPageNumber(request.PageContext); GetItem(request.PageContext).Draw(request);
base.Draw(request);
} }
private void SetPageNumber(IPageContext context) private TextBlockSpan GetItem(IPageContext context)
{ {
var pageNumberPlaceholder = 123; var pageNumberPlaceholder = 123;
@@ -27,7 +26,11 @@ namespace QuestPDF.Elements.Text.Items
? context.GetLocationPage(SlotName) ? context.GetLocationPage(SlotName)
: pageNumberPlaceholder; : pageNumberPlaceholder;
Text = pageNumber.ToString(); return new TextBlockSpan
{
Style = Style,
Text = pageNumber.ToString()
};
} }
} }
} }
@@ -14,7 +14,7 @@ namespace QuestPDF.Elements.Text.Items
private Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?> MeasureCache = private Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?> MeasureCache =
new Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?>(); new Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?>();
public virtual TextMeasurementResult? Measure(TextMeasurementRequest request) public TextMeasurementResult? Measure(TextMeasurementRequest request)
{ {
var cacheKey = (request.StartIndex, request.AvailableWidth); var cacheKey = (request.StartIndex, request.AvailableWidth);
@@ -45,7 +45,7 @@ namespace QuestPDF.Elements.Text.Items
{ {
Width = 0, Width = 0,
LineHeight = Style.LineHeight ?? 1, LineHeight = Style.LineHeight,
Ascent = fontMetrics.Ascent, Ascent = fontMetrics.Ascent,
Descent = fontMetrics.Descent Descent = fontMetrics.Descent
}; };
@@ -96,7 +96,7 @@ namespace QuestPDF.Elements.Text.Items
Ascent = fontMetrics.Ascent, Ascent = fontMetrics.Ascent,
Descent = fontMetrics.Descent, Descent = fontMetrics.Descent,
LineHeight = Style.LineHeight ?? 1, LineHeight = Style.LineHeight,
StartIndex = startIndex, StartIndex = startIndex,
EndIndex = endIndex, EndIndex = endIndex,
@@ -105,7 +105,7 @@ namespace QuestPDF.Elements.Text.Items
}; };
} }
public virtual void Draw(TextDrawingRequest request) public void Draw(TextDrawingRequest request)
{ {
var fontMetrics = Style.ToFontMetrics(); var fontMetrics = Style.ToFontMetrics();
@@ -115,12 +115,12 @@ namespace QuestPDF.Elements.Text.Items
request.Canvas.DrawText(text, Position.Zero, Style); request.Canvas.DrawText(text, Position.Zero, Style);
// draw underline // draw underline
if ((Style.HasUnderline ?? false) && fontMetrics.UnderlinePosition.HasValue) if (Style.HasUnderline && fontMetrics.UnderlinePosition.HasValue)
DrawLine(fontMetrics.UnderlinePosition.Value, fontMetrics.UnderlineThickness ?? 1); DrawLine(fontMetrics.UnderlinePosition.Value, fontMetrics.UnderlineThickness.Value);
// draw stroke // draw stroke
if ((Style.HasStrikethrough ?? false) && fontMetrics.StrikeoutPosition.HasValue) if (Style.HasStrikethrough && fontMetrics.StrikeoutPosition.HasValue)
DrawLine(fontMetrics.StrikeoutPosition.Value, fontMetrics.StrikeoutThickness ?? 1); DrawLine(fontMetrics.StrikeoutPosition.Value, fontMetrics.StrikeoutThickness.Value);
void DrawLine(float offset, float thickness) void DrawLine(float offset, float thickness)
{ {
+5 -7
View File
@@ -11,16 +11,14 @@ namespace QuestPDF.Elements.Text
internal class TextBlock : Element, IStateResettable internal class TextBlock : Element, IStateResettable
{ {
public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left; public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
public List<ITextBlockItem> Items { get; set; } = new List<ITextBlockItem>(); public List<ITextBlockItem> Children { get; set; } = new List<ITextBlockItem>();
public string Text => string.Join(" ", Items.Where(x => x is TextBlockSpan).Cast<TextBlockSpan>().Select(x => x.Text)); public Queue<ITextBlockItem> RenderingQueue { get; set; }
public int CurrentElementIndex { get; set; }
private Queue<ITextBlockItem> RenderingQueue { get; set; }
private int CurrentElementIndex { get; set; }
public void ResetState() public void ResetState()
{ {
RenderingQueue = new Queue<ITextBlockItem>(Items); RenderingQueue = new Queue<ITextBlockItem>(Children);
CurrentElementIndex = 0; CurrentElementIndex = 0;
} }
@@ -32,7 +30,7 @@ namespace QuestPDF.Elements.Text
var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height).ToList(); var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height).ToList();
if (!lines.Any()) if (!lines.Any())
return SpacePlan.Wrap(); return SpacePlan.PartialRender(Size.Zero);
var width = lines.Max(x => x.Width); var width = lines.Max(x => x.Width);
var height = lines.Sum(x => x.LineHeight); var height = lines.Sum(x => x.LineHeight);
+14 -20
View File
@@ -14,45 +14,39 @@ namespace QuestPDF.Fluent
return element.Element(border); return element.Element(border);
} }
public static IContainer Border(this IContainer element, float value, Unit unit = Unit.Point) public static IContainer Border(this IContainer element, float value)
{ {
return element return element.BorderHorizontal(value).BorderVertical(value);
.BorderHorizontal(value, unit)
.BorderVertical(value, unit);
} }
public static IContainer BorderVertical(this IContainer element, float value, Unit unit = Unit.Point) public static IContainer BorderVertical(this IContainer element, float value)
{ {
return element return element.BorderLeft(value).BorderRight(value);
.BorderLeft(value, unit)
.BorderRight(value, unit);
} }
public static IContainer BorderHorizontal(this IContainer element, float value, Unit unit = Unit.Point) public static IContainer BorderHorizontal(this IContainer element, float value)
{ {
return element return element.BorderTop(value).BorderBottom(value);
.BorderTop(value, unit)
.BorderBottom(value, unit);
} }
public static IContainer BorderLeft(this IContainer element, float value, Unit unit = Unit.Point) public static IContainer BorderLeft(this IContainer element, float value)
{ {
return element.Border(x => x.Left = value.ToPoints(unit)); return element.Border(x => x.Left = value);
} }
public static IContainer BorderRight(this IContainer element, float value, Unit unit = Unit.Point) public static IContainer BorderRight(this IContainer element, float value)
{ {
return element.Border(x => x.Right = value.ToPoints(unit)); return element.Border(x => x.Right = value);
} }
public static IContainer BorderTop(this IContainer element, float value, Unit unit = Unit.Point) public static IContainer BorderTop(this IContainer element, float value)
{ {
return element.Border(x => x.Top = value.ToPoints(unit)); return element.Border(x => x.Top = value);
} }
public static IContainer BorderBottom(this IContainer element, float value, Unit unit = Unit.Point) public static IContainer BorderBottom(this IContainer element, float value)
{ {
return element.Border(x => x.Bottom = value.ToPoints(unit)); return element.Border(x => x.Bottom = value);
} }
public static IContainer BorderColor(this IContainer element, string color) public static IContainer BorderColor(this IContainer element, string color)
-44
View File
@@ -1,44 +0,0 @@
using System;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
namespace QuestPDF.Fluent
{
public class ColumnDescriptor
{
internal Column Column { get; } = new();
public void Spacing(float value, Unit unit = Unit.Point)
{
Column.Spacing = value.ToPoints(unit);
}
public IContainer Item()
{
var container = new Container();
Column.Items.Add(new ColumnItem
{
Child = container
});
return container;
}
}
public static class ColumnExtensions
{
[Obsolete("This element has been renamed since version 2022.2. Please use the 'Column' method.")]
public static void Stack(this IContainer element, Action<ColumnDescriptor> handler)
{
element.Column(handler);
}
public static void Column(this IContainer element, Action<ColumnDescriptor> handler)
{
var descriptor = new ColumnDescriptor();
handler(descriptor);
element.Element(descriptor.Column);
}
}
}
+23 -41
View File
@@ -1,53 +1,41 @@
using System; using System;
using System.Linq.Expressions; using System.Linq.Expressions;
using QuestPDF.Drawing.Exceptions; using QuestPDF.Drawing.Exceptions;
using QuestPDF.Elements;
using QuestPDF.Helpers; using QuestPDF.Helpers;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
namespace QuestPDF.Fluent namespace QuestPDF.Fluent
{ {
internal class ComponentDescriptor<TComponent> where TComponent : IComponent class ComponentDescriptor<T> where T : IComponent
{ {
public TComponent Component { get; } public T Component { get; }
public ComponentDescriptor(TComponent component) public ComponentDescriptor(T component)
{ {
Component = component; Component = component;
} }
public IContainer Slot(Expression<Func<TComponent, ISlot>> selector) public IContainer Slot(Expression<Func<T, ISlot>> selector)
{ {
AssureThatTheSlotIsNotConfiguredYet(selector); try
var slot = new Slot();
Component.SetPropertyValue(selector, slot);
return slot;
}
public void Slot<TArgument>(Expression<Func<TComponent, ISlot<TArgument>>> selector, Action<TArgument, IContainer> handler)
{
AssureThatTheSlotIsNotConfiguredYet(selector);
var slot = new Slot<TArgument>
{ {
GetContent = argument => var existingValue = Component.GetPropertyValue(selector);
{
var container = new Container();
handler(argument, container);
return container;
}
};
Component.SetPropertyValue(selector, slot); if (existingValue != null)
} throw new DocumentComposeException($"The slot {selector.GetPropertyName()} of the component {(typeof(T).Name)} was already used.");
private void AssureThatTheSlotIsNotConfiguredYet<TSlot>(Expression<Func<TComponent, TSlot>> selector) where TSlot : class var slot = new Slot();
{ Component.SetPropertyValue(selector, slot);
var existingValue = Component.GetPropertyValue(selector); return slot;
}
if (existingValue != null) catch (DocumentComposeException)
throw new DocumentComposeException($"The slot {selector.GetPropertyName()} of the component {(typeof(TComponent).Name)} was already used."); {
throw;
}
catch
{
throw new DocumentComposeException("Every slot in a component should be a public property with getter and setter.");
}
} }
} }
@@ -63,7 +51,7 @@ namespace QuestPDF.Fluent
element.Component(new T(), null); element.Component(new T(), null);
} }
internal static void Component<T>(this IContainer element, T component, Action<ComponentDescriptor<T>>? handler = null) where T : IComponent static void Component<T>(this IContainer element, T component, Action<ComponentDescriptor<T>>? handler = null) where T : IComponent
{ {
var descriptor = new ComponentDescriptor<T>(component); var descriptor = new ComponentDescriptor<T>(component);
handler?.Invoke(descriptor); handler?.Invoke(descriptor);
@@ -74,21 +62,15 @@ namespace QuestPDF.Fluent
component.Compose(element.Container()); component.Compose(element.Container());
} }
internal static void Component<T>(this IContainer element, Action<ComponentDescriptor<T>>? handler = null) where T : IComponent, new() static void Component<T>(this IContainer element, Action<ComponentDescriptor<T>>? handler = null) where T : IComponent, new()
{ {
element.Component(new T(), handler); element.Component(new T(), handler);
} }
internal static void Slot(this IContainer element, ISlot slot) static void Slot(this IContainer element, ISlot slot)
{ {
var child = (slot as Slot)?.Child; var child = (slot as Slot)?.Child;
element.Element(child); element.Element(child);
} }
internal static void Slot<TArgument>(this IContainer element, ISlot<TArgument> slot, TArgument argument)
{
var child = (slot as Slot<TArgument>)?.GetContent(argument) ?? Empty.Instance;
element.Element(child);
}
} }
} }
+12 -16
View File
@@ -14,38 +14,34 @@ namespace QuestPDF.Fluent
return element.Element(constrained); return element.Element(constrained);
} }
public static IContainer Width(this IContainer element, float value, Unit unit = Unit.Point) public static IContainer Width(this IContainer element, float value)
{ {
return element return element.MinWidth(value).MaxWidth(value);
.MinWidth(value, unit)
.MaxWidth(value, unit);
} }
public static IContainer MinWidth(this IContainer element, float value, Unit unit = Unit.Point) public static IContainer MinWidth(this IContainer element, float value)
{ {
return element.Constrained(x => x.MinWidth = value.ToPoints(unit)); return element.Constrained(x => x.MinWidth = value);
} }
public static IContainer MaxWidth(this IContainer element, float value, Unit unit = Unit.Point) public static IContainer MaxWidth(this IContainer element, float value)
{ {
return element.Constrained(x => x.MaxWidth = value.ToPoints(unit)); return element.Constrained(x => x.MaxWidth = value);
} }
public static IContainer Height(this IContainer element, float value, Unit unit = Unit.Point) public static IContainer Height(this IContainer element, float value)
{ {
return element return element.MinHeight(value).MaxHeight(value);
.MinHeight(value, unit)
.MaxHeight(value, unit);
} }
public static IContainer MinHeight(this IContainer element, float value, Unit unit = Unit.Point) public static IContainer MinHeight(this IContainer element, float value)
{ {
return element.Constrained(x => x.MinHeight = value.ToPoints(unit)); return element.Constrained(x => x.MinHeight = value);
} }
public static IContainer MaxHeight(this IContainer element, float value, Unit unit = Unit.Point) public static IContainer MaxHeight(this IContainer element, float value)
{ {
return element.Constrained(x => x.MaxHeight = value.ToPoints(unit)); return element.Constrained(x => x.MaxHeight = value);
} }
} }
} }
+6 -38
View File
@@ -8,16 +8,16 @@ namespace QuestPDF.Fluent
{ {
internal Decoration Decoration { get; } = new Decoration(); internal Decoration Decoration { get; } = new Decoration();
public IContainer Before() public IContainer Header()
{ {
var container = new Container(); var container = new Container();
Decoration.Before = container; Decoration.Header = container;
return container; return container;
} }
public void Before(Action<IContainer> handler) public void Header(Action<IContainer> handler)
{ {
handler?.Invoke(Before()); handler?.Invoke(Header());
} }
public IContainer Content() public IContainer Content()
@@ -32,49 +32,17 @@ namespace QuestPDF.Fluent
handler?.Invoke(Content()); handler?.Invoke(Content());
} }
public IContainer After()
{
var container = new Container();
Decoration.After = container;
return container;
}
public void After(Action<IContainer> handler)
{
handler?.Invoke(After());
}
#region Obsolete
[Obsolete("This element has been renamed since version 2022.2. Please use the 'Before' method.")]
public IContainer Header()
{
var container = new Container();
Decoration.Before = container;
return container;
}
[Obsolete("This element has been renamed since version 2022.2. Please use the 'Before' method.")]
public void Header(Action<IContainer> handler)
{
handler?.Invoke(Header());
}
[Obsolete("This element has been renamed since version 2022.2. Please use the 'After' method.")]
public IContainer Footer() public IContainer Footer()
{ {
var container = new Container(); var container = new Container();
Decoration.After = container; Decoration.Footer = container;
return container; return container;
} }
[Obsolete("This element has been renamed since version 2022.2. Please use the 'After' method.")]
public void Footer(Action<IContainer> handler) public void Footer(Action<IContainer> handler)
{ {
handler?.Invoke(Footer()); handler?.Invoke(Footer());
} }
#endregion
} }
public static class DecorationExtensions public static class DecorationExtensions
@@ -84,7 +52,7 @@ namespace QuestPDF.Fluent
var descriptor = new DecorationDescriptor(); var descriptor = new DecorationDescriptor();
handler(descriptor); handler(descriptor);
element.Element(descriptor.Decoration); element.Component(descriptor.Decoration);
} }
} }
} }
+11 -30
View File
@@ -41,6 +41,16 @@ namespace QuestPDF.Fluent
return handler(parent.Container()).Container(); return handler(parent.Container()).Container();
} }
public static void Dynamic<TDynamic>(this IContainer element) where TDynamic : IDynamicComponent, new()
{
element.Dynamic(new TDynamic());
}
public static void Dynamic(this IContainer element, IDynamicComponent dynamicElement)
{
element.Element(new DynamicHost(dynamicElement));
}
public static IContainer AspectRatio(this IContainer element, float ratio, AspectRatioOption option = AspectRatioOption.FitWidth) public static IContainer AspectRatio(this IContainer element, float ratio, AspectRatioOption option = AspectRatioOption.FitWidth)
{ {
return element.Element(new AspectRatio return element.Element(new AspectRatio
@@ -71,11 +81,6 @@ namespace QuestPDF.Fluent
return element.Element(new ShowOnce()); return element.Element(new ShowOnce());
} }
public static IContainer SkipOnce(this IContainer element)
{
return element.Element(new SkipOnce());
}
public static IContainer ShowEntire(this IContainer element) public static IContainer ShowEntire(this IContainer element)
{ {
return element.Element(new ShowEntire()); return element.Element(new ShowEntire());
@@ -136,38 +141,14 @@ namespace QuestPDF.Fluent
}); });
} }
[Obsolete("This element has been renamed since version 2022.1. Please use the MinimalBox method.")]
public static IContainer Box(this IContainer element) public static IContainer Box(this IContainer element)
{ {
return element.Element(new MinimalBox()); return element.Element(new Box());
}
public static IContainer MinimalBox(this IContainer element)
{
return element.Element(new MinimalBox());
} }
public static IContainer Unconstrained(this IContainer element) public static IContainer Unconstrained(this IContainer element)
{ {
return element.Element(new Unconstrained()); return element.Element(new Unconstrained());
} }
public static IContainer DefaultTextStyle(this IContainer element, TextStyle textStyle)
{
return element.Element(new DefaultTextStyle
{
TextStyle = textStyle
});
}
public static IContainer StopPaging(this IContainer element)
{
return element.Element(new StopPaging());
}
public static IContainer ScaleToFit(this IContainer element)
{
return element.Element(new ScaleToFit());
}
} }
} }

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