diff --git a/QuestPDF.Examples/ChartExamples.cs b/QuestPDF.Examples/ChartExamples.cs index 2d61057..a8eac35 100644 --- a/QuestPDF.Examples/ChartExamples.cs +++ b/QuestPDF.Examples/ChartExamples.cs @@ -52,14 +52,14 @@ namespace QuestPDF.Examples container .Background(Colors.White) .Padding(25) - .Stack(stack => + .Column(column => { - stack + column .Item() .PaddingBottom(10) .Text("Chart example", TextStyle.Default.Size(20).SemiBold().Color(Colors.Blue.Medium)); - stack + column .Item() .Border(1) .ExtendHorizontal() diff --git a/QuestPDF.Examples/ColumnExamples.cs b/QuestPDF.Examples/ColumnExamples.cs new file mode 100644 index 0000000..3c5a222 --- /dev/null +++ b/QuestPDF.Examples/ColumnExamples.cs @@ -0,0 +1,37 @@ +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()); + } + }); + }); + } + } +} \ No newline at end of file diff --git a/QuestPDF.Examples/ComplexLayoutBenchmark.cs b/QuestPDF.Examples/ComplexLayoutBenchmark.cs index 60538f4..fb5fd19 100644 --- a/QuestPDF.Examples/ComplexLayoutBenchmark.cs +++ b/QuestPDF.Examples/ComplexLayoutBenchmark.cs @@ -36,8 +36,8 @@ namespace QuestPDF.Examples .BorderColor(Colors.Black) .Row(row => { - row.RelativeColumn().Element(x => GenerateStructure(x, level)); - row.RelativeColumn().Element(x => GenerateStructure(x, level)); + row.RelativeItem().Element(x => GenerateStructure(x, level)); + row.RelativeItem().Element(x => GenerateStructure(x, level)); }); } else @@ -45,10 +45,10 @@ namespace QuestPDF.Examples container .Border(level / 4f) .BorderColor(Colors.Black) - .Stack(stack => + .Column(column => { - stack.Item().Element(x => GenerateStructure(x, level)); - stack.Item().Element(x => GenerateStructure(x, level)); + column.Item().Element(x => GenerateStructure(x, level)); + column.Item().Element(x => GenerateStructure(x, level)); }); } } diff --git a/QuestPDF.Examples/ContinousPage.cs b/QuestPDF.Examples/ContinousPage.cs index 5593d5f..1e5c545 100644 --- a/QuestPDF.Examples/ContinousPage.cs +++ b/QuestPDF.Examples/ContinousPage.cs @@ -22,10 +22,10 @@ namespace QuestPDF.Examples page.Header().Text("Header"); - page.Content().PaddingVertical(10).Border(1).Padding(10).Stack(stack => + page.Content().PaddingVertical(10).Border(1).Padding(10).Column(column => { foreach (var index in Enumerable.Range(1, 100)) - stack.Item().Text($"Line {index}", TextStyle.Default.Color(Placeholders.Color())); + column.Item().Text($"Line {index}", TextStyle.Default.Color(Placeholders.Color())); }); page.Footer().Text("Footer"); diff --git a/QuestPDF.Examples/DebuggingTesting.cs b/QuestPDF.Examples/DebuggingTesting.cs index cecaa95..c8434da 100644 --- a/QuestPDF.Examples/DebuggingTesting.cs +++ b/QuestPDF.Examples/DebuggingTesting.cs @@ -9,7 +9,7 @@ namespace QuestPDF.Examples public class DebuggingTesting { [Test] - public void Stack() + public void Column() { Assert.Throws(() => { @@ -23,7 +23,7 @@ namespace QuestPDF.Examples .Width(100) .Background(Colors.Grey.Lighten3) .DebugPointer("Example debug pointer") - .Stack(x => + .Column(x => { x.Item().Text("Test"); x.Item().Width(150); diff --git a/QuestPDF.Examples/DefaultTextStyleExample.cs b/QuestPDF.Examples/DefaultTextStyleExample.cs index c88f59c..e7db9eb 100644 --- a/QuestPDF.Examples/DefaultTextStyleExample.cs +++ b/QuestPDF.Examples/DefaultTextStyleExample.cs @@ -13,7 +13,7 @@ namespace QuestPDF.Examples { RenderingTest .Create() - .ProduceImages() + .ProducePdf() .ShowResults() .RenderDocument(container => { @@ -26,11 +26,11 @@ namespace QuestPDF.Examples page.Size(PageSizes.A4); page.Background(Colors.White); - page.Content().Stack(stack => + page.Content().Column(column => { - stack.Item().Text(Placeholders.Sentence()); + column.Item().Text(Placeholders.Sentence()); - stack.Item().Text(text => + column.Item().Text(text => { // text in this block is additionally semibold text.DefaultTextStyle(TextStyle.Default.SemiBold()); diff --git a/QuestPDF.Examples/DefaultTextStyleExamples.cs b/QuestPDF.Examples/DefaultTextStyleExamples.cs index dae5325..4165a39 100644 --- a/QuestPDF.Examples/DefaultTextStyleExamples.cs +++ b/QuestPDF.Examples/DefaultTextStyleExamples.cs @@ -24,12 +24,12 @@ namespace QuestPDF.Examples container .Padding(10) .DefaultTextStyle(TextStyle.Default.Bold().Underline()) - .Stack(stack => + .Column(column => { - stack.Item().Text("Default style applies to all children", TextStyle.Default); - stack.Item().Text("You can override certain styles", TextStyle.Default.Underline(false).Color(Colors.Green.Darken2)); + 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)); - stack.Item().PaddingTop(10).Border(1).Grid(grid => + column.Item().PaddingTop(10).Border(1).Grid(grid => { grid.Columns(4); diff --git a/QuestPDF.Examples/DifferentHeaderOnFirstPageExample.cs b/QuestPDF.Examples/DifferentHeaderOnFirstPageExample.cs index 9cae448..4c2b733 100644 --- a/QuestPDF.Examples/DifferentHeaderOnFirstPageExample.cs +++ b/QuestPDF.Examples/DifferentHeaderOnFirstPageExample.cs @@ -23,21 +23,21 @@ namespace QuestPDF.Examples container.Page(page => { page.Size(PageSizes.A6); - page.Margin(30); + page.Margin(5); page.Background(Colors.White); - page.Header().Stack(stack => + page.Header().Column(column => { - stack.Item().ShowOnce().Background(Colors.Blue.Lighten2).Height(60); - stack.Item().SkipOnce().Background(Colors.Green.Lighten2).Height(40); + column.Item().ShowOnce().Background(Colors.Blue.Lighten2).Height(60); + column.Item().SkipOnce().Background(Colors.Green.Lighten2).Height(40); }); - page.Content().PaddingVertical(10).Stack(stack => + page.Content().PaddingVertical(10).Column(column => { - stack.Spacing(10); + column.Spacing(10); foreach (var _ in Enumerable.Range(0, 13)) - stack.Item().Background(Colors.Grey.Lighten2).Height(40); + column.Item().Background(Colors.Grey.Lighten2).Height(40); }); page.Footer().AlignCenter().Text(text => diff --git a/QuestPDF.Examples/ElementExamples.cs b/QuestPDF.Examples/ElementExamples.cs index e37423b..635961c 100644 --- a/QuestPDF.Examples/ElementExamples.cs +++ b/QuestPDF.Examples/ElementExamples.cs @@ -40,7 +40,7 @@ namespace QuestPDF.Examples .Decoration(decoration => { decoration - .Header() + .Before() .Background(Colors.Grey.Medium) .Padding(10) .Text("Notes", TextStyle.Default.Size(16).Color("#FFF")); @@ -66,27 +66,27 @@ namespace QuestPDF.Examples container .Background("#FFF") .Padding(20) - .Stack(stack => + .Column(column => { - stack.Item() + column.Item() .PaddingBottom(10) .AlignCenter() .Text("This Row element is 700pt wide"); - stack.Item().Row(row => + column.Item().Row(row => { - row.ConstantColumn(100) + row.ConstantItem(100) .Background(Colors.Grey.Lighten1) .Padding(10) .ExtendVertical() .Text("This column is 100 pt wide"); - row.RelativeColumn() + row.RelativeItem() .Background(Colors.Grey.Lighten2) .Padding(10) .Text("This column takes 1/3 of the available space (200pt)"); - row.RelativeColumn(2) + row.RelativeItem(2) .Background(Colors.Grey.Lighten3) .Padding(10) .Text("This column takes 2/3 of the available space (400pt)"); @@ -109,15 +109,15 @@ namespace QuestPDF.Examples .Row(row => { row.Spacing(20); - row.RelativeColumn(2).Border(1).Background(Colors.Grey.Lighten1); - row.RelativeColumn(3).Border(1).Background(Colors.Grey.Lighten2); - row.RelativeColumn(4).Border(1).Background(Colors.Grey.Lighten3); + row.RelativeItem(2).Border(1).Background(Colors.Grey.Lighten1); + row.RelativeItem(3).Border(1).Background(Colors.Grey.Lighten2); + row.RelativeItem(4).Border(1).Background(Colors.Grey.Lighten3); }); }); } [Test] - public void Stack() + public void column() { RenderingTest .Create() @@ -127,13 +127,13 @@ namespace QuestPDF.Examples container .Background("#FFF") .Padding(15) - .Stack(stack => + .Column(column => { - stack.Spacing(15); + column.Spacing(15); - stack.Item().Background(Colors.Grey.Medium).Height(50); - stack.Item().Background(Colors.Grey.Lighten1).Height(100); - stack.Item().Background(Colors.Grey.Lighten2).Height(150); + column.Item().Background(Colors.Grey.Medium).Height(50); + column.Item().Background(Colors.Grey.Lighten1).Height(100); + column.Item().Background(Colors.Grey.Lighten2).Height(150); }); }); } @@ -267,12 +267,12 @@ namespace QuestPDF.Examples layers .PrimaryLayer() .Padding(25) - .Stack(stack => + .Column(column => { - stack.Spacing(5); + column.Spacing(5); foreach (var _ in Enumerable.Range(0, 7)) - stack.Item().Text(Placeholders.Sentence()); + column.Item().Text(Placeholders.Sentence()); }); // layer above the main content @@ -304,16 +304,16 @@ namespace QuestPDF.Examples // { // page.Header().PageNumber("Page {pdf:currentPage}"); // - // page.Content().Height(300).Stack(content => + // page.Content().Height(300).column(content => // { // content.Item().Height(200).Background(Colors.Grey.Lighten2); // - // content.Item().EnsureSpace(100).Stack(stack => + // content.Item().EnsureSpace(100).column(column => // { - // stack.Spacing(10); + // column.Spacing(10); // // foreach (var _ in Enumerable.Range(0, 4)) - // stack.Item().Height(50).Background(Colors.Green.Lighten1); + // column.Item().Height(50).Background(Colors.Green.Lighten1); // }); // }); // }); @@ -378,7 +378,7 @@ namespace QuestPDF.Examples .Row(row => { foreach (var color in colors) - row.RelativeColumn().Background(color); + row.RelativeItem().Background(color); }); }); } @@ -437,10 +437,10 @@ namespace QuestPDF.Examples { layers.Layer().Text("Something else"); - layers.PrimaryLayer().Stack(stack => + layers.PrimaryLayer().Column(column => { - stack.Item().PaddingTop(20).Text("Text 1"); - stack.Item().PaddingTop(40).Text("Text 2"); + column.Item().PaddingTop(20).Text("Text 1"); + column.Item().PaddingTop(40).Text("Text 2"); }); layers.Layer().Canvas((canvas, size) => @@ -501,13 +501,13 @@ namespace QuestPDF.Examples .SemiBold(); decoration - .Header() + .Before() .PaddingBottom(10) .Text("Example: scale component", headerFontStyle); decoration .Content() - .Stack(stack => + .Column(column => { var scales = new[] { 0.8f, 0.9f, 1.1f, 1.2f }; @@ -519,7 +519,7 @@ namespace QuestPDF.Examples var fontStyle = TextStyle.Default.Size(16); - stack + column .Item() .Border(1) .Background(fontColor) @@ -708,14 +708,14 @@ namespace QuestPDF.Examples .Border(2) .Row(row => { - row.ConstantColumn(25) + row.ConstantItem(25) .Border(1) .RotateLeft() .AlignCenter() .AlignMiddle() .Text("Sample text"); - row.RelativeColumn().Border(1).Padding(5).Text(Placeholders.Paragraph()); + row.RelativeItem().Border(1).Padding(5).Text(Placeholders.Paragraph()); }); }); } @@ -731,11 +731,11 @@ namespace QuestPDF.Examples container .Padding(25) .PaddingLeft(75) - .Stack(stack => + .Column(column => { - stack.Item().Width(300).Height(150).Background(Colors.Blue.Lighten4); + column.Item().Width(300).Height(150).Background(Colors.Blue.Lighten4); - stack + column .Item() // creates an infinite space for its child @@ -751,7 +751,7 @@ namespace QuestPDF.Examples .Background(Colors.Blue.Darken1); - stack.Item().Width(300).Height(150).Background(Colors.Blue.Lighten3); + column.Item().Width(300).Height(150).Background(Colors.Blue.Lighten3); }); }); } @@ -766,13 +766,13 @@ namespace QuestPDF.Examples { container .Padding(25) - .Stack(stack => + .Column(column => { - stack.Item().Row(row => + column.Item().Row(row => { - row.RelativeColumn().LabelCell("Label 1"); + row.RelativeItem().LabelCell("Label 1"); - row.RelativeColumn(3).Grid(grid => + row.RelativeItem(3).Grid(grid => { grid.Columns(3); @@ -784,11 +784,11 @@ namespace QuestPDF.Examples }); }); - stack.Item().Row(row => + column.Item().Row(row => { - row.RelativeColumn().ValueCell().Text("Value 1"); + row.RelativeItem().ValueCell().Text("Value 1"); - row.RelativeColumn(3).Grid(grid => + row.RelativeItem(3).Grid(grid => { grid.Columns(3); @@ -800,10 +800,10 @@ namespace QuestPDF.Examples }); }); - stack.Item().Row(row => + column.Item().Row(row => { - row.RelativeColumn().LabelCell("Label 6"); - row.RelativeColumn().ValueCell().Text("Value 6"); + row.RelativeItem().LabelCell("Label 6"); + row.RelativeItem().ValueCell().Text("Value 6"); }); }); }); diff --git a/QuestPDF.Examples/EnsureSpaceExample.cs b/QuestPDF.Examples/EnsureSpaceExample.cs index 0808774..0425f1b 100644 --- a/QuestPDF.Examples/EnsureSpaceExample.cs +++ b/QuestPDF.Examples/EnsureSpaceExample.cs @@ -25,15 +25,15 @@ namespace QuestPDF.Examples page.Header().Text("With ensure space", TextStyle.Default.SemiBold()); - page.Content().Stack(stack => + page.Content().Column(column => { - stack + column .Item() .ExtendHorizontal() .Height(75) .Background(Colors.Grey.Lighten2); - stack + column .Item() .EnsureSpace(100) .Text(Placeholders.LoremIpsum()); diff --git a/QuestPDF.Examples/FrameExample.cs b/QuestPDF.Examples/FrameExample.cs index 29cfa1d..e8f89b8 100644 --- a/QuestPDF.Examples/FrameExample.cs +++ b/QuestPDF.Examples/FrameExample.cs @@ -36,14 +36,14 @@ namespace QuestPDF.Examples container .Background("#FFF") .Padding(25) - .Stack(stack => + .Column(column => { for(var i = 1; i <= 4; i++) { - stack.Item().Row(row => + column.Item().Row(row => { - row.RelativeColumn(2).LabelCell(Placeholders.Label()); - row.RelativeColumn(3).ValueCell().Text(Placeholders.Paragraph()); + row.RelativeItem(2).LabelCell(Placeholders.Label()); + row.RelativeItem(3).ValueCell().Text(Placeholders.Paragraph()); }); } }); diff --git a/QuestPDF.Examples/ImageExamples.cs b/QuestPDF.Examples/ImageExamples.cs index fd0a599..b3bed24 100644 --- a/QuestPDF.Examples/ImageExamples.cs +++ b/QuestPDF.Examples/ImageExamples.cs @@ -19,17 +19,17 @@ namespace QuestPDF.Examples .ShowResults() .Render(page => { - page.Padding(25).Stack(stack => + page.Padding(25).Column(column => { - stack.Spacing(25); + column.Spacing(25); - stack.Item().Image("logo.png"); + column.Item().Image("logo.png"); var binaryData = File.ReadAllBytes("logo.png"); - stack.Item().Image(binaryData); + column.Item().Image(binaryData); using var stream = new FileStream("logo.png", FileMode.Open); - stack.Item().Image(stream); + column.Item().Image(stream); }); }); } diff --git a/QuestPDF.Examples/InlinedExamples.cs b/QuestPDF.Examples/InlinedExamples.cs index 72c7e1e..fce4c85 100644 --- a/QuestPDF.Examples/InlinedExamples.cs +++ b/QuestPDF.Examples/InlinedExamples.cs @@ -25,7 +25,7 @@ namespace QuestPDF.Examples .Padding(25) .Decoration(decoration => { - decoration.Header().Text(text => + decoration.Before().Text(text => { text.DefaultTextStyle(TextStyle.Default.Size(20)); diff --git a/QuestPDF.Examples/LineExamples.cs b/QuestPDF.Examples/LineExamples.cs new file mode 100644 index 0000000..7a8852a --- /dev/null +++ b/QuestPDF.Examples/LineExamples.cs @@ -0,0 +1,57 @@ +using System.IO; +using NUnit.Framework; +using QuestPDF.Examples.Engine; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Examples +{ + public class LineExamples + { + [Test] + public void LineHorizontal() + { + RenderingTest + .Create() + .PageSize(PageSizes.A5) + .ProduceImages() + .ShowResults() + .Render(container => + { + container + .Padding(15) + .MinimalBox() + .DefaultTextStyle(TextStyle.Default.Size(16)) + .Column(column => + { + column.Item().Text("Above text"); + column.Item().PaddingVertical(5).LineHorizontal(1).LineColor(Colors.Grey.Medium); + column.Item().Text("Below text"); + }); + }); + } + + [Test] + public void LineVertical() + { + RenderingTest + .Create() + .PageSize(PageSizes.A5) + .ProduceImages() + .ShowResults() + .Render(container => + { + container + .Padding(15) + .DefaultTextStyle(TextStyle.Default.Size(16)) + .Row(row => + { + row.AutoItem().Text("Left text"); + row.AutoItem().PaddingHorizontal(10).LineVertical(1).LineColor(Colors.Grey.Medium); + row.AutoItem().Text("Right text"); + }); + }); + } + } +} \ No newline at end of file diff --git a/QuestPDF.Examples/LoremPicsumExample.cs b/QuestPDF.Examples/LoremPicsumExample.cs index 3dca20c..e42417c 100644 --- a/QuestPDF.Examples/LoremPicsumExample.cs +++ b/QuestPDF.Examples/LoremPicsumExample.cs @@ -43,7 +43,7 @@ namespace QuestPDF.Examples container .Background("#FFF") .Padding(25) - .Stack(column => + .Column(column => { column.Spacing(10); diff --git a/QuestPDF.Examples/Padding.cs b/QuestPDF.Examples/Padding.cs index e20b71b..a544765 100644 --- a/QuestPDF.Examples/Padding.cs +++ b/QuestPDF.Examples/Padding.cs @@ -63,7 +63,7 @@ namespace QuestPDF.Examples .Render(container => { container - .Stack(column => + .Column(column => { column .Item() @@ -113,14 +113,14 @@ namespace QuestPDF.Examples .Render(container => { container - .Stack(column => + .Column(column => { column .Item() .Height(150) .Row(row => { - row.RelativeColumn() + row.RelativeItem() .Extend() .Background("FFF") @@ -128,7 +128,7 @@ namespace QuestPDF.Examples .Width(50) .Background("444"); - row.RelativeColumn() + row.RelativeItem() .Extend() .Background("BBB") @@ -142,7 +142,7 @@ namespace QuestPDF.Examples .Height(150) .Row(row => { - row.RelativeColumn() + row.RelativeItem() .Extend() .Background("BBB") @@ -150,7 +150,7 @@ namespace QuestPDF.Examples .Width(50) .Background("444"); - row.RelativeColumn() + row.RelativeItem() .Extend() .Background("BBB") diff --git a/QuestPDF.Examples/RowExamples.cs b/QuestPDF.Examples/RowExamples.cs index e476eb2..e63109c 100644 --- a/QuestPDF.Examples/RowExamples.cs +++ b/QuestPDF.Examples/RowExamples.cs @@ -9,7 +9,7 @@ namespace QuestPDF.Examples public class RowExamples { [Test] - public void ColumnTypes() + public void ItemTypes() { RenderingTest .Create() @@ -22,25 +22,24 @@ namespace QuestPDF.Examples .Padding(25) .MinimalBox() .Border(1) - .Stack(stack => + .Column(column => { - stack.Item().LabelCell("Total width: 600px"); + column.Item().LabelCell("Total width: 600px"); - stack.Item().Row(row => + column.Item().Row(row => { - row.ConstantColumn(150).ValueCell("150px"); - row.ConstantColumn(100).ValueCell("100px"); - row.RelativeColumn(4).ValueCell("200px"); - row.RelativeColumn(3).ValueCell("150px"); + row.ConstantItem(150).ValueCell("150px"); + row.ConstantItem(100).ValueCell("100px"); + row.RelativeItem(4).ValueCell("200px"); + row.RelativeItem(3).ValueCell("150px"); }); - stack.Item().Row(row => + column.Item().Row(row => { - row.ConstantColumn(100).ValueCell("100px"); - row.ConstantColumn(50).ValueCell("50px"); - row.RelativeColumn(3).ValueCell("300px"); - row.RelativeColumn(1).ValueCell("100px"); - row.ConstantColumn(50).ValueCell("50px"); + row.ConstantItem(100).ValueCell("100px"); + row.ConstantItem(50).ValueCell("50px"); + row.RelativeItem(2).ValueCell("100px"); + row.RelativeItem(1).ValueCell("50px"); }); }); }); @@ -63,17 +62,17 @@ namespace QuestPDF.Examples .Padding(25) .Row(row => { - row.RelativeColumn().Stack(stack => + row.RelativeItem().Column(column => { - stack.Item().ShowOnce().Element(CreateBox).Text("X"); - stack.Item().Element(CreateBox).Text("1"); - stack.Item().Element(CreateBox).Text("2"); + column.Item().ShowOnce().Element(CreateBox).Text("X"); + column.Item().Element(CreateBox).Text("1"); + column.Item().Element(CreateBox).Text("2"); }); - row.RelativeColumn().Stack(stack => + row.RelativeItem().Column(column => { - stack.Item().Element(CreateBox).Text("1"); - stack.Item().Element(CreateBox).Text("2"); + column.Item().Element(CreateBox).Text("1"); + column.Item().Element(CreateBox).Text("2"); }); }); }); diff --git a/QuestPDF.Examples/ScaleToFitExamples.cs b/QuestPDF.Examples/ScaleToFitExamples.cs new file mode 100644 index 0000000..b416512 --- /dev/null +++ b/QuestPDF.Examples/ScaleToFitExamples.cs @@ -0,0 +1,42 @@ +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) + .ProduceImages() + .ShowResults() + .Render(container => + { + container.Padding(25).Column(column => + { + var text = Placeholders.Paragraph(); + + foreach (var i in Enumerable.Range(2, 5)) + { + column + .Item() + .MinimalBox() + .Border(1) + .Padding(5) + .Width(i * 40) + .Height(i * 20) + .ScaleToFit() + .Text(text); + } + }); + }); + } + } +} \ No newline at end of file diff --git a/QuestPDF.Examples/ShowOnceExample.cs b/QuestPDF.Examples/ShowOnceExample.cs index 72e4727..8d88fe8 100644 --- a/QuestPDF.Examples/ShowOnceExample.cs +++ b/QuestPDF.Examples/ShowOnceExample.cs @@ -27,14 +27,14 @@ namespace QuestPDF.Examples page.Content().PaddingVertical(5).Row(row => { - row.RelativeColumn() + row.RelativeItem() .Background(Colors.Grey.Lighten2) .Border(1) .Padding(5) .ShowOnce() .Text(Placeholders.Label()); - row.RelativeColumn(2) + row.RelativeItem(2) .Border(1) .Padding(5) .Text(Placeholders.Paragraph()); diff --git a/QuestPDF.Examples/SkipOnceExample.cs b/QuestPDF.Examples/SkipOnceExample.cs index 8e73e9d..634fee5 100644 --- a/QuestPDF.Examples/SkipOnceExample.cs +++ b/QuestPDF.Examples/SkipOnceExample.cs @@ -23,10 +23,10 @@ namespace QuestPDF.Examples page.Size(PageSizes.A7.Landscape()); page.Background(Colors.White); - page.Header().Stack(stack => + page.Header().Column(column => { - stack.Item().ShowOnce().Text("This header is visible on the first page."); - stack.Item().SkipOnce().Text("This header is visible on the second page and all following."); + 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() diff --git a/QuestPDF.Examples/StopPaging.cs b/QuestPDF.Examples/StopPaging.cs new file mode 100644 index 0000000..cae0c98 --- /dev/null +++ b/QuestPDF.Examples/StopPaging.cs @@ -0,0 +1,48 @@ +using NUnit.Framework; +using QuestPDF.Examples.Engine; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Examples +{ + public class StopPaging + { + [Test] + public void Example() + { + RenderingTest + .Create() + .PageSize(300, 250) + .ProduceImages() + .ShowResults() + .Render(container => + { + container + .Padding(25) + .DefaultTextStyle(TextStyle.Default.Size(14)) + .Decoration(decoration => + { + decoration + .Before() + .Text(text => + { + text.DefaultTextStyle(TextStyle.Default.SemiBold().Color(Colors.Blue.Medium)); + + text.Span("Page "); + text.CurrentPageNumber(); + }); + + decoration + .Content() + .Column(column => + { + column.Spacing(25); + column.Item().StopPaging().Text(Placeholders.LoremIpsum()); + column.Item().ExtendHorizontal().Height(75).Background(Colors.Grey.Lighten2); + }); + }); + }); + } + } +} \ No newline at end of file diff --git a/QuestPDF.Examples/TextBenchmark.cs b/QuestPDF.Examples/TextBenchmark.cs index 301df11..47645a3 100644 --- a/QuestPDF.Examples/TextBenchmark.cs +++ b/QuestPDF.Examples/TextBenchmark.cs @@ -117,19 +117,19 @@ namespace QuestPDF.Examples { decoration .Content() - .Stack(stack => + .Column(column => { - stack.Item().Element(Title); - stack.Item().PageBreak(); - stack.Item().Element(TableOfContents); - stack.Item().PageBreak(); + column.Item().Element(Title); + column.Item().PageBreak(); + column.Item().Element(TableOfContents); + column.Item().PageBreak(); - Chapters(stack); + Chapters(column); - stack.Item().Element(Acknowledgements); + column.Item().Element(Acknowledgements); }); - decoration.Footer().Element(Footer); + decoration.After().Element(Footer); }); } @@ -139,61 +139,61 @@ namespace QuestPDF.Examples .Extend() .PaddingBottom(200) .AlignBottom() - .Stack(stack => + .Column(column => { - stack.Item().Text("Quo Vadis", TextStyle.Default.Size(72).Bold().Color(Colors.Blue.Darken2)); - stack.Item().Text("Henryk Sienkiewicz", TextStyle.Default.Size(24).Color(Colors.Grey.Darken2)); + column.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)); }); } void TableOfContents(IContainer container) { - container.Stack(stack => + container.Column(column => { - SectionTitle(stack, "Spis treści"); + SectionTitle(column, "Spis treści"); foreach (var chapter in chapters) { - stack.Item().InternalLink(chapter.Title).Row(row => + column.Item().InternalLink(chapter.Title).Row(row => { - row.RelativeColumn().Text(chapter.Title, normalStyle); - row.ConstantColumn(100).AlignRight().Text(text => text.PageNumberOfLocation(chapter.Title, normalStyle)); + row.RelativeItem().Text(chapter.Title, normalStyle); + row.ConstantItem(100).AlignRight().Text(text => text.PageNumberOfLocation(chapter.Title, normalStyle)); }); } }); } - void Chapters(StackDescriptor stack) + void Chapters(ColumnDescriptor column) { foreach (var chapter in chapters) { - stack.Item().Element(container => Chapter(container, chapter.Title, chapter.Content)); + column.Item().Element(container => Chapter(container, chapter.Title, chapter.Content)); } } void Chapter(IContainer container, string title, string content) { - container.Stack(stack => + container.Column(column => { - SectionTitle(stack, title); + SectionTitle(column, title); - stack.Item().Text(text => + column.Item().Text(text => { text.ParagraphSpacing(5); text.Span(content, normalStyle); }); - stack.Item().PageBreak(); + column.Item().PageBreak(); }); } void Acknowledgements(IContainer container) { - container.Stack(stack => + container.Column(column => { - SectionTitle(stack, "Podziękowania"); + SectionTitle(column, "Podziękowania"); - stack.Item().Text(text => + column.Item().Text(text => { text.DefaultTextStyle(normalStyle); @@ -204,10 +204,10 @@ namespace QuestPDF.Examples }); } - void SectionTitle(StackDescriptor stack, string text) + void SectionTitle(ColumnDescriptor column, string text) { - stack.Item().Location(text).Text(text, subtitleStyle); - stack.Item().PaddingTop(10).PaddingBottom(50).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).ExtendHorizontal(); + column.Item().Location(text).Text(text, subtitleStyle); + column.Item().PaddingTop(10).PaddingBottom(50).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).ExtendHorizontal(); } void Footer(IContainer container) diff --git a/QuestPDF.Examples/TextExamples.cs b/QuestPDF.Examples/TextExamples.cs index dfe8d35..ef12e96 100644 --- a/QuestPDF.Examples/TextExamples.cs +++ b/QuestPDF.Examples/TextExamples.cs @@ -126,7 +126,7 @@ namespace QuestPDF.Examples } [Test] - public void TextStack() + public void Textcolumn() { RenderingTest .Create() diff --git a/QuestPDF.ReportSample/Layouts/DifferentHeadersTemplate.cs b/QuestPDF.ReportSample/Layouts/DifferentHeadersTemplate.cs index 7f54bdb..e67fb58 100644 --- a/QuestPDF.ReportSample/Layouts/DifferentHeadersTemplate.cs +++ b/QuestPDF.ReportSample/Layouts/DifferentHeadersTemplate.cs @@ -26,58 +26,58 @@ namespace QuestPDF.ReportSample.Layouts private void ComposeHeader(IContainer container) { - container.Background(Colors.Grey.Lighten3).Border(1).Stack(stack => + container.Background(Colors.Grey.Lighten3).Border(1).Column(column => { - stack.Item().ShowOnce().Padding(5).AlignMiddle().Row(row => + column.Item().ShowOnce().Padding(5).AlignMiddle().Row(row => { - row.RelativeColumn(2).AlignMiddle().Text("PRIMARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold()); - row.RelativeColumn(1).AlignRight().MinimalBox().AlignMiddle().Background(Colors.Blue.Darken2).Padding(30); + 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); }); - stack.Item().SkipOnce().Padding(5).Row(row => + column.Item().SkipOnce().Padding(5).Row(row => { - row.RelativeColumn(2).Text("SECONDARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold()); - row.RelativeColumn(1).AlignRight().MinimalBox().Background(Colors.Blue.Lighten4).Padding(15); + 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.Stack(stack => + container.Column(column => { - stack.Item().PaddingVertical(80).Text("First"); - stack.Item().PageBreak(); - stack.Item().PaddingVertical(80).Text("Second"); - stack.Item().PageBreak(); - stack.Item().PaddingVertical(80).Text("Third"); - stack.Item().PageBreak(); + 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).Stack(stack => + container.Background(Colors.Grey.Lighten3).Column(column => { - stack.Item().ShowOnce().Background(Colors.Grey.Lighten3).Row(row => + column.Item().ShowOnce().Background(Colors.Grey.Lighten3).Row(row => { - row.RelativeColumn().Text(x => + row.RelativeItem().Text(x => { x.CurrentPageNumber(); x.Span(" / "); x.TotalPages(); }); - row.RelativeColumn().AlignRight().Text("Footer for header"); + row.RelativeItem().AlignRight().Text("Footer for header"); }); - stack.Item().SkipOnce().Background(Colors.Grey.Lighten3).Row(row => + column.Item().SkipOnce().Background(Colors.Grey.Lighten3).Row(row => { - row.RelativeColumn().Text(x => + row.RelativeItem().Text(x => { x.CurrentPageNumber(); x.Span(" / "); x.TotalPages(); }); - row.RelativeColumn().AlignRight().Text("Footer for every page except header"); + row.RelativeItem().AlignRight().Text("Footer for every page except header"); }); }); } diff --git a/QuestPDF.ReportSample/Layouts/PhotoTemplate.cs b/QuestPDF.ReportSample/Layouts/PhotoTemplate.cs index d8fa483..f578e95 100644 --- a/QuestPDF.ReportSample/Layouts/PhotoTemplate.cs +++ b/QuestPDF.ReportSample/Layouts/PhotoTemplate.cs @@ -17,11 +17,11 @@ namespace QuestPDF.ReportSample.Layouts { container .ShowEntire() - .Stack(stack => + .Column(column => { - stack.Spacing(5); - stack.Item().Element(PhotoWithMaps); - stack.Item().Element(PhotoDetails); + column.Spacing(5); + column.Item().Element(PhotoWithMaps); + column.Item().Element(PhotoDetails); }); } @@ -30,14 +30,14 @@ namespace QuestPDF.ReportSample.Layouts container .Row(row => { - row.RelativeColumn(2).AspectRatio(4 / 3f).Component(); + row.RelativeItem(2).AspectRatio(4 / 3f).Component(); - row.RelativeColumn().PaddingLeft(5).Stack(stack => + row.RelativeItem().PaddingLeft(5).Column(column => { - stack.Spacing(7f); + column.Spacing(7f); - stack.Item().AspectRatio(4 / 3f).Component(); - stack.Item().AspectRatio(4 / 3f).Component(); + column.Item().AspectRatio(4 / 3f).Component(); + column.Item().AspectRatio(4 / 3f).Component(); }); }); } diff --git a/QuestPDF.ReportSample/Layouts/SectionTemplate.cs b/QuestPDF.ReportSample/Layouts/SectionTemplate.cs index fa1eb5e..2e37ec3 100644 --- a/QuestPDF.ReportSample/Layouts/SectionTemplate.cs +++ b/QuestPDF.ReportSample/Layouts/SectionTemplate.cs @@ -21,25 +21,25 @@ namespace QuestPDF.ReportSample.Layouts .Decoration(decoration => { decoration - .Header() + .Before() .PaddingBottom(5) .Text(Model.Title, Typography.Headline); - decoration.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Stack(stack => + decoration.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Column(column => { foreach (var part in Model.Parts) { - stack.Item().EnsureSpace(25).Row(row => + column.Item().EnsureSpace(25).Row(row => { - row.ConstantColumn(150).LabelCell().Text(part.Label); - var frame = row.RelativeColumn().ValueCell(); + row.ConstantItem(150).LabelCell().Text(part.Label); + var frame = row.RelativeItem().ValueCell(); if (part is ReportSectionText text) frame.ShowEntire().Text(text.Text); - + if (part is ReportSectionMap map) frame.Element(x => MapElement(x, map)); - + if (part is ReportSectionPhotos photos) frame.Element(x => PhotosElement(x, photos)); }); @@ -56,12 +56,12 @@ namespace QuestPDF.ReportSample.Layouts return; } - container.ShowEntire().Stack(stack => + container.ShowEntire().Column(column => { - stack.Spacing(5); + column.Spacing(5); - stack.Item().MaxWidth(250).AspectRatio(4 / 3f).Component(); - stack.Item().Text(model.Location.Format()); + column.Item().MaxWidth(250).AspectRatio(4 / 3f).Component(); + column.Item().Text(model.Location.Format()); }); } diff --git a/QuestPDF.ReportSample/Layouts/StandardReport.cs b/QuestPDF.ReportSample/Layouts/StandardReport.cs index 25fac2a..2bba69a 100644 --- a/QuestPDF.ReportSample/Layouts/StandardReport.cs +++ b/QuestPDF.ReportSample/Layouts/StandardReport.cs @@ -48,19 +48,19 @@ namespace QuestPDF.ReportSample.Layouts private void ComposeHeader(IContainer container) { - container.Stack(stack => + container.Column(column => { - stack.Item().Row(row => + column.Item().Row(row => { row.Spacing(50); - row.RelativeColumn().PaddingTop(-10).Text(Model.Title, Typography.Title); - row.ConstantColumn(90).ExternalLink("https://www.questpdf.com").MaxHeight(30).Component(); + row.RelativeItem().PaddingTop(-10).Text(Model.Title, Typography.Title); + row.ConstantItem(90).ExternalLink("https://www.questpdf.com").MaxHeight(30).Component(); }); - stack.Item().ShowOnce().PaddingVertical(15).Border(1f).BorderColor(Colors.Grey.Lighten1).ExtendHorizontal(); + column.Item().ShowOnce().PaddingVertical(15).Border(1f).BorderColor(Colors.Grey.Lighten1).ExtendHorizontal(); - stack.Item().ShowOnce().Grid(grid => + column.Item().ShowOnce().Grid(grid => { grid.Columns(2); grid.Spacing(5); @@ -79,22 +79,22 @@ namespace QuestPDF.ReportSample.Layouts void ComposeContent(IContainer container) { - container.PaddingVertical(20).Stack(stack => + container.PaddingVertical(20).Column(column => { - stack.Spacing(20); - - stack.Item().Component(new TableOfContentsTemplate(Model.Sections)); + column.Spacing(20); - stack.Item().PageBreak(); + column.Item().Component(new TableOfContentsTemplate(Model.Sections)); + + column.Item().PageBreak(); foreach (var section in Model.Sections) - stack.Item().Location(section.Title).Component(new SectionTemplate(section)); - - stack.Item().PageBreak(); - stack.Item().Location("Photos"); + column.Item().Location(section.Title).Component(new SectionTemplate(section)); + + column.Item().PageBreak(); + column.Item().Location("Photos"); foreach (var photo in Model.Photos) - stack.Item().Component(new PhotoTemplate(photo)); + column.Item().Component(new PhotoTemplate(photo)); }); } } diff --git a/QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs b/QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs index 64ff8f2..3f93f93 100644 --- a/QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs +++ b/QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs @@ -19,18 +19,18 @@ namespace QuestPDF.ReportSample.Layouts .Decoration(decoration => { decoration - .Header() + .Before() .PaddingBottom(5) .Text("Table of contents", Typography.Headline); - decoration.Content().Stack(stack => + decoration.Content().Column(column => { - stack.Spacing(5); + column.Spacing(5); for (var i = 0; i < Sections.Count; i++) - stack.Item().Element(c => DrawLink(c, i+1, Sections[i].Title)); + column.Item().Element(c => DrawLink(c, i+1, Sections[i].Title)); - stack.Item().Element(c => DrawLink(c, Sections.Count+1, "Photos")); + column.Item().Element(c => DrawLink(c, Sections.Count+1, "Photos")); }); }); } @@ -41,9 +41,9 @@ namespace QuestPDF.ReportSample.Layouts .InternalLink(locationName) .Row(row => { - row.ConstantColumn(25).Text($"{number}."); - row.RelativeColumn().Text(locationName); - row.ConstantColumn(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName)); + row.ConstantItem(25).Text($"{number}."); + row.RelativeItem().Text(locationName); + row.ConstantItem(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName)); }); } } diff --git a/QuestPDF.ReportSample/Tests.cs b/QuestPDF.ReportSample/Tests.cs index 96b2f7f..111df78 100644 --- a/QuestPDF.ReportSample/Tests.cs +++ b/QuestPDF.ReportSample/Tests.cs @@ -24,7 +24,7 @@ namespace QuestPDF.ReportSample [Test] public void GenerateAndShowPdf() { - ImagePlaceholder.Solid = true; + //ImagePlaceholder.Solid = true; var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.pdf"); Report.GeneratePdf(path); diff --git a/QuestPDF.UnitTests/StackTests.cs b/QuestPDF.UnitTests/ColumnTests.cs similarity index 52% rename from QuestPDF.UnitTests/StackTests.cs rename to QuestPDF.UnitTests/ColumnTests.cs index 58218bb..5ea4af3 100644 --- a/QuestPDF.UnitTests/StackTests.cs +++ b/QuestPDF.UnitTests/ColumnTests.cs @@ -1,4 +1,5 @@ -using NUnit.Framework; +using System.Linq; +using NUnit.Framework; using QuestPDF.Drawing; using QuestPDF.Elements; using QuestPDF.Fluent; @@ -8,19 +9,40 @@ using QuestPDF.UnitTests.TestEngine; namespace QuestPDF.UnitTests { [TestFixture] - public class StackTests + public class ColumnTests { + 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 [Test] public void Measure_ReturnsWrap_WhenFirstChildWraps() { TestPlan - .For(x => new BinaryStack - { - First = x.CreateChild("first"), - Second = x.CreateChild("second") - }) + .For(CreateColumnWithTwoItems) .MeasureElement(new Size(400, 300)) .ExpectChildMeasure("first", new Size(400, 300), SpacePlan.Wrap()) .CheckMeasureResult(SpacePlan.Wrap()); @@ -30,11 +52,7 @@ namespace QuestPDF.UnitTests public void Measure_ReturnsPartialRender_WhenFirstChildReturnsPartialRender() { TestPlan - .For(x => new BinaryStack - { - First = x.CreateChild("first"), - Second = x.CreateChild("second") - }) + .For(CreateColumnWithTwoItems) .MeasureElement(new Size(400, 300)) .ExpectChildMeasure("first", new Size(400, 300), SpacePlan.PartialRender(300, 200)) .CheckMeasureResult(SpacePlan.PartialRender(300, 200)); @@ -44,11 +62,7 @@ namespace QuestPDF.UnitTests public void Measure_ReturnsPartialRender_WhenSecondChildWraps() { TestPlan - .For(x => new BinaryStack - { - First = x.CreateChild("first"), - Second = x.CreateChild("second") - }) + .For(CreateColumnWithTwoItems) .MeasureElement(new Size(400, 300)) .ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100)) .ExpectChildMeasure("second", new Size(400, 200), SpacePlan.Wrap()) @@ -59,11 +73,7 @@ namespace QuestPDF.UnitTests public void Measure_ReturnsPartialRender_WhenSecondChildReturnsPartialRender() { TestPlan - .For(x => new BinaryStack - { - First = x.CreateChild("first"), - Second = x.CreateChild("second") - }) + .For(CreateColumnWithTwoItems) .MeasureElement(new Size(400, 300)) .ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100)) .ExpectChildMeasure("second", new Size(400, 200), SpacePlan.PartialRender(300, 150)) @@ -74,33 +84,13 @@ namespace QuestPDF.UnitTests public void Measure_ReturnsFullRender_WhenSecondChildReturnsFullRender() { TestPlan - .For(x => new BinaryStack - { - First = x.CreateChild("first"), - Second = x.CreateChild("second") - }) + .For(CreateColumnWithTwoItems) .MeasureElement(new Size(400, 300)) .ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100)) .ExpectChildMeasure("second", new Size(400, 200), SpacePlan.FullRender(100, 50)) .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 #region Draw @@ -109,11 +99,7 @@ namespace QuestPDF.UnitTests public void Draw_WhenFirstChildWraps() { TestPlan - .For(x => new BinaryStack - { - First = x.CreateChild("first"), - Second = x.CreateChild("second") - }) + .For(CreateColumnWithTwoItems) .DrawElement(new Size(400, 300)) .ExpectChildMeasure("first", new Size(400, 300), SpacePlan.Wrap()) .CheckDrawResult(); @@ -123,14 +109,12 @@ namespace QuestPDF.UnitTests public void Draw_WhenFirstChildPartiallyRenders() { TestPlan - .For(x => new BinaryStack - { - First = x.CreateChild("first"), - Second = x.CreateChild("second") - }) + .For(CreateColumnWithTwoItems) .DrawElement(new Size(400, 300)) .ExpectChildMeasure("first", new Size(400, 300), SpacePlan.PartialRender(200, 100)) + .ExpectCanvasTranslate(0, 0) .ExpectChildDraw("first", new Size(400, 100)) + .ExpectCanvasTranslate(0, 0) .CheckDrawResult(); } @@ -138,15 +122,13 @@ namespace QuestPDF.UnitTests public void Draw_WhenFirstChildFullyRenders_AndSecondChildWraps() { TestPlan - .For(x => new BinaryStack - { - First = x.CreateChild("first"), - Second = x.CreateChild("second") - }) + .For(CreateColumnWithTwoItems) .DrawElement(new Size(400, 300)) .ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100)) - .ExpectChildDraw("first", new Size(400, 100)) .ExpectChildMeasure("second", new Size(400, 200), SpacePlan.Wrap()) + .ExpectCanvasTranslate(0, 0) + .ExpectChildDraw("first", new Size(400, 100)) + .ExpectCanvasTranslate(0, 0) .CheckDrawResult(); } @@ -154,15 +136,13 @@ namespace QuestPDF.UnitTests public void Draw_WhenFirstChildFullyRenders_AndSecondChildPartiallyRenders() { TestPlan - .For(x => new BinaryStack - { - First = x.CreateChild("first"), - Second = x.CreateChild("second") - }) + .For(CreateColumnWithTwoItems) .DrawElement(new Size(400, 300)) .ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100)) - .ExpectChildDraw("first", new Size(400, 100)) .ExpectChildMeasure("second", new Size(400, 200), SpacePlan.PartialRender(250, 150)) + .ExpectCanvasTranslate(0, 0) + .ExpectChildDraw("first", new Size(400, 100)) + .ExpectCanvasTranslate(0, 0) .ExpectCanvasTranslate(0, 100) .ExpectChildDraw("second", new Size(400, 150)) .ExpectCanvasTranslate(0, -100) @@ -173,15 +153,13 @@ namespace QuestPDF.UnitTests public void Draw_WhenFirstChildFullyRenders_AndSecondChildFullyRenders() { TestPlan - .For(x => new BinaryStack - { - First = x.CreateChild("first"), - Second = x.CreateChild("second") - }) + .For(CreateColumnWithTwoItems) .DrawElement(new Size(400, 300)) .ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100)) - .ExpectChildDraw("first", new Size(400, 100)) .ExpectChildMeasure("second", new Size(400, 200), SpacePlan.FullRender(250, 150)) + .ExpectCanvasTranslate(0, 0) + .ExpectChildDraw("first", new Size(400, 100)) + .ExpectCanvasTranslate(0, 0) .ExpectCanvasTranslate(0, 100) .ExpectChildDraw("second", new Size(400, 150)) .ExpectCanvasTranslate(0, -100) @@ -192,19 +170,13 @@ namespace QuestPDF.UnitTests public void Draw_UsesEmpty_WhenFirstChildIsRendered() { TestPlan - .For(x => new BinaryStack - { - First = x.CreateChild("first"), - Second = x.CreateChild("second"), - - IsFirstRendered = true - }) + .For(CreateColumnWithTwoItemsWhereFirstIsFullyRendered) .DrawElement(new Size(400, 300)) .ExpectChildMeasure("second", new Size(400, 300), SpacePlan.PartialRender(200, 300)) .ExpectCanvasTranslate(0, 0) .ExpectChildDraw("second", new Size(400, 300)) .ExpectCanvasTranslate(0, 0) - .CheckState(x => x.IsFirstRendered) + .CheckState(x => x.Items.First().IsRendered) .CheckDrawResult(); } @@ -212,97 +184,14 @@ namespace QuestPDF.UnitTests public void Draw_TogglesFirstRenderedFlag_WhenSecondFullyRenders() { TestPlan - .For(x => new BinaryStack - { - First = x.CreateChild("first"), - Second = x.CreateChild("second"), - - IsFirstRendered = true - }) + .For(CreateColumnWithTwoItemsWhereFirstIsFullyRendered) .DrawElement(new Size(400, 300)) .ExpectChildMeasure("second", new Size(400, 300), SpacePlan.FullRender(200, 300)) .ExpectCanvasTranslate(0, 0) .ExpectChildDraw("second", new Size(400, 300)) .ExpectCanvasTranslate(0, 0) .CheckDrawResult() - .CheckState(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); + .CheckState(x => !x.Items.First().IsRendered); } #endregion diff --git a/QuestPDF.UnitTests/DecorationTests.cs b/QuestPDF.UnitTests/DecorationTests.cs index b1ea8b4..6f481fc 100644 --- a/QuestPDF.UnitTests/DecorationTests.cs +++ b/QuestPDF.UnitTests/DecorationTests.cs @@ -9,35 +9,27 @@ namespace QuestPDF.UnitTests [TestFixture] 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 [Test] - public void Measure_ReturnsWrap_WhenDecorationReturnsWrap() + public void Measure_ReturnsWrap_WhenBeforeReturnsWrap() { TestPlan - .For(x => new BinaryDecoration - { - Type = DecorationType.Append, - DecorationElement = x.CreateChild("decoration"), - ContentElement = x.CreateChild("content") - }) + .For(CreateDecoration) .MeasureElement(new Size(400, 300)) - .ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.Wrap()) - .CheckMeasureResult(SpacePlan.Wrap()); - } - - [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)) + .ExpectChildMeasure("before", new Size(400, 300), SpacePlan.Wrap()) + .ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50)) + .ExpectChildMeasure("content", new Size(400, 250), SpacePlan.FullRender(100, 100)) .CheckMeasureResult(SpacePlan.Wrap()); } @@ -45,89 +37,100 @@ namespace QuestPDF.UnitTests public void Measure_ReturnsWrap_WhenContentReturnsWrap() { TestPlan - .For(x => new BinaryDecoration - { - Type = DecorationType.Append, - DecorationElement = x.CreateChild("decoration"), - ContentElement = x.CreateChild("content") - }) + .For(CreateDecoration) .MeasureElement(new Size(400, 300)) - .ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100)) + .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.Wrap()) .CheckMeasureResult(SpacePlan.Wrap()); } [Test] - public void Measure_ReturnsPartialRender_WhenContentReturnsPartialRender() + public void Measure_ReturnsWrap_WhenAfterReturnsWrap() { TestPlan - .For(x => new BinaryDecoration - { - Type = DecorationType.Append, - DecorationElement = x.CreateChild("decoration"), - ContentElement = x.CreateChild("content") - }) + .For(CreateDecoration) .MeasureElement(new Size(400, 300)) - .ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100)) - .ExpectChildMeasure("content", new Size(400, 200), SpacePlan.PartialRender(200, 150)) - .CheckMeasureResult(SpacePlan.PartialRender(400, 250)); + .ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(100, 50)) + .ExpectChildMeasure("after", new Size(400, 300), SpacePlan.Wrap()) + .ExpectChildMeasure("content", new Size(400, 250), SpacePlan.FullRender(100, 100)) + .CheckMeasureResult(SpacePlan.Wrap()); } [Test] - public void Measure_ReturnsFullRender_WhenContentReturnsFullRender() + public void Measure_ReturnsWrap_WhenBeforeReturnsPartialRender() { TestPlan - .For(x => new BinaryDecoration - { - Type = DecorationType.Append, - DecorationElement = x.CreateChild("decoration"), - ContentElement = x.CreateChild("content") - }) + .For(CreateDecoration) .MeasureElement(new Size(400, 300)) - .ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100)) - .ExpectChildMeasure("content", new Size(400, 200), SpacePlan.FullRender(200, 150)) - .CheckMeasureResult(SpacePlan.FullRender(400, 250)); + .ExpectChildMeasure("before", new Size(400, 300), SpacePlan.PartialRender(100, 50)) + .ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50)) + .ExpectChildMeasure("content", new Size(400, 250), SpacePlan.FullRender(100, 100)) + .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 #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] public void Draw_Append() { TestPlan - .For(x => new BinaryDecoration - { - Type = DecorationType.Append, - DecorationElement = x.CreateChild("decoration"), - ContentElement = x.CreateChild("content") - }) + .For(CreateDecoration) .DrawElement(new Size(400, 300)) - .ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100)) - .ExpectChildDraw("content", new Size(400, 200)) - .ExpectCanvasTranslate(0, 200) - .ExpectChildDraw("decoration", new Size(400, 100)) - .ExpectCanvasTranslate(0, -200) + .ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(200, 40)) + .ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(200, 60)) + .ExpectChildMeasure("content", new Size(400, 200), SpacePlan.FullRender(300, 100)) + + .ExpectCanvasTranslate(0, 0) + .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(); } diff --git a/QuestPDF.UnitTests/GridTests.cs b/QuestPDF.UnitTests/GridTests.cs index e55d08e..54149a1 100644 --- a/QuestPDF.UnitTests/GridTests.cs +++ b/QuestPDF.UnitTests/GridTests.cs @@ -39,26 +39,26 @@ namespace QuestPDF.UnitTests // assert var expected = new Container(); - expected.Stack(stack => + expected.Column(column => { - stack.Item().Row(row => + column.Item().Row(row => { - row.RelativeColumn(6).Element(childA); - row.RelativeColumn(4).Element(childB); - row.RelativeColumn(2); + row.RelativeItem(6).Element(childA); + row.RelativeItem(4).Element(childB); + row.RelativeItem(2); }); - stack.Item().Row(row => + column.Item().Row(row => { - row.RelativeColumn(4).Element(childC); - row.RelativeColumn(2).Element(childD); - row.RelativeColumn(6); + row.RelativeItem(4).Element(childC); + row.RelativeItem(2).Element(childD); + row.RelativeItem(6); }); - stack.Item().Row(row => + column.Item().Row(row => { - row.RelativeColumn(8).Element(childE); - row.RelativeColumn(4); + row.RelativeItem(8).Element(childE); + row.RelativeItem(4); }); }); @@ -93,29 +93,29 @@ namespace QuestPDF.UnitTests // assert var expected = new Container(); - expected.Stack(stack => + expected.Column(column => { - stack.Item().Row(row => + column.Item().Row(row => { - row.RelativeColumn(1); - row.RelativeColumn(6).Element(childA); - row.RelativeColumn(4).Element(childB); - row.RelativeColumn(1); + row.RelativeItem(1); + row.RelativeItem(6).Element(childA); + row.RelativeItem(4).Element(childB); + row.RelativeItem(1); }); - stack.Item().Row(row => + column.Item().Row(row => { - row.RelativeColumn(3); - row.RelativeColumn(4).Element(childC); - row.RelativeColumn(2).Element(childD); - row.RelativeColumn(3); + row.RelativeItem(3); + row.RelativeItem(4).Element(childC); + row.RelativeItem(2).Element(childD); + row.RelativeItem(3); }); - stack.Item().Row(row => + column.Item().Row(row => { - row.RelativeColumn(2); - row.RelativeColumn(8).Element(childE); - row.RelativeColumn(2); + row.RelativeItem(2); + row.RelativeItem(8).Element(childE); + row.RelativeItem(2); }); }); @@ -150,26 +150,26 @@ namespace QuestPDF.UnitTests // assert var expected = new Container(); - expected.Stack(stack => + expected.Column(column => { - stack.Item().Row(row => + column.Item().Row(row => { - row.RelativeColumn(2); - row.RelativeColumn(6).Element(childA); - row.RelativeColumn(4).Element(childB); + row.RelativeItem(2); + row.RelativeItem(6).Element(childA); + row.RelativeItem(4).Element(childB); }); - stack.Item().Row(row => + column.Item().Row(row => { - row.RelativeColumn(6); - row.RelativeColumn(4).Element(childC); - row.RelativeColumn(2).Element(childD); + row.RelativeItem(6); + row.RelativeItem(4).Element(childC); + row.RelativeItem(2).Element(childD); }); - stack.Item().Row(row => + column.Item().Row(row => { - row.RelativeColumn(4); - row.RelativeColumn(8).Element(childE); + row.RelativeItem(4); + row.RelativeItem(8).Element(childE); }); }); @@ -210,36 +210,36 @@ namespace QuestPDF.UnitTests // assert var expected = new Container(); - expected.Stack(stack => + expected.Column(column => { - stack.Spacing(20); + column.Spacing(20); - stack.Item().Row(row => + column.Item().Row(row => { row.Spacing(30); - row.RelativeColumn(3); - row.RelativeColumn(5).Element(childA); - row.RelativeColumn(5).Element(childB); - row.RelativeColumn(3); + row.RelativeItem(3); + row.RelativeItem(5).Element(childA); + row.RelativeItem(5).Element(childB); + row.RelativeItem(3); }); - stack.Item().Row(row => + column.Item().Row(row => { row.Spacing(30); - row.RelativeColumn(3); - row.RelativeColumn(10).Element(childC); - row.RelativeColumn(3); + row.RelativeItem(3); + row.RelativeItem(10).Element(childC); + row.RelativeItem(3); }); - stack.Item().Row(row => + column.Item().Row(row => { row.Spacing(30); - row.RelativeColumn(2); - row.RelativeColumn(12).Element(childD); - row.RelativeColumn(2); + row.RelativeItem(2); + row.RelativeItem(12).Element(childD); + row.RelativeItem(2); }); }); diff --git a/QuestPDF.UnitTests/RowTests.cs b/QuestPDF.UnitTests/RowTests.cs deleted file mode 100644 index de74a76..0000000 --- a/QuestPDF.UnitTests/RowTests.cs +++ /dev/null @@ -1,268 +0,0 @@ -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)) - .ExpectChildMeasure("right", new Size(150, 300), SpacePlan.FullRender(150, 200)) - .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 - } -} \ No newline at end of file diff --git a/QuestPDF/Drawing/DocumentContainer.cs b/QuestPDF/Drawing/DocumentContainer.cs index e813839..18a8cc9 100644 --- a/QuestPDF/Drawing/DocumentContainer.cs +++ b/QuestPDF/Drawing/DocumentContainer.cs @@ -16,13 +16,13 @@ namespace QuestPDF.Drawing var container = new Container(); container - .Stack(stack => + .Column(column => { Pages .SelectMany(x => new List() { - () => stack.Item().PageBreak(), - () => stack.Item().Component(x) + () => column.Item().PageBreak(), + () => column.Item().Component(x) }) .Skip(1) .ToList() diff --git a/QuestPDF/Drawing/DocumentGenerator.cs b/QuestPDF/Drawing/DocumentGenerator.cs index f4d455c..e253c94 100644 --- a/QuestPDF/Drawing/DocumentGenerator.cs +++ b/QuestPDF/Drawing/DocumentGenerator.cs @@ -18,14 +18,16 @@ namespace QuestPDF.Drawing internal static void GeneratePdf(Stream stream, IDocument document) { var metadata = document.GetMetadata(); - var canvas = new PdfCanvas(stream, metadata); + var writeOnlyStream = new WriteOnlyStream(stream); + var canvas = new PdfCanvas(writeOnlyStream, metadata); RenderDocument(canvas, document); } internal static void GenerateXps(Stream stream, IDocument document) { var metadata = document.GetMetadata(); - var canvas = new XpsCanvas(stream, metadata); + var writeOnlyStream = new WriteOnlyStream(stream); + var canvas = new XpsCanvas(writeOnlyStream, metadata); RenderDocument(canvas, document); } diff --git a/QuestPDF/Drawing/PdfCanvas.cs b/QuestPDF/Drawing/PdfCanvas.cs index fd7b1df..ac703eb 100644 --- a/QuestPDF/Drawing/PdfCanvas.cs +++ b/QuestPDF/Drawing/PdfCanvas.cs @@ -1,5 +1,5 @@ using System.IO; -using QuestPDF.Infrastructure; +using QuestPDF.Helpers; using SkiaSharp; namespace QuestPDF.Drawing diff --git a/QuestPDF/Drawing/XpsCanvas.cs b/QuestPDF/Drawing/XpsCanvas.cs index c885ef8..2c3f65e 100644 --- a/QuestPDF/Drawing/XpsCanvas.cs +++ b/QuestPDF/Drawing/XpsCanvas.cs @@ -1,5 +1,5 @@ using System.IO; -using QuestPDF.Infrastructure; +using QuestPDF.Helpers; using SkiaSharp; namespace QuestPDF.Drawing diff --git a/QuestPDF/Elements/Column.cs b/QuestPDF/Elements/Column.cs new file mode 100644 index 0000000..da42e96 --- /dev/null +++ b/QuestPDF/Elements/Column.cs @@ -0,0 +1,120 @@ +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 Items { get; } = new(); + internal float Spacing { get; set; } + + public void ResetState() + { + Items.ForEach(x => x.IsRendered = false); + } + + internal override IEnumerable GetChildren() + { + return Items; + } + + internal override void CreateProxy(Func 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 PlanLayout(Size availableSpace) + { + var topOffset = 0f; + var commands = new List(); + + 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; + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Decoration.cs b/QuestPDF/Elements/Decoration.cs index 39959f4..61d72e6 100644 --- a/QuestPDF/Elements/Decoration.cs +++ b/QuestPDF/Elements/Decoration.cs @@ -1,98 +1,112 @@ using System; using System.Collections.Generic; +using System.Linq; using QuestPDF.Drawing; using QuestPDF.Fluent; using QuestPDF.Infrastructure; namespace QuestPDF.Elements { - internal enum DecorationType + internal class DecorationItemRenderingCommand { - Prepend, - Append + public Element Element { get; set; } + public SpacePlan Measurement { get; set; } + public Position Offset { get; set; } } - internal class BinaryDecoration : Element, ICacheable + internal class Decoration : Element, ICacheable { - public Element DecorationElement { get; set; } = Empty.Instance; - public Element ContentElement { get; set; } = Empty.Instance; - public DecorationType Type { get; set; } + internal Element Before { get; set; } = new Empty(); + internal Element Content { get; set; } = new Empty(); + internal Element After { get; set; } = new Empty(); internal override IEnumerable GetChildren() { - yield return DecorationElement; - yield return ContentElement; + yield return Before; + yield return Content; + yield return After; } - - internal override void CreateProxy(Func create) + + internal override void CreateProxy(Func create) { - DecorationElement = create(DecorationElement); - ContentElement = create(ContentElement); + Before = create(Before); + Content = create(Content); + After = create(After); } internal override SpacePlan Measure(Size availableSpace) { - var decorationMeasure = DecorationElement.Measure(availableSpace); - - if (decorationMeasure.Type == SpacePlanType.Wrap || decorationMeasure.Type == SpacePlanType.PartialRender) + var renderingCommands = PlanLayout(availableSpace).ToList(); + + if (renderingCommands.Any(x => x.Measurement.Type == SpacePlanType.Wrap)) return SpacePlan.Wrap(); - var decorationSize = decorationMeasure; - var contentMeasure = ContentElement.Measure(new Size(availableSpace.Width, availableSpace.Height - decorationSize.Height)); + var width = renderingCommands.Max(x => x.Measurement.Width); + var height = renderingCommands.Sum(x => x.Measurement.Height); + var size = new Size(width, height); - if (contentMeasure.Type == SpacePlanType.Wrap) + if (width > availableSpace.Width + Size.Epsilon || height > availableSpace.Height + Size.Epsilon) 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); - - if (contentSize.Type == SpacePlanType.PartialRender) - return SpacePlan.PartialRender(resultSize); - - if (contentSize.Type == SpacePlanType.FullRender) - return SpacePlan.FullRender(resultSize); - - throw new NotSupportedException(); + return willBeFullyRendered + ? SpacePlan.FullRender(size) + : SpacePlan.PartialRender(size); } internal override void Draw(Size availableSpace) { - var decorationSize = DecorationElement.Measure(availableSpace); - var contentSize = new Size(availableSpace.Width, availableSpace.Height - decorationSize.Height); - - var translateHeight = Type == DecorationType.Prepend ? decorationSize.Height : contentSize.Height; - Action drawDecoration = () => DecorationElement?.Draw(new Size(availableSpace.Width, decorationSize.Height)); - Action drawContent = () => ContentElement?.Draw(new Size (availableSpace.Width, contentSize.Height)); - - var first = Type == DecorationType.Prepend ? drawDecoration : drawContent; - var second = Type == DecorationType.Prepend ? drawContent : drawDecoration; - - first(); - Canvas.Translate(new Position(0, translateHeight)); - second(); - Canvas.Translate(new Position(0, -translateHeight)); - } - } - - 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) - { - container.Element(new BinaryDecoration + var renderingCommands = PlanLayout(availableSpace).ToList(); + var width = renderingCommands.Max(x => x.Measurement.Width); + + foreach (var command in renderingCommands) { - Type = DecorationType.Prepend, - DecorationElement = Header, - ContentElement = new BinaryDecoration - { - Type = DecorationType.Append, - ContentElement = Content, - DecorationElement = Footer - } - }); + var elementSize = new Size(width, command.Measurement.Height); + + Canvas.Translate(command.Offset); + command.Element.Draw(elementSize); + Canvas.Translate(command.Offset.Reverse()); + } + } + + private IEnumerable PlanLayout(Size availableSpace) + { + SpacePlan GetDecorationMeasurement(Element element) + { + var measurement = element.Measure(availableSpace); + + return measurement.Type == SpacePlanType.FullRender + ? measurement + : SpacePlan.Wrap(); + } + + 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) + }; } } } \ No newline at end of file diff --git a/QuestPDF/Elements/Grid.cs b/QuestPDF/Elements/Grid.cs index 7014bf6..a1d80e1 100644 --- a/QuestPDF/Elements/Grid.cs +++ b/QuestPDF/Elements/Grid.cs @@ -27,12 +27,12 @@ namespace QuestPDF.Elements { ChildrenQueue = new Queue(Children); - container.Stack(stack => + container.Column(column => { - stack.Spacing(VerticalSpacing); + column.Spacing(VerticalSpacing); while (ChildrenQueue.Any()) - stack.Item().Row(BuildRow); + column.Item().Row(BuildRow); }); } @@ -65,12 +65,12 @@ namespace QuestPDF.Elements emptySpace /= 2; if (hasEmptySpace && Alignment != HorizontalAlignment.Left) - row.RelativeColumn(emptySpace); + row.RelativeItem(emptySpace); - elements.ForEach(x => row.RelativeColumn(x.Columns).Element(x.Child)); + elements.ForEach(x => row.RelativeItem(x.Columns).Element(x.Child)); if (hasEmptySpace && Alignment != HorizontalAlignment.Right) - row.RelativeColumn(emptySpace); + row.RelativeItem(emptySpace); } } } \ No newline at end of file diff --git a/QuestPDF/Elements/Inlined.cs b/QuestPDF/Elements/Inlined.cs index 9044e6e..8e2aa53 100644 --- a/QuestPDF/Elements/Inlined.cs +++ b/QuestPDF/Elements/Inlined.cs @@ -8,15 +8,7 @@ namespace QuestPDF.Elements { internal class InlinedElement : Container { - public SpacePlan? MeasureCache { get; set; } - internal override SpacePlan Measure(Size availableSpace) - { - // TODO: once element caching proxy is introduces, this can be removed - - MeasureCache ??= Child.Measure(Size.Max); - return MeasureCache.Value; - } } internal enum InlinedAlignment @@ -112,8 +104,11 @@ namespace QuestPDF.Elements foreach (var element in elements) { - var size = element.Measure(Size.Max); + 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); @@ -132,48 +127,39 @@ namespace QuestPDF.Elements if (elements.Count == 1) return 0; - if (ElementsAlignment == InlinedAlignment.Justify) - return difference / (elements.Count - 1); - - if (ElementsAlignment == InlinedAlignment.SpaceAround) - return difference / (elements.Count + 1); - - return HorizontalSpacing; + return ElementsAlignment switch + { + InlinedAlignment.Justify => difference / (elements.Count - 1), + InlinedAlignment.SpaceAround => difference / (elements.Count + 1), + _ => HorizontalSpacing + }; } float AlignOffset() { - if (ElementsAlignment == InlinedAlignment.Left) - return 0; - - if (ElementsAlignment == InlinedAlignment.Justify) - return 0; - - if (ElementsAlignment == InlinedAlignment.SpaceAround) - return elementOffset; - var difference = availableSpace.Width - lineSize.Width - (elements.Count - 1) * HorizontalSpacing; - - if (ElementsAlignment == InlinedAlignment.Center) - return difference / 2; - if (ElementsAlignment == InlinedAlignment.Right) - return difference; - - return 0; + 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) { - if (BaselineAlignment == VerticalAlignment.Top) - return 0; - var difference = lineHeight - elementSize.Height; - - if (BaselineAlignment == VerticalAlignment.Middle) - return difference / 2; - return difference; + return BaselineAlignment switch + { + VerticalAlignment.Top => 0, + VerticalAlignment.Middle => difference / 2, + _ => difference + }; } } } @@ -250,11 +236,12 @@ namespace QuestPDF.Elements float GetInitialAlignmentOffset() { // this method makes sure that the spacing between elements is no lesser than configured - - if (ElementsAlignment == InlinedAlignment.SpaceAround) - return HorizontalSpacing * 2; - return 0; + return ElementsAlignment switch + { + InlinedAlignment.SpaceAround => HorizontalSpacing * 2, + _ => 0 + }; } } } diff --git a/QuestPDF/Elements/Line.cs b/QuestPDF/Elements/Line.cs new file mode 100644 index 0000000..03def38 --- /dev/null +++ b/QuestPDF/Elements/Line.cs @@ -0,0 +1,46 @@ +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); + } + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Page.cs b/QuestPDF/Elements/Page.cs index 0368409..7d06bbe 100644 --- a/QuestPDF/Elements/Page.cs +++ b/QuestPDF/Elements/Page.cs @@ -45,7 +45,7 @@ namespace QuestPDF.Elements .Decoration(decoration => { decoration - .Header() + .Before() .DebugPointer("Page header") .Element(Header); @@ -57,7 +57,7 @@ namespace QuestPDF.Elements .Element(Content); decoration - .Footer() + .After() .DebugPointer("Page footer") .Element(Footer); }); diff --git a/QuestPDF/Elements/Row.cs b/QuestPDF/Elements/Row.cs index a315c84..3323d72 100644 --- a/QuestPDF/Elements/Row.cs +++ b/QuestPDF/Elements/Row.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using QuestPDF.Drawing; @@ -6,174 +7,152 @@ using QuestPDF.Infrastructure; namespace QuestPDF.Elements { - internal class RowElement : Constrained + internal enum RowItemType { - public float ConstantSize { get; } - public float RelativeSize { get; } - - public RowElement(float constantSize, float relativeSize) - { - ConstantSize = constantSize; - RelativeSize = relativeSize; - } - - public void SetWidth(float width) - { - MinWidth = width; - MaxWidth = width; - } + Auto, + Constant, + Relative } - internal class BinaryRow : Element, ICacheable, IStateResettable + internal class RowItem : Container { - internal Element Left { get; set; } - internal Element Right { get; set; } - - private bool IsLeftRendered { get; set; } - private bool IsRightRendered { get; set; } + 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 Items { get; } = new(); + internal float Spacing { get; set; } + public void ResetState() { - IsLeftRendered = false; - IsRightRendered = false; + Items.ForEach(x => x.IsRendered = false); } internal override IEnumerable GetChildren() { - yield return Left; - yield return Right; + return Items; } internal override void CreateProxy(Func create) { - Left = create(Left); - Right = create(Right); + Items.ForEach(x => x.Child = create(x.Child)); } internal override SpacePlan Measure(Size availableSpace) { - var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height)); + UpdateItemsWidth(availableSpace.Width); + var renderingCommands = PlanLayout(availableSpace); + + if (renderingCommands.Any(x => !x.RowItem.IsRendered && x.Measurement.Type == SpacePlanType.Wrap)) + return SpacePlan.Wrap(); + + var width = renderingCommands.Last().Offset.X + renderingCommands.Last().Size.Width; + 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) + return SpacePlan.Wrap(); - if (leftMeasurement.Type == SpacePlanType.Wrap) - return SpacePlan.Wrap(); + if (renderingCommands.Any(x => !x.RowItem.IsRendered && x.Measurement.Type == SpacePlanType.PartialRender)) + return SpacePlan.PartialRender(size); - var rightMeasurement = Right.Measure(new Size(availableSpace.Width - leftMeasurement.Width, availableSpace.Height)); - - if (rightMeasurement.Type == SpacePlanType.Wrap) - return SpacePlan.Wrap(); - - var totalWidth = leftMeasurement.Width + rightMeasurement.Width; - var totalHeight = Math.Max(leftMeasurement.Height, rightMeasurement.Height); - - var targetSize = new Size(totalWidth, totalHeight); - - if ((!IsLeftRendered && leftMeasurement.Type == SpacePlanType.PartialRender) || - (!IsRightRendered && rightMeasurement.Type == SpacePlanType.PartialRender)) - return SpacePlan.PartialRender(targetSize); - - return SpacePlan.FullRender(targetSize); + return SpacePlan.FullRender(size); } internal override void Draw(Size availableSpace) { - var leftSpace = new Size(availableSpace.Width, availableSpace.Height); - var leftMeasurement = Left.Measure(leftSpace); + UpdateItemsWidth(availableSpace.Width); + var renderingCommands = PlanLayout(availableSpace); - if (leftMeasurement.Type == SpacePlanType.FullRender) - IsLeftRendered = true; - - Left.Draw(new Size(leftMeasurement.Width, availableSpace.Height)); - - var rightSpace = new Size(availableSpace.Width - leftMeasurement.Width, availableSpace.Height); - var rightMeasurement = Right.Measure(rightSpace); - - if (rightMeasurement.Type == SpacePlanType.FullRender) - IsRightRendered = true; - - Canvas.Translate(new Position(leftMeasurement.Width, 0)); - Right.Draw(rightSpace); - Canvas.Translate(new Position(-leftMeasurement.Width, 0)); - } - } - - internal class Row : Element - { - public float Spacing { get; set; } = 0; - - public ICollection Items { get; internal set; } = new List(); - private Element? RootElement { get; set; } - - internal override IEnumerable GetChildren() - { - if (RootElement == null) - ComposeTree(); - - yield return RootElement; - } - - 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() - { - Items = AddSpacing(Items, Spacing); - - var elements = Items.Cast().ToArray(); - RootElement = BuildTree(elements); - } - - private void UpdateElementsWidth(float availableWidth) - { - var constantWidth = Items.Sum(x => x.ConstantSize); - var relativeWidth = Items.Sum(x => x.RelativeSize); - - var widthPerRelativeUnit = (relativeWidth > 0) ? (availableWidth - constantWidth) / relativeWidth : 0; - - foreach (var row in Items) + foreach (var command in renderingCommands) { - row.SetWidth(row.ConstantSize + row.RelativeSize * widthPerRelativeUnit); + if (command.Measurement.Type == SpacePlanType.FullRender) + command.RowItem.IsRendered = true; + + if (command.Measurement.Type == SpacePlanType.Wrap) + continue; + + 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 static ICollection AddSpacing(ICollection elements, float spacing) + + private ICollection PlanLayout(Size availableSpace) { - if (spacing < Size.Epsilon) - return elements; - - return elements - .SelectMany(x => new[] { new RowElement(spacing, 0), x }) - .Skip(1) - .ToList(); - } + var leftOffset = 0f; + var renderingCommands = new List(); - private static Element BuildTree(Span elements) - { - if (elements.IsEmpty) - return Empty.Instance; - - if (elements.Length == 1) - return elements[0]; - - var half = elements.Length / 2; - - return new BinaryRow + foreach (var item in Items) { - Left = BuildTree(elements.Slice(0, half)), - Right = BuildTree(elements.Slice(half)) - }; + 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; } - - #endregion } } \ No newline at end of file diff --git a/QuestPDF/Elements/ScaleToFit.cs b/QuestPDF/Elements/ScaleToFit.cs new file mode 100644 index 0000000..354df6b --- /dev/null +++ b/QuestPDF/Elements/ScaleToFit.cs @@ -0,0 +1,78 @@ +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; + } + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Stack.cs b/QuestPDF/Elements/Stack.cs deleted file mode 100644 index b190498..0000000 --- a/QuestPDF/Elements/Stack.cs +++ /dev/null @@ -1,143 +0,0 @@ -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 IEnumerable GetChildren() - { - yield return First; - yield return Second; - } - - public void ResetState() - { - IsFirstRendered = false; - } - - internal override void CreateProxy(Func 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 Items { get; } = new List(); - public float Spacing { get; set; } = 0; - - public void Compose(IContainer container) - { - var elements = AddSpacing(Spacing, Items); - - container - .PaddingBottom(-Spacing) - .Element(BuildTree(elements.ToArray())); - } - - static ICollection AddSpacing(float spacing, ICollection elements) - { - if (spacing < Size.Epsilon) - return elements; - - return elements - .Where(x => !(x is Empty)) - .Select(x => new Padding - { - Bottom = spacing, - Child = x - }) - .Cast() - .ToList(); - } - - static Element BuildTree(Span 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)) - }; - } - } -} \ No newline at end of file diff --git a/QuestPDF/Elements/StopPaging.cs b/QuestPDF/Elements/StopPaging.cs new file mode 100644 index 0000000..aadb319 --- /dev/null +++ b/QuestPDF/Elements/StopPaging.cs @@ -0,0 +1,25 @@ +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() + }; + } + } +} \ No newline at end of file diff --git a/QuestPDF/Fluent/BorderExtensions.cs b/QuestPDF/Fluent/BorderExtensions.cs index deafa13..32e94f1 100644 --- a/QuestPDF/Fluent/BorderExtensions.cs +++ b/QuestPDF/Fluent/BorderExtensions.cs @@ -14,39 +14,45 @@ namespace QuestPDF.Fluent return element.Element(border); } - public static IContainer Border(this IContainer element, float value) + public static IContainer Border(this IContainer element, float value, Unit unit = Unit.Point) { - return element.BorderHorizontal(value).BorderVertical(value); + return element + .BorderHorizontal(value, unit) + .BorderVertical(value, unit); } - public static IContainer BorderVertical(this IContainer element, float value) + public static IContainer BorderVertical(this IContainer element, float value, Unit unit = Unit.Point) { - return element.BorderLeft(value).BorderRight(value); + return element + .BorderLeft(value, unit) + .BorderRight(value, unit); } - public static IContainer BorderHorizontal(this IContainer element, float value) + public static IContainer BorderHorizontal(this IContainer element, float value, Unit unit = Unit.Point) { - return element.BorderTop(value).BorderBottom(value); + return element + .BorderTop(value, unit) + .BorderBottom(value, unit); } - public static IContainer BorderLeft(this IContainer element, float value) + public static IContainer BorderLeft(this IContainer element, float value, Unit unit = Unit.Point) { - return element.Border(x => x.Left = value); + return element.Border(x => x.Left = value.ToPoints(unit)); } - public static IContainer BorderRight(this IContainer element, float value) + public static IContainer BorderRight(this IContainer element, float value, Unit unit = Unit.Point) { - return element.Border(x => x.Right = value); + return element.Border(x => x.Right = value.ToPoints(unit)); } - public static IContainer BorderTop(this IContainer element, float value) + public static IContainer BorderTop(this IContainer element, float value, Unit unit = Unit.Point) { - return element.Border(x => x.Top = value); + return element.Border(x => x.Top = value.ToPoints(unit)); } - public static IContainer BorderBottom(this IContainer element, float value) + public static IContainer BorderBottom(this IContainer element, float value, Unit unit = Unit.Point) { - return element.Border(x => x.Bottom = value); + return element.Border(x => x.Bottom = value.ToPoints(unit)); } public static IContainer BorderColor(this IContainer element, string color) diff --git a/QuestPDF/Fluent/ColumnExtensions.cs b/QuestPDF/Fluent/ColumnExtensions.cs new file mode 100644 index 0000000..9c10b20 --- /dev/null +++ b/QuestPDF/Fluent/ColumnExtensions.cs @@ -0,0 +1,44 @@ +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 handler) + { + element.Column(handler); + } + + public static void Column(this IContainer element, Action handler) + { + var descriptor = new ColumnDescriptor(); + handler(descriptor); + element.Element(descriptor.Column); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Fluent/ConstrainedExtensions.cs b/QuestPDF/Fluent/ConstrainedExtensions.cs index bb30da2..0f64abe 100644 --- a/QuestPDF/Fluent/ConstrainedExtensions.cs +++ b/QuestPDF/Fluent/ConstrainedExtensions.cs @@ -14,34 +14,38 @@ namespace QuestPDF.Fluent return element.Element(constrained); } - public static IContainer Width(this IContainer element, float value) + public static IContainer Width(this IContainer element, float value, Unit unit = Unit.Point) { - return element.MinWidth(value).MaxWidth(value); + return element + .MinWidth(value, unit) + .MaxWidth(value, unit); } - public static IContainer MinWidth(this IContainer element, float value) + public static IContainer MinWidth(this IContainer element, float value, Unit unit = Unit.Point) { - return element.Constrained(x => x.MinWidth = value); + return element.Constrained(x => x.MinWidth = value.ToPoints(unit)); } - public static IContainer MaxWidth(this IContainer element, float value) + public static IContainer MaxWidth(this IContainer element, float value, Unit unit = Unit.Point) { - return element.Constrained(x => x.MaxWidth = value); + return element.Constrained(x => x.MaxWidth = value.ToPoints(unit)); } - public static IContainer Height(this IContainer element, float value) + public static IContainer Height(this IContainer element, float value, Unit unit = Unit.Point) { - return element.MinHeight(value).MaxHeight(value); + return element + .MinHeight(value, unit) + .MaxHeight(value, unit); } - public static IContainer MinHeight(this IContainer element, float value) + public static IContainer MinHeight(this IContainer element, float value, Unit unit = Unit.Point) { - return element.Constrained(x => x.MinHeight = value); + return element.Constrained(x => x.MinHeight = value.ToPoints(unit)); } - public static IContainer MaxHeight(this IContainer element, float value) + public static IContainer MaxHeight(this IContainer element, float value, Unit unit = Unit.Point) { - return element.Constrained(x => x.MaxHeight = value); + return element.Constrained(x => x.MaxHeight = value.ToPoints(unit)); } } } \ No newline at end of file diff --git a/QuestPDF/Fluent/DecorationExtensions.cs b/QuestPDF/Fluent/DecorationExtensions.cs index d8cc7d6..97a4428 100644 --- a/QuestPDF/Fluent/DecorationExtensions.cs +++ b/QuestPDF/Fluent/DecorationExtensions.cs @@ -8,16 +8,16 @@ namespace QuestPDF.Fluent { internal Decoration Decoration { get; } = new Decoration(); - public IContainer Header() + public IContainer Before() { var container = new Container(); - Decoration.Header = container; + Decoration.Before = container; return container; } - public void Header(Action handler) + public void Before(Action handler) { - handler?.Invoke(Header()); + handler?.Invoke(Before()); } public IContainer Content() @@ -32,17 +32,49 @@ namespace QuestPDF.Fluent handler?.Invoke(Content()); } - public IContainer Footer() + public IContainer After() { var container = new Container(); - Decoration.Footer = container; + Decoration.After = container; return container; } + public void After(Action 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 handler) + { + handler?.Invoke(Header()); + } + + [Obsolete("This element has been renamed since version 2022.2. Please use the 'After' method.")] + public IContainer Footer() + { + var container = new Container(); + Decoration.After = container; + return container; + } + + [Obsolete("This element has been renamed since version 2022.2. Please use the 'After' method.")] public void Footer(Action handler) { handler?.Invoke(Footer()); } + + #endregion } public static class DecorationExtensions @@ -52,7 +84,7 @@ namespace QuestPDF.Fluent var descriptor = new DecorationDescriptor(); handler(descriptor); - element.Component(descriptor.Decoration); + element.Element(descriptor.Decoration); } } } \ No newline at end of file diff --git a/QuestPDF/Fluent/ElementExtensions.cs b/QuestPDF/Fluent/ElementExtensions.cs index 856585d..d9ce5fb 100644 --- a/QuestPDF/Fluent/ElementExtensions.cs +++ b/QuestPDF/Fluent/ElementExtensions.cs @@ -136,8 +136,7 @@ namespace QuestPDF.Fluent }); } - // TODO: deprecated Box method in QuestPDF 2022.1 - [Obsolete("This element has been renamed. Please use the MinimalBox method.")] + [Obsolete("This element has been renamed since version 2022.1. Please use the MinimalBox method.")] public static IContainer Box(this IContainer element) { return element.Element(new MinimalBox()); @@ -160,5 +159,15 @@ namespace QuestPDF.Fluent 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()); + } } } diff --git a/QuestPDF/Fluent/GridExtensions.cs b/QuestPDF/Fluent/GridExtensions.cs index d08942e..e7ae0ca 100644 --- a/QuestPDF/Fluent/GridExtensions.cs +++ b/QuestPDF/Fluent/GridExtensions.cs @@ -8,20 +8,20 @@ namespace QuestPDF.Fluent { internal Grid Grid { get; } = new Grid(); - public void Spacing(float value) + public void Spacing(float value, Unit unit = Unit.Point) { - VerticalSpacing(value); - HorizontalSpacing(value); + VerticalSpacing(value, unit); + HorizontalSpacing(value, unit); } - public void VerticalSpacing(float value) + public void VerticalSpacing(float value, Unit unit = Unit.Point) { - Grid.VerticalSpacing = value; + Grid.VerticalSpacing = value.ToPoints(unit); } - public void HorizontalSpacing(float value) + public void HorizontalSpacing(float value, Unit unit = Unit.Point) { - Grid.HorizontalSpacing = value; + Grid.HorizontalSpacing = value.ToPoints(unit); } public void Columns(int value = Grid.DefaultColumnsCount) diff --git a/QuestPDF/Fluent/InlinedExtensions.cs b/QuestPDF/Fluent/InlinedExtensions.cs index b250c49..140185c 100644 --- a/QuestPDF/Fluent/InlinedExtensions.cs +++ b/QuestPDF/Fluent/InlinedExtensions.cs @@ -10,14 +10,21 @@ namespace QuestPDF.Fluent { internal Inlined Inlined { get; } = new Inlined(); - public void Spacing(float value) + public void Spacing(float value, Unit unit = Unit.Point) { - VerticalSpacing(value); - HorizontalSpacing(value); + VerticalSpacing(value, unit); + HorizontalSpacing(value, unit); } - public void VerticalSpacing(float value) => Inlined.VerticalSpacing = value; - public void HorizontalSpacing(float value) => Inlined.HorizontalSpacing = value; + public void VerticalSpacing(float value, Unit unit = Unit.Point) + { + Inlined.VerticalSpacing = value.ToPoints(unit); + } + + public void HorizontalSpacing(float value, Unit unit = Unit.Point) + { + Inlined.HorizontalSpacing = value.ToPoints(unit); + } public void BaselineTop() => Inlined.BaselineAlignment = VerticalAlignment.Top; public void BaselineMiddle() => Inlined.BaselineAlignment = VerticalAlignment.Middle; diff --git a/QuestPDF/Fluent/LineExtensions.cs b/QuestPDF/Fluent/LineExtensions.cs new file mode 100644 index 0000000..2dacdcf --- /dev/null +++ b/QuestPDF/Fluent/LineExtensions.cs @@ -0,0 +1,36 @@ +using System; +using QuestPDF.Elements; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Fluent +{ + public static class LineExtensions + { + private static ILine Line(this IContainer element, LineType type, float size) + { + var line = new Line + { + Size = size, + Type = type + }; + + element.Element(line); + return line; + } + + public static ILine LineVertical(this IContainer element, float size, Unit unit = Unit.Point) + { + return element.Line(LineType.Vertical, size.ToPoints(unit)); + } + + public static ILine LineHorizontal(this IContainer element, float size, Unit unit = Unit.Point) + { + return element.Line(LineType.Horizontal, size.ToPoints(unit)); + } + + public static void LineColor(this ILine descriptor, string value) + { + (descriptor as Line).Color = value; + } + } +} \ No newline at end of file diff --git a/QuestPDF/Fluent/PaddingExtensions.cs b/QuestPDF/Fluent/PaddingExtensions.cs index 6f4b579..33d0b46 100644 --- a/QuestPDF/Fluent/PaddingExtensions.cs +++ b/QuestPDF/Fluent/PaddingExtensions.cs @@ -14,39 +14,45 @@ namespace QuestPDF.Fluent return element.Element(padding); } - public static IContainer Padding(this IContainer element, float value) + public static IContainer Padding(this IContainer element, float value, Unit unit = Unit.Point) { - return element.PaddingVertical(value).PaddingHorizontal(value); + return element + .PaddingVertical(value, unit) + .PaddingHorizontal(value, unit); } - public static IContainer PaddingHorizontal(this IContainer element, float value) + public static IContainer PaddingHorizontal(this IContainer element, float value, Unit unit = Unit.Point) { - return element.PaddingLeft(value).PaddingRight(value); + return element + .PaddingLeft(value, unit) + .PaddingRight(value, unit); } - public static IContainer PaddingVertical(this IContainer element, float value) + public static IContainer PaddingVertical(this IContainer element, float value, Unit unit = Unit.Point) { - return element.PaddingTop(value).PaddingBottom(value); + return element + .PaddingTop(value, unit) + .PaddingBottom(value, unit); } - public static IContainer PaddingTop(this IContainer element, float value) + public static IContainer PaddingTop(this IContainer element, float value, Unit unit = Unit.Point) { - return element.Padding(x => x.Top = value); + return element.Padding(x => x.Top += value.ToPoints(unit)); } - public static IContainer PaddingBottom(this IContainer element, float value) + public static IContainer PaddingBottom(this IContainer element, float value, Unit unit = Unit.Point) { - return element.Padding(x => x.Bottom = value); + return element.Padding(x => x.Bottom += value.ToPoints(unit)); } - public static IContainer PaddingLeft(this IContainer element, float value) + public static IContainer PaddingLeft(this IContainer element, float value, Unit unit = Unit.Point) { - return element.Padding(x => x.Left = value); + return element.Padding(x => x.Left += value.ToPoints(unit)); } - public static IContainer PaddingRight(this IContainer element, float value) + public static IContainer PaddingRight(this IContainer element, float value, Unit unit = Unit.Point) { - return element.Padding(x => x.Right = value); + return element.Padding(x => x.Right += value.ToPoints(unit)); } } } \ No newline at end of file diff --git a/QuestPDF/Fluent/PageExtensions.cs b/QuestPDF/Fluent/PageExtensions.cs index 82d7dc5..ab968ee 100644 --- a/QuestPDF/Fluent/PageExtensions.cs +++ b/QuestPDF/Fluent/PageExtensions.cs @@ -10,16 +10,24 @@ namespace QuestPDF.Fluent { internal Page Page { get; } = new Page(); + public void Size(float width, float height, Unit unit = Unit.Inch) + { + var pageSize = new PageSize(width, height, unit); + + MinSize(pageSize); + MaxSize(pageSize); + } + public void Size(PageSize pageSize) { MinSize(pageSize); MaxSize(pageSize); } - public void ContinuousSize(float width) + public void ContinuousSize(float width, Unit unit = Unit.Point) { - MinSize(new PageSize(width, 0)); - MaxSize(new PageSize(width, Infrastructure.Size.Max.Height)); + MinSize(new PageSize(width.ToPoints(unit), 0)); + MaxSize(new PageSize(width.ToPoints(unit), Infrastructure.Size.Max.Height)); } public void MinSize(PageSize pageSize) @@ -32,42 +40,42 @@ namespace QuestPDF.Fluent Page.MaxSize = pageSize; } - public void MarginLeft(float value) + public void MarginLeft(float value, Unit unit = Unit.Point) { - Page.MarginLeft = value; + Page.MarginLeft = value.ToPoints(unit); } - public void MarginRight(float value) + public void MarginRight(float value, Unit unit = Unit.Point) { - Page.MarginRight = value; + Page.MarginRight = value.ToPoints(unit); } - public void MarginTop(float value) + public void MarginTop(float value, Unit unit = Unit.Point) { - Page.MarginTop = value; + Page.MarginTop = value.ToPoints(unit); } - public void MarginBottom(float value) + public void MarginBottom(float value, Unit unit = Unit.Point) { - Page.MarginBottom = value; + Page.MarginBottom = value.ToPoints(unit); } - public void MarginVertical(float value) + public void MarginVertical(float value, Unit unit = Unit.Point) { - MarginTop(value); - MarginBottom(value); + MarginTop(value, unit); + MarginBottom(value, unit); } - public void MarginHorizontal(float value) + public void MarginHorizontal(float value, Unit unit = Unit.Point) { - MarginLeft(value); - MarginRight(value); + MarginLeft(value, unit); + MarginRight(value, unit); } - public void Margin(float value) + public void Margin(float value, Unit unit = Unit.Point) { - MarginVertical(value); - MarginHorizontal(value); + MarginVertical(value, unit); + MarginHorizontal(value, unit); } public void DefaultTextStyle(TextStyle textStyle) diff --git a/QuestPDF/Fluent/RowExtensions.cs b/QuestPDF/Fluent/RowExtensions.cs index b07f561..2aa5f69 100644 --- a/QuestPDF/Fluent/RowExtensions.cs +++ b/QuestPDF/Fluent/RowExtensions.cs @@ -6,30 +6,51 @@ namespace QuestPDF.Fluent { public class RowDescriptor { - internal Row Row { get; } = new Row(); + internal Row Row { get; } = new(); public void Spacing(float value) { Row.Spacing = value; } - - public IContainer ConstantColumn(float width) + + private IContainer Item(RowItemType type, float size = 0) { - return Column(constantWidth: width); - } - - public IContainer RelativeColumn(float width = 1) - { - return Column(relativeWidth: width); - } - - private IContainer Column(float constantWidth = 0, float relativeWidth = 0) - { - var element = new RowElement(constantWidth, relativeWidth); + var element = new RowItem + { + Type = type, + Size = size + }; Row.Items.Add(element); return element; } + + [Obsolete("This element has been renamed since version 2022.2. Please use the RelativeItem method.")] + public IContainer RelativeColumn(float size = 1) + { + return Item(RowItemType.Relative, size); + } + + [Obsolete("This element has been renamed since version 2022.2. Please use the ConstantItem method.")] + public IContainer ConstantColumn(float size) + { + return Item(RowItemType.Constant, size); + } + + public IContainer RelativeItem(float size = 1) + { + return Item(RowItemType.Relative, size); + } + + public IContainer ConstantItem(float size, Unit unit = Unit.Point) + { + return Item(RowItemType.Constant, size.ToPoints(unit)); + } + + public IContainer AutoItem() + { + return Item(RowItemType.Auto); + } } public static class RowExtensions diff --git a/QuestPDF/Fluent/ScaleExtensions.cs b/QuestPDF/Fluent/ScaleExtensions.cs index 58ae16a..eb8eb74 100644 --- a/QuestPDF/Fluent/ScaleExtensions.cs +++ b/QuestPDF/Fluent/ScaleExtensions.cs @@ -21,7 +21,7 @@ namespace QuestPDF.Fluent public static IContainer ScaleHorizontal(this IContainer element, float value) { - return element.Scale(x => x.ScaleX = value); + return element.Scale(x => x.ScaleX *= value); } public static IContainer FlipHorizontal(this IContainer element) @@ -31,7 +31,7 @@ namespace QuestPDF.Fluent public static IContainer ScaleVertical(this IContainer element, float value) { - return element.Scale(x => x.ScaleY = value); + return element.Scale(x => x.ScaleY *= value); } public static IContainer FlipVertical(this IContainer element) diff --git a/QuestPDF/Fluent/StackExtensions.cs b/QuestPDF/Fluent/StackExtensions.cs deleted file mode 100644 index dd14048..0000000 --- a/QuestPDF/Fluent/StackExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using QuestPDF.Elements; -using QuestPDF.Infrastructure; - -namespace QuestPDF.Fluent -{ - public class StackDescriptor - { - internal Stack Stack { get; } = new Stack(); - - public void Spacing(float value) - { - Stack.Spacing = value; - } - - public IContainer Item() - { - var container = new Container(); - Stack.Items.Add(container); - return container; - } - } - - public static class StackExtensions - { - public static void Stack(this IContainer element, Action handler) - { - var descriptor = new StackDescriptor(); - handler(descriptor); - element.Component(descriptor.Stack); - } - } -} \ No newline at end of file diff --git a/QuestPDF/Fluent/TableExtensions.cs b/QuestPDF/Fluent/TableExtensions.cs index 8e66257..f345c83 100644 --- a/QuestPDF/Fluent/TableExtensions.cs +++ b/QuestPDF/Fluent/TableExtensions.cs @@ -14,9 +14,9 @@ namespace QuestPDF.Fluent { internal List Columns { get; } = new(); - public void ConstantColumn(float width) + public void ConstantColumn(float width, Unit unit = Unit.Point) { - ComplexColumn(constantWidth: width); + ComplexColumn(constantWidth: width.ToPoints(unit)); } public void RelativeColumn(float width = 1) @@ -24,7 +24,7 @@ namespace QuestPDF.Fluent ComplexColumn(relativeWidth: width); } - public void ComplexColumn(float constantWidth = 0, float relativeWidth = 0) + private void ComplexColumn(float constantWidth = 0, float relativeWidth = 0) { var columnDefinition = new TableColumnDefinition(constantWidth, relativeWidth); Columns.Add(columnDefinition); @@ -101,9 +101,9 @@ namespace QuestPDF.Fluent container .Decoration(decoration => { - decoration.Header().Element(HeaderTable); + decoration.Before().Element(HeaderTable); decoration.Content().Element(ContentTable); - decoration.Footer().Element(FooterTable); + decoration.After().Element(FooterTable); }); return container; diff --git a/QuestPDF/Fluent/TextExtensions.cs b/QuestPDF/Fluent/TextExtensions.cs index c08cfd6..2d688b4 100644 --- a/QuestPDF/Fluent/TextExtensions.cs +++ b/QuestPDF/Fluent/TextExtensions.cs @@ -37,9 +37,9 @@ namespace QuestPDF.Fluent Alignment = HorizontalAlignment.Right; } - public void ParagraphSpacing(float value) + public void ParagraphSpacing(float value, Unit unit = Unit.Point) { - Spacing = value; + Spacing = value.ToPoints(unit); } private void AddItemToLastTextBlock(ITextBlockItem item) @@ -174,12 +174,12 @@ namespace QuestPDF.Fluent { TextBlocks.ToList().ForEach(x => x.Alignment = Alignment); - container.DefaultTextStyle(DefaultStyle).Stack(stack => + container.DefaultTextStyle(DefaultStyle).Column(column => { - stack.Spacing(Spacing); + column.Spacing(Spacing); foreach (var textBlock in TextBlocks) - stack.Item().Element(textBlock); + column.Item().Element(textBlock); }); } } diff --git a/QuestPDF/Fluent/TranslateExtensions.cs b/QuestPDF/Fluent/TranslateExtensions.cs index 401eade..2e32059 100644 --- a/QuestPDF/Fluent/TranslateExtensions.cs +++ b/QuestPDF/Fluent/TranslateExtensions.cs @@ -14,14 +14,14 @@ namespace QuestPDF.Fluent return element.Element(translate); } - public static IContainer TranslateX(this IContainer element, float value) + public static IContainer TranslateX(this IContainer element, float value, Unit unit = Unit.Point) { - return element.Translate(x => x.TranslateX = value); + return element.Translate(x => x.TranslateX += value.ToPoints(unit)); } - public static IContainer TranslateY(this IContainer element, float value) + public static IContainer TranslateY(this IContainer element, float value, Unit unit = Unit.Point) { - return element.Translate(x => x.TranslateY = value); + return element.Translate(x => x.TranslateY += value.ToPoints(unit)); } } } \ No newline at end of file diff --git a/QuestPDF/Helpers/PageSizes.cs b/QuestPDF/Helpers/PageSizes.cs index c62e0df..fd52af1 100644 --- a/QuestPDF/Helpers/PageSizes.cs +++ b/QuestPDF/Helpers/PageSizes.cs @@ -8,10 +8,10 @@ namespace QuestPDF.Helpers public readonly float Width; public readonly float Height; - public PageSize(float width, float height) + public PageSize(float width, float height, Unit unit = Unit.Point) { - Width = width; - Height = height; + Width = width.ToPoints(unit); + Height = height.ToPoints(unit); } public static implicit operator Size(PageSize pageSize) => new Size(pageSize.Width, pageSize.Height); diff --git a/QuestPDF/Helpers/WriteOnlyStream.cs b/QuestPDF/Helpers/WriteOnlyStream.cs new file mode 100644 index 0000000..e0c4343 --- /dev/null +++ b/QuestPDF/Helpers/WriteOnlyStream.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; + +namespace QuestPDF.Helpers +{ + /// + /// SkiaSharp calls the Position property when generating target document file. + /// If the output stream does not support the Position property, the NullReferenceException is thrown. + /// This wrapper fixes this issue by providing cached Position value (always the end of the stream / current length). + /// Example stream affected: HttpContext.Response.Body + /// + internal class WriteOnlyStream : Stream + { + private readonly Stream InnerStream; + private long StreamLength { get; set; } + + public WriteOnlyStream(Stream stream) + { + if (!stream.CanWrite) + throw new NotSupportedException("Stream cannot be written"); + + InnerStream = stream; + } + + public override bool CanRead => false; + + public override bool CanSeek => false; + + public override bool CanWrite => true; + + public override long Length => StreamLength; + + public override long Position + { + get => StreamLength; + set => throw new NotImplementedException(); + } + + public override void Flush() => InnerStream.Flush(); + + public override int Read(byte[] buffer, int offset, int count) + => throw new NotImplementedException(); + + public override long Seek(long offset, SeekOrigin origin) + => throw new NotImplementedException(); + + public override void SetLength(long value) + => throw new NotImplementedException(); + + public override void Write(byte[] buffer, int offset, int count) + { + InnerStream.Write(buffer, offset, count); + StreamLength += count; + } + } +} diff --git a/QuestPDF/Infrastructure/Size.cs b/QuestPDF/Infrastructure/Size.cs index 53758a9..bf80b7f 100644 --- a/QuestPDF/Infrastructure/Size.cs +++ b/QuestPDF/Infrastructure/Size.cs @@ -3,13 +3,13 @@ public readonly struct Size { public const float Epsilon = 0.001f; - public const float Infinity = float.PositiveInfinity; + public const float Infinity = 14_400; public readonly float Width; public readonly float Height; public static Size Zero { get; } = new Size(0, 0); - public static Size Max { get; } = new Size(14_400, 14_400); + public static Size Max { get; } = new Size(Infinity, Infinity); public Size(float width, float height) { diff --git a/QuestPDF/Infrastructure/Unit.cs b/QuestPDF/Infrastructure/Unit.cs new file mode 100644 index 0000000..3d2cf8d --- /dev/null +++ b/QuestPDF/Infrastructure/Unit.cs @@ -0,0 +1,44 @@ +using System; +using static QuestPDF.Infrastructure.Unit; + +namespace QuestPDF.Infrastructure +{ + public enum Unit + { + Point, + + Meter, + Centimetre, + Millimetre, + + Feet, + Inch, + Mill + } + + internal static class UnitExtensions + { + private const float InchToCentimetre = 2.54f; + private const float InchToPoints = 72; + + public static float ToPoints(this float value, Unit unit) + { + return value * GetConversionFactor(); + + float GetConversionFactor() + { + return unit switch + { + Point => 1, + Meter => 100 / InchToCentimetre * InchToPoints, + Centimetre => 1 / InchToCentimetre * InchToPoints, + Millimetre => 10 / InchToCentimetre * InchToPoints, + Feet => 12 * InchToPoints, + Inch => InchToPoints, + Mill => InchToPoints / 1000f, + _ => throw new ArgumentOutOfRangeException(nameof(unit), unit, null) + }; + } + } + } +} \ No newline at end of file diff --git a/QuestPDF/QuestPDF.csproj b/QuestPDF/QuestPDF.csproj index 4956ddb..9215d4e 100644 --- a/QuestPDF/QuestPDF.csproj +++ b/QuestPDF/QuestPDF.csproj @@ -4,7 +4,7 @@ MarcinZiabek CodeFlint QuestPDF - 2022.1.0 + 2022.2.0 QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API. $([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt")) 9 diff --git a/QuestPDF/Resources/Description.md b/QuestPDF/Resources/Description.md index d3f8432..b1a444d 100644 --- a/QuestPDF/Resources/Description.md +++ b/QuestPDF/Resources/Description.md @@ -71,15 +71,15 @@ void ComposeHeader(IContainer container) container.Row(row => { { - stack.Item().Text($"Invoice #{Model.InvoiceNumber}", titleStyle); + column.Item().Text($"Invoice #{Model.InvoiceNumber}", titleStyle); - stack.Item().Text(text => + column.Item().Text(text => { text.Span("Issue date: ", TextStyle.Default.SemiBold()); text.Span($"{Model.IssueDate:d}"); }); - stack.Item().Text(text => + column.Item().Text(text => { text.Span("Due date: ", TextStyle.Default.SemiBold()); text.Span($"{Model.DueDate:d}"); @@ -97,7 +97,7 @@ Implementation of **the content area** that contains seller and customer details ```csharp void ComposeContent(IContainer container) { - container.PaddingVertical(40).Stack(column => + container.PaddingVertical(40).column(column => { column.Spacing(20); @@ -147,7 +147,7 @@ void ComposeTable(IContainer container) // content decoration .Content() - .Stack(column => + .column(column => { foreach (var item in Model.Items) { @@ -174,7 +174,7 @@ void ComposeTable(IContainer container) ```csharp void ComposeComments(IContainer container) { - container.ShowEntire().Background(Colors.Grey.Lighten3).Padding(10).Stack(message => + container.ShowEntire().Background(Colors.Grey.Lighten3).Padding(10).column(message => { message.Spacing(5); message.Item().Text("Comments", TextStyle.Default.Size(14).SemiBold()); @@ -200,7 +200,7 @@ public class AddressComponent : IComponent public void Compose(IContainer container) { - container.ShowEntire().Stack(column => + container.ShowEntire().column(column => { column.Spacing(5); diff --git a/QuestPDF/Resources/ReleaseNotes.txt b/QuestPDF/Resources/ReleaseNotes.txt index cc6151c..dcdce0b 100644 --- a/QuestPDF/Resources/ReleaseNotes.txt +++ b/QuestPDF/Resources/ReleaseNotes.txt @@ -1,4 +1,8 @@ -- Introduced new element: `Table` - a great way to construct complex document structures, e.g. reports. This element covers all cases offered by combination of the `Stack` and the `Row` elements. Additionally, it provides support for more complex layouts and corner cases. Updating to the `Table` element can greatly simplify your code 😁 -- Added new element `DefaultTextStyle` - it allows set new text style to all its children, -- Improved the default paging behavior for the `Row` element. In some minor corner cases it might cause infinite layout exceptions and confuse developers. -- Fixed default page sizes for: Letter and Legal. +- Added a `ScaleToFit` element - scales its child down so it fits in the provided space, +- Added a `StopPaging` element - when its child requires more than one page to fully render, only the first page is shown, +- Added a 'LineVertical' and a 'LineHorizontal' elements - those will simplify your code a lot, there is no need to use the `Border` element anymore! +- Renaming: the `Stack` element was renamed to the `Column` element, +- Renaming: children of the `Row` element are now called `items` instead of `columns`, e.g. `RelativeItem` instead of `RelativeColumn`, +- Added support of the `AutoItem` to the `Row` element - those items take as little width as possible, +- Improved default Fluent configuration behavior for elements: Scale, Padding, Translate, +- Improved integration support with the HttpContext.Response.Body. This improvement was introduced by schulz3000, thank you! diff --git a/readme.md b/readme.md index 457fe44..3a6b933 100644 --- a/readme.md +++ b/readme.md @@ -149,41 +149,40 @@ void ComposeTable(IContainer container) { var headerStyle = TextStyle.Default.SemiBold(); - container.Decoration(decoration => + container.Table(table => { - // header - decoration.Header().BorderBottom(1).Padding(5).Row(row => + table.ColumnsDefinition(columns => { - row.ConstantColumn(25).Text("#", headerStyle); - row.RelativeColumn(3).Text("Product", headerStyle); - row.RelativeColumn().AlignRight().Text("Unit price", headerStyle); - row.RelativeColumn().AlignRight().Text("Quantity", headerStyle); - row.RelativeColumn().AlignRight().Text("Total", headerStyle); + columns.ConstantColumn(25); + columns.RelativeColumn(3); + columns.RelativeColumn(); + columns.RelativeColumn(); + columns.RelativeColumn(); }); - - // content - decoration - .Content() - .Stack(column => - { - foreach (var item in Model.Items) - { - column - .Item() - .ShowEntire() - .BorderBottom(1) - .BorderColor(Colors.Grey.Lighten2) - .Padding(5) - .Row(row => - { - row.ConstantColumn(25).Text(Model.Items.IndexOf(item) + 1); - row.RelativeColumn(3).Text(item.Name); - row.RelativeColumn().AlignRight().Text($"{item.Price}$"); - row.RelativeColumn().AlignRight().Text(item.Quantity); - row.RelativeColumn().AlignRight().Text($"{item.Price * item.Quantity}$"); - }); - } - }); + + table.Header(header => + { + header.Cell().Text("#", headerStyle); + header.Cell().Text("Product", headerStyle); + header.Cell().AlignRight().Text("Unit price", headerStyle); + header.Cell().AlignRight().Text("Quantity", headerStyle); + header.Cell().AlignRight().Text("Total", headerStyle); + + header.Cell().ColumnSpan(5) + .PaddingVertical(5).BorderBottom(1).BorderColor(Colors.Black); + }); + + foreach (var item in Model.Items) + { + table.Cell().Text(Model.Items.IndexOf(item) + 1); + table.Cell().Text(item.Name); + table.Cell().AlignRight().Text($"{item.Price}$"); + table.Cell().AlignRight().Text(item.Quantity); + table.Cell().AlignRight().Text($"{item.Price * item.Quantity}$"); + + table.Cell().ColumnSpan(5) + .PaddingVertical(5).LineHorizontal(1).LineColor(Colors.Grey.Lighten2); + } }); } ```