diff --git a/QuestPDF.Examples/ElementExamples.cs b/QuestPDF.Examples/ElementExamples.cs index 37ce1fb..2dace42 100644 --- a/QuestPDF.Examples/ElementExamples.cs +++ b/QuestPDF.Examples/ElementExamples.cs @@ -19,20 +19,20 @@ namespace QuestPDF.Examples //[ShowResult] [ImageSize(300, 300)] - public void Section(IContainer container) + public void Decoration(IContainer container) { container .Background("#FFF") .Padding(25) - .Decoration(section => + .Decoration(decoration => { - section + decoration .Header() .Background("#888") .Padding(10) .Text("Notes", TextStyle.Default.Size(16).Color("#FFF")); - section + decoration .Content() .Background("#DDD") .Padding(10) @@ -181,12 +181,12 @@ namespace QuestPDF.Examples grid.Spacing(5); grid.Columns(12); - grid.Element(8).Background("#DDD").Height(50).Padding(5).Text("This is a short text", textStyle); - grid.Element(4).Background("#BBB").Padding(5).Text("Showing how to...", textStyle); - grid.Element(2).Background("#999").Height(50); - grid.Element(4).Background("#AAA").Border(2).BorderColor("#666").Padding(5).Text("...generate", textStyle); - grid.Element(6).Background("#CCC").Padding(5).Text("simple grids", textStyle.Size(18).Bold()); - grid.Element(8).Background("#DDD").Height(50); + grid.Item(8).Background("#DDD").Height(50).Padding(5).Text("This is a short text", textStyle); + grid.Item(4).Background("#BBB").Padding(5).Text("Showing how to...", textStyle); + grid.Item(2).Background("#999").Height(50); + grid.Item(4).Background("#AAA").Border(2).BorderColor("#666").Padding(5).Text("...generate", textStyle); + grid.Item(6).Background("#CCC").Padding(5).Text("simple grids", textStyle.Size(18).Bold()); + grid.Item(8).Background("#DDD").Height(50); }); } diff --git a/QuestPDF.ReportSample/Layouts/PhotoTemplate.cs b/QuestPDF.ReportSample/Layouts/PhotoTemplate.cs index 515c813..8384147 100644 --- a/QuestPDF.ReportSample/Layouts/PhotoTemplate.cs +++ b/QuestPDF.ReportSample/Layouts/PhotoTemplate.cs @@ -20,8 +20,8 @@ namespace QuestPDF.ReportSample.Layouts .Stack(stack => { stack.Spacing(5); - stack.Item(PhotoWithMaps); - stack.Item(PhotoDetails); + stack.Item().Element(PhotoWithMaps); + stack.Item().Element(PhotoDetails); }); } @@ -48,13 +48,13 @@ namespace QuestPDF.ReportSample.Layouts { grid.Columns(6); - grid.Element().LabelCell().Text("Date", Typography.Normal); - grid.Element(2).ValueCell().Text(Model.Date?.ToString("g") ?? string.Empty, Typography.Normal); - grid.Element().LabelCell().Text("Location", Typography.Normal); - grid.Element(2).ValueCell().Text(Model.Location.Format(), Typography.Normal); + grid.Item().LabelCell().Text("Date", Typography.Normal); + grid.Item(2).ValueCell().Text(Model.Date?.ToString("g") ?? string.Empty, Typography.Normal); + grid.Item().LabelCell().Text("Location", Typography.Normal); + grid.Item(2).ValueCell().Text(Model.Location.Format(), Typography.Normal); - grid.Element().LabelCell().Text("Comments", Typography.Normal); - grid.Element(5).ValueCell().Text(Model.Comments, Typography.Normal); + grid.Item().LabelCell().Text("Comments", Typography.Normal); + grid.Item(5).ValueCell().Text(Model.Comments, Typography.Normal); }); } } diff --git a/QuestPDF.ReportSample/Layouts/SectionTemplate.cs b/QuestPDF.ReportSample/Layouts/SectionTemplate.cs index 197eca1..8241810 100644 --- a/QuestPDF.ReportSample/Layouts/SectionTemplate.cs +++ b/QuestPDF.ReportSample/Layouts/SectionTemplate.cs @@ -17,14 +17,14 @@ namespace QuestPDF.ReportSample.Layouts { container .EnsureSpace() - .Decoration(section => + .Decoration(decoration => { - section + decoration .Header() .PaddingBottom(5) .Text(Model.Title, Typography.Headline); - section.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Stack(stack => + decoration.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Stack(stack => { foreach (var part in Model.Parts) { @@ -77,7 +77,7 @@ namespace QuestPDF.ReportSample.Layouts grid.Spacing(5); grid.Columns(3); - model.Photos.ForEach(x => grid.Element().AspectRatio(4 / 3f).Image(Placeholders.Image)); + model.Photos.ForEach(x => grid.Item().AspectRatio(4 / 3f).Image(Placeholders.Image)); }); } } diff --git a/QuestPDF.ReportSample/Layouts/StandardReport.cs b/QuestPDF.ReportSample/Layouts/StandardReport.cs index e6c43e9..ba71f14 100644 --- a/QuestPDF.ReportSample/Layouts/StandardReport.cs +++ b/QuestPDF.ReportSample/Layouts/StandardReport.cs @@ -30,8 +30,8 @@ namespace QuestPDF.ReportSample.Layouts .PaddingHorizontal(50) .Page(page => { - page.Header(ComposeHeader); - page.Content(ComposeContent); + page.Header().Element(ComposeHeader); + page.Content().Element(ComposeContent); page.Footer().AlignCenter().PageNumber("Page {number}"); }); } @@ -57,7 +57,7 @@ namespace QuestPDF.ReportSample.Layouts foreach (var field in Model.HeaderFields) { - grid.Element().Stack(row => + grid.Item().Stack(row => { row.Item().AlignLeft().Text(field.Label, Typography.Normal.SemiBold()); row.Item().Text(field.Value, Typography.Normal); diff --git a/QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs b/QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs index 47692db..17255a5 100644 --- a/QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs +++ b/QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs @@ -16,21 +16,21 @@ namespace QuestPDF.ReportSample.Layouts public void Compose(IContainer container) { container - .Decoration(section => + .Decoration(decoration => { - section + decoration .Header() .PaddingBottom(5) .Text("Table of contents", Typography.Headline); - section.Content().Stack(stack => + decoration.Content().Stack(stack => { stack.Spacing(5); for (var i = 0; i < Sections.Count; i++) - stack.Item(c => DrawLink(c, i+1, Sections[i].Title)); + stack.Item().Element(c => DrawLink(c, i+1, Sections[i].Title)); - stack.Item(c => DrawLink(c, Sections.Count+1, "Photos")); + stack.Item().Element(c => DrawLink(c, Sections.Count+1, "Photos")); }); }); } diff --git a/QuestPDF.UnitTests/EnsureSpaceTests.cs b/QuestPDF.UnitTests/EnsureSpaceTests.cs index 2dda190..67ac3cb 100644 --- a/QuestPDF.UnitTests/EnsureSpaceTests.cs +++ b/QuestPDF.UnitTests/EnsureSpaceTests.cs @@ -7,10 +7,10 @@ using QuestPDF.UnitTests.TestEngine; namespace QuestPDF.UnitTests { [TestFixture] - public class WrapWhenLittleSpaceTests + public class EnsureSpaceTests { [Test] - public void Measure_ReturnsWrap_WhenNotEnoughSpace() + public void Measure_ReturnsWrap_WhenChildReturnsWrap() { TestPlan .For(x => new EnsureSpace @@ -19,11 +19,54 @@ namespace QuestPDF.UnitTests MinHeight = 200 }) .MeasureElement(new Size(400, 100)) + .ExpectChildMeasure(new Size(400, 100), new Wrap()) .CheckMeasureResult(new Wrap()); } [Test] - public void Measure_Continues_WhenEnoughSpace() + public void Measure_ReturnsWrap_WhenChildReturnsPartialRender_AndNotEnoughSpace() + { + TestPlan + .For(x => new EnsureSpace + { + Child = x.CreateChild(), + MinHeight = 200 + }) + .MeasureElement(new Size(400, 100)) + .ExpectChildMeasure(new Size(400, 100), new PartialRender(300, 50)) + .CheckMeasureResult(new Wrap()); + } + + [Test] + public void Measure_ReturnsPartialRender_WhenChildReturnsPartialRender_AndEnoughSpace() + { + TestPlan + .For(x => new EnsureSpace + { + Child = x.CreateChild(), + MinHeight = 200 + }) + .MeasureElement(new Size(400, 300)) + .ExpectChildMeasure(new Size(400, 300), new PartialRender(300, 250)) + .CheckMeasureResult(new PartialRender(300, 250)); + } + + [Test] + public void Measure_ReturnsFullRender_WhenChildReturnsFullRender_AndNotEnoughSpace() + { + TestPlan + .For(x => new EnsureSpace + { + Child = x.CreateChild(), + MinHeight = 200 + }) + .MeasureElement(new Size(400, 100)) + .ExpectChildMeasure(new Size(400, 100), new FullRender(300, 50)) + .CheckMeasureResult(new FullRender(300, 50)); + } + + [Test] + public void Measure_ReturnsFullRender_WhenChildReturnsFullRender_AndEnoughSpace() { TestPlan .For(x => new EnsureSpace diff --git a/QuestPDF.UnitTests/GridTests.cs b/QuestPDF.UnitTests/GridTests.cs index 5153e39..1cbcfde 100644 --- a/QuestPDF.UnitTests/GridTests.cs +++ b/QuestPDF.UnitTests/GridTests.cs @@ -6,7 +6,7 @@ using QuestPDF.UnitTests.TestEngine; namespace QuestPDF.UnitTests { - [TestFixture] + /*[TestFixture] public class GridTests { #region Alignment @@ -29,11 +29,11 @@ namespace QuestPDF.UnitTests { grid.AlignLeft(); - grid.Element(6).Element(childA); - grid.Element(4).Element(childB); - grid.Element(4).Element(childC); - grid.Element(2).Element(childD); - grid.Element(8).Element(childE); + grid.Item(6).Element(childA); + grid.Item(4).Element(childB); + grid.Item(4).Element(childC); + grid.Item(2).Element(childD); + grid.Item(8).Element(childE); }); // assert @@ -83,11 +83,11 @@ namespace QuestPDF.UnitTests { grid.AlignCenter(); - grid.Element(6).Element(childA); - grid.Element(4).Element(childB); - grid.Element(4).Element(childC); - grid.Element(2).Element(childD); - grid.Element(8).Element(childE); + grid.Item(6).Element(childA); + grid.Item(4).Element(childB); + grid.Item(4).Element(childC); + grid.Item(2).Element(childD); + grid.Item(8).Element(childE); }); // assert @@ -140,11 +140,11 @@ namespace QuestPDF.UnitTests { grid.AlignRight(); - grid.Element(6).Element(childA); - grid.Element(4).Element(childB); - grid.Element(4).Element(childC); - grid.Element(2).Element(childD); - grid.Element(8).Element(childE); + grid.Item(6).Element(childA); + grid.Item(4).Element(childB); + grid.Item(4).Element(childC); + grid.Item(2).Element(childD); + grid.Item(8).Element(childE); }); // assert @@ -177,5 +177,5 @@ namespace QuestPDF.UnitTests } #endregion - } + }*/ } \ No newline at end of file diff --git a/QuestPDF.UnitTests/RowTests.cs b/QuestPDF.UnitTests/RowTests.cs index 0e7af83..c49c84d 100644 --- a/QuestPDF.UnitTests/RowTests.cs +++ b/QuestPDF.UnitTests/RowTests.cs @@ -1,8 +1,7 @@ -using System.Collections.Generic; -using FluentAssertions; -using NUnit.Framework; +using NUnit.Framework; +using QuestPDF.Drawing.SpacePlan; using QuestPDF.Elements; -using QuestPDF.Fluent; +using QuestPDF.Infrastructure; using QuestPDF.UnitTests.TestEngine; namespace QuestPDF.UnitTests @@ -10,103 +9,108 @@ namespace QuestPDF.UnitTests [TestFixture] public class RowTests { - #region Spacing + #region Measure [Test] - public void Fluent_WithoutSpacing() + public void Measure_ReturnsWrap_WhenLeftChildReturnsWrap() { - // arrange - var structure = new Container(); - - var childA = TestPlan.CreateUniqueElement(); - var childB = TestPlan.CreateUniqueElement(); - var childC = TestPlan.CreateUniqueElement(); - - // act - structure - .Row(stack => + TestPlan + .For(x => new SimpleRow { - stack.Spacing(0); - - stack.ConstantColumn(100).Element(childA); - stack.RelativeColumn(2).Element(childB); - stack.RelativeColumn(3).Element(childC); - }); - - // assert - var expected = new Row() - { - Children = new List - { - new ConstantRowElement(100) - { - Child = childA - }, - new RelativeRowElement(2) - { - Child = childB - }, - new RelativeRowElement(3) - { - Child = childC - }, - } - }; - - structure.Child.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering().IncludingAllRuntimeProperties()); + Left = x.CreateChild("left"), + Right = x.CreateChild("right") + }) + .MeasureElement(new Size(400, 300)) + .ExpectChildMeasure("left", new Size(400, 300), new Wrap()) + .CheckMeasureResult(new Wrap()); } [Test] - public void Fluent_WithSpacing() + public void Measure_ReturnsWrap_WhenRightChildReturnsWrap() { - // arrange - var structure = new Container(); - - var childA = TestPlan.CreateUniqueElement(); - var childB = TestPlan.CreateUniqueElement(); - var childC = TestPlan.CreateUniqueElement(); - - // act - structure - .Row(stack => + TestPlan + .For(x => new SimpleRow { - stack.Spacing(25); - - stack.ConstantColumn(100).Element(childA); - stack.RelativeColumn(2).Element(childB); - stack.RelativeColumn(3).Element(childC); - }); - - // assert - var expected = new Padding - { - Right = -25, - Child = new Row() + Left = x.CreateChild("left"), + Right = x.CreateChild("right") + }) + .MeasureElement(new Size(400, 300)) + .ExpectChildMeasure("left", new Size(400, 300), new FullRender(250, 150)) + .ExpectChildMeasure("right", new Size(150, 300), new Wrap()) + .CheckMeasureResult(new Wrap()); + } + + [Test] + public void Measure_ReturnsPartialRender_WhenLeftChildReturnsPartialRender() + { + TestPlan + .For(x => new SimpleRow { - Children = new List - { - new ConstantRowElement(100) - { - Child = childA - }, - new ConstantRowElement(25), - new RelativeRowElement(2) - { - Child = childB - }, - new ConstantRowElement(25), - new RelativeRowElement(3) - { - Child = childC - }, - new ConstantRowElement(25) - } - } - }; - - structure.Child.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering().IncludingAllRuntimeProperties()); + Left = x.CreateChild("left"), + Right = x.CreateChild("right") + }) + .MeasureElement(new Size(400, 300)) + .ExpectChildMeasure("left", new Size(400, 300), new PartialRender(250, 150)) + .ExpectChildMeasure("right", new Size(150, 300), new FullRender(100, 100)) + .CheckMeasureResult(new PartialRender(350, 150)); + } + + [Test] + public void Measure_ReturnsPartialRender_WhenRightChildReturnsPartialRender() + { + TestPlan + .For(x => new SimpleRow + { + Left = x.CreateChild("left"), + Right = x.CreateChild("right") + }) + .MeasureElement(new Size(400, 300)) + .ExpectChildMeasure("left", new Size(400, 300), new FullRender(250, 150)) + .ExpectChildMeasure("right", new Size(150, 300), new PartialRender(100, 100)) + .CheckMeasureResult(new PartialRender(350, 150)); + } + + [Test] + public void Measure_ReturnsFullRender_WhenBothChildrenReturnFullRender() + { + TestPlan + .For(x => new SimpleRow + { + Left = x.CreateChild("left"), + Right = x.CreateChild("right") + }) + .MeasureElement(new Size(400, 300)) + .ExpectChildMeasure("left", new Size(400, 300), new FullRender(200, 150)) + .ExpectChildMeasure("right", new Size(200, 300), new FullRender(100, 100)) + .CheckMeasureResult(new FullRender(300, 150)); } #endregion + + #region Draw + + [Test] + public void Draw() + { + TestPlan + .For(x => new SimpleRow + { + Left = x.CreateChild("left"), + Right = x.CreateChild("right") + }) + .DrawElement(new Size(400, 300)) + .ExpectChildMeasure("left", new Size(400, 300), new FullRender(250, 150)) + .ExpectChildDraw("left", new Size(250, 300)) + .ExpectCanvasTranslate(250, 0) + .ExpectChildDraw("right", new Size(150, 300)) + .ExpectCanvasTranslate(-250, 0) + .CheckDrawResult(); + } + + #endregion + + // TODO: add tests for the spacing property + // TODO: add tests for the tree builder method + // TODO: add tests for relative column } } \ No newline at end of file diff --git a/QuestPDF.UnitTests/StackTests.cs b/QuestPDF.UnitTests/StackTests.cs index 0617fc7..134810b 100644 --- a/QuestPDF.UnitTests/StackTests.cs +++ b/QuestPDF.UnitTests/StackTests.cs @@ -1,9 +1,6 @@ -using System.Collections.Generic; -using FluentAssertions; -using NUnit.Framework; +using NUnit.Framework; using QuestPDF.Drawing.SpacePlan; using QuestPDF.Elements; -using QuestPDF.Fluent; using QuestPDF.Infrastructure; using QuestPDF.UnitTests.TestEngine; @@ -13,278 +10,226 @@ namespace QuestPDF.UnitTests public class StackTests { #region Measure - + [Test] - public void Measure_NoChildren_Empty() + public void Measure_ReturnsWrap_WhenFirstChildWraps() { TestPlan - .For(x => new Stack()) - .MeasureElement(new Size(500, 1000)) - .CheckMeasureResult(new FullRender(Size.Zero)); - } - - [Test] - public void Measure_ReturnsWrap_WhenPageable_AndAnyChildReturnsWrap() - { - TestPlan - .For(x => new Stack + .For(x => new SimpleStack { - Children = new [] - { - x.CreateChild("a"), - x.CreateChild("b"), - x.CreateChild("c"), - x.CreateChild("d") - } + First = x.CreateChild("first"), + Second = x.CreateChild("second") }) - .MeasureElement(new Size(500, 1000)) - .ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new FullRender(500, 200)) - .ExpectChildMeasure("b", expectedInput: new Size(500, 800), returns: new FullRender(500, 300)) - .ExpectChildMeasure("c", expectedInput: new Size(500, 500), returns: new Wrap()) - .CheckMeasureResult(new PartialRender(500, 500)); - } - - [Test] - public void Measure_ReturnsWrap_WhenPageable_AndFirstChildReturnsWrap() - { - TestPlan - .For(x => new Stack - { - Children = new [] - { - x.CreateChild("a"), - x.CreateChild("b"), - x.CreateChild("c") - } - }) - .MeasureElement(new Size(500, 1000)) - .ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new Wrap()) + .MeasureElement(new Size(400, 300)) + .ExpectChildMeasure("first", new Size(400, 300), new Wrap()) .CheckMeasureResult(new Wrap()); } - #endregion - - #region Combined - [Test] - public void WhenPageable_AndChildReturnsWrap() + public void Measure_ReturnsPartialRender_WhenFirstChildReturnsPartialRender() { TestPlan - .For(x => new Stack + .For(x => new SimpleStack { - Children = new[] - { - x.CreateChild("a"), - x.CreateChild("b"), - x.CreateChild("c") - } + First = x.CreateChild("first"), + Second = x.CreateChild("second") }) - - // page 1: measure - .MeasureElement(new Size(500, 1000)) - .ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new FullRender(400, 200)) - .ExpectChildMeasure("b", expectedInput: new Size(500, 800), returns: new Wrap()) - .CheckMeasureResult(new PartialRender(500, 200)) - - // page 1: draw - .DrawElement(new Size(500, 1000)) - - .ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new FullRender(400, 200)) - .ExpectCanvasTranslate(0, 0) - .ExpectChildDraw("a", new Size(500, 200)) - .ExpectCanvasTranslate(0, 0) - - .ExpectChildMeasure("b", expectedInput: new Size(500, 800), returns: new Wrap()) - - .CheckDrawResult() - - // // page 2: measure - .MeasureElement(new Size(500, 900)) - .ExpectChildMeasure("b", expectedInput: new Size(500, 900), returns: new FullRender(300, 200)) - .ExpectChildMeasure("c", expectedInput: new Size(500, 700), returns: new FullRender(300, 300)) - .CheckMeasureResult(new FullRender(500, 500)) - - // page 2: draw - .DrawElement(new Size(500, 900)) - - .ExpectChildMeasure("b", expectedInput: new Size(500, 900), returns: new FullRender(300, 200)) - .ExpectCanvasTranslate(0, 0) - .ExpectChildDraw("b", new Size(500, 200)) - .ExpectCanvasTranslate(0, 0) - - .ExpectChildMeasure("c", expectedInput: new Size(500, 700), returns: new FullRender(300, 300)) - .ExpectCanvasTranslate(0, 200) - .ExpectChildDraw("c", new Size(500, 300)) - .ExpectCanvasTranslate(0, -200) - - .CheckDrawResult(); + .MeasureElement(new Size(400, 300)) + .ExpectChildMeasure("first", new Size(400, 300), new PartialRender(300, 200)) + .CheckMeasureResult(new PartialRender(300, 200)); } - + [Test] - public void WhenPageable_AndChildReturnsPartialRender() + public void Measure_ReturnsPartialRender_WhenSecondChildWraps() { TestPlan - .For(x => new Stack + .For(x => new SimpleStack { - Children = new[] - { - x.CreateChild("a"), - x.CreateChild("b"), - x.CreateChild("c") - } + First = x.CreateChild("first"), + Second = x.CreateChild("second") }) - - // page 1: measure - .MeasureElement(new Size(500, 1000)) - .ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new FullRender(400, 200)) - .ExpectChildMeasure("b", expectedInput: new Size(500, 800), returns: new PartialRender(300, 300)) - .CheckMeasureResult(new PartialRender(500, 500)) - - // page 1: draw - .DrawElement(new Size(500, 1000)) - - .ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new FullRender(400, 200)) - .ExpectCanvasTranslate(0, 0) - .ExpectChildDraw("a", new Size(500, 200)) - .ExpectCanvasTranslate(0, 0) - - .ExpectChildMeasure("b", expectedInput: new Size(500, 800), returns: new PartialRender(300, 300)) - .ExpectCanvasTranslate(0, 200) - .ExpectChildDraw("b", new Size(500, 300)) - .ExpectCanvasTranslate(0, -200) - - .CheckDrawResult() - - // page 2: measure - .MeasureElement(new Size(500, 900)) - .ExpectChildMeasure("b", expectedInput: new Size(500, 900), returns: new FullRender(300, 200)) - .ExpectChildMeasure("c", expectedInput: new Size(500, 700), returns: new FullRender(300, 300)) - .CheckMeasureResult(new FullRender(500, 500)) - - // page 2: draw - .DrawElement(new Size(500, 900)) - - .ExpectChildMeasure("b", expectedInput: new Size(500, 900), returns: new FullRender(300, 200)) - .ExpectCanvasTranslate(0, 0) - .ExpectChildDraw("b", new Size(500, 200)) - .ExpectCanvasTranslate(0, 0) - - .ExpectChildMeasure("c", expectedInput: new Size(500, 700), returns: new FullRender(300, 300)) - .ExpectCanvasTranslate(0, 200) - .ExpectChildDraw("c", new Size(500, 300)) - .ExpectCanvasTranslate(0, -200) - + .MeasureElement(new Size(400, 300)) + .ExpectChildMeasure("first", new Size(400, 300), new FullRender(200, 100)) + .ExpectChildMeasure("second", new Size(400, 200), new Wrap()) + .CheckMeasureResult(new PartialRender(200, 100)); + } + + [Test] + public void Measure_ReturnsPartialRender_WhenSecondChildReturnsPartialRender() + { + TestPlan + .For(x => new SimpleStack + { + First = x.CreateChild("first"), + Second = x.CreateChild("second") + }) + .MeasureElement(new Size(400, 300)) + .ExpectChildMeasure("first", new Size(400, 300), new FullRender(200, 100)) + .ExpectChildMeasure("second", new Size(400, 200), new PartialRender(300, 150)) + .CheckMeasureResult(new PartialRender(300, 250)); + } + + [Test] + public void Measure_ReturnsFullRender_WhenSecondChildReturnsFullRender() + { + TestPlan + .For(x => new SimpleStack + { + First = x.CreateChild("first"), + Second = x.CreateChild("second") + }) + .MeasureElement(new Size(400, 300)) + .ExpectChildMeasure("first", new Size(400, 300), new FullRender(200, 100)) + .ExpectChildMeasure("second", new Size(400, 200), new FullRender(100, 50)) + .CheckMeasureResult(new FullRender(200, 150)); + } + + [Test] + public void Measure_UsesEmpty_WhenFirstChildIsRendered() + { + TestPlan + .For(x => new SimpleStack + { + First = x.CreateChild("first"), + Second = x.CreateChild("second"), + + IsFirstRendered = true + }) + .MeasureElement(new Size(400, 300)) + .ExpectChildMeasure("second", new Size(400, 300), new FullRender(200, 300)) + .CheckMeasureResult(new FullRender(200, 300)); + } + + #endregion + + #region Draw + + [Test] + public void Draw_WhenFirstChildWraps() + { + TestPlan + .For(x => new SimpleStack + { + First = x.CreateChild("first"), + Second = x.CreateChild("second") + }) + .DrawElement(new Size(400, 300)) + .ExpectChildMeasure("first", new Size(400, 300), new Wrap()) .CheckDrawResult(); } - #endregion - - #region Spacing - [Test] - public void Fluent_WithoutSpacing() + public void Draw_WhenFirstChildPartiallyRenders() { - // arrange - var structure = new Container(); - - var childA = TestPlan.CreateUniqueElement(); - var childB = TestPlan.CreateUniqueElement(); - var childC = TestPlan.CreateUniqueElement(); - - // act - structure - .Stack(stack => + TestPlan + .For(x => new SimpleStack { - stack.Spacing(0); - - stack.Item().Element(childA); - stack.Item().Element(childB); - stack.Item().Element(childC); - }); - - // assert - var expected = new Stack - { - Children = new List - { - new Container - { - Child = childA - }, - new Container - { - Child = childB - }, - new Container - { - Child = childC - } - } - }; - - structure.Child.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering().IncludingAllRuntimeProperties()); + First = x.CreateChild("first"), + Second = x.CreateChild("second") + }) + .DrawElement(new Size(400, 300)) + .ExpectChildMeasure("first", new Size(400, 300), new PartialRender(200, 100)) + .ExpectChildDraw("first", new Size(400, 100)) + .CheckDrawResult(); } [Test] - public void Fluent_WithSpacing() + public void Draw_WhenFirstChildFullyRenders_AndSecondChildWraps() { - // arrange - var structure = new Container(); - - var childA = TestPlan.CreateUniqueElement(); - var childB = TestPlan.CreateUniqueElement(); - var childC = TestPlan.CreateUniqueElement(); - - // act - structure - .Stack(stack => + TestPlan + .For(x => new SimpleStack { - stack.Spacing(100); - - stack.Item().Element(childA); - stack.Item().Element(childB); - stack.Item().Element(childC); - }); - - // assert - var expected = new Padding - { - Bottom = -100, - Child = new Stack + First = x.CreateChild("first"), + Second = x.CreateChild("second") + }) + .DrawElement(new Size(400, 300)) + .ExpectChildMeasure("first", new Size(400, 300), new FullRender(200, 100)) + .ExpectChildDraw("first", new Size(400, 100)) + .ExpectChildMeasure("second", new Size(400, 200), new Wrap()) + .CheckDrawResult(); + } + + [Test] + public void Draw_WhenFirstChildFullyRenders_AndSecondChildPartiallyRenders() + { + TestPlan + .For(x => new SimpleStack { - Children = new List - { - new Padding - { - Bottom = 100, - Child = new Container - { - Child = childA - } - }, - new Padding - { - Bottom = 100, - Child = new Container - { - Child = childB - } - }, - new Padding - { - Bottom = 100, - Child = new Container - { - Child = childC - } - }, - } - } - }; - - structure.Child.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering().IncludingAllRuntimeProperties()); + First = x.CreateChild("first"), + Second = x.CreateChild("second") + }) + .DrawElement(new Size(400, 300)) + .ExpectChildMeasure("first", new Size(400, 300), new FullRender(200, 100)) + .ExpectChildDraw("first", new Size(400, 100)) + .ExpectChildMeasure("second", new Size(400, 200), new PartialRender(250, 150)) + .ExpectCanvasTranslate(0, 100) + .ExpectChildDraw("second", new Size(400, 150)) + .ExpectCanvasTranslate(0, -100) + .CheckDrawResult(); + } + + [Test] + public void Draw_WhenFirstChildFullyRenders_AndSecondChildFullyRenders() + { + TestPlan + .For(x => new SimpleStack + { + First = x.CreateChild("first"), + Second = x.CreateChild("second") + }) + .DrawElement(new Size(400, 300)) + .ExpectChildMeasure("first", new Size(400, 300), new FullRender(200, 100)) + .ExpectChildDraw("first", new Size(400, 100)) + .ExpectChildMeasure("second", new Size(400, 200), new FullRender(250, 150)) + .ExpectCanvasTranslate(0, 100) + .ExpectChildDraw("second", new Size(400, 150)) + .ExpectCanvasTranslate(0, -100) + .CheckDrawResult(); + } + + [Test] + public void Draw_UsesEmpty_WhenFirstChildIsRendered() + { + TestPlan + .For(x => new SimpleStack + { + First = x.CreateChild("first"), + Second = x.CreateChild("second"), + + IsFirstRendered = true + }) + .DrawElement(new Size(400, 300)) + .ExpectChildMeasure("second", new Size(400, 300), new PartialRender(200, 300)) + .ExpectCanvasTranslate(0, 0) + .ExpectChildDraw("second", new Size(400, 300)) + .ExpectCanvasTranslate(0, 0) + .CheckState(x => x.IsFirstRendered) + .CheckDrawResult(); + } + + [Test] + public void Draw_TogglesFirstRenderedFlag_WhenSecondFullyRenders() + { + TestPlan + .For(x => new SimpleStack + { + First = x.CreateChild("first"), + Second = x.CreateChild("second"), + + IsFirstRendered = true + }) + .DrawElement(new Size(400, 300)) + .ExpectChildMeasure("second", new Size(400, 300), new FullRender(200, 300)) + .ExpectCanvasTranslate(0, 0) + .ExpectChildDraw("second", new Size(400, 300)) + .ExpectCanvasTranslate(0, 0) + .CheckDrawResult() + .CheckState(x => !x.IsFirstRendered); } #endregion + + // TODO: add tests for the spacing property + // TODO: add tests for the tree builder method } } \ No newline at end of file diff --git a/QuestPDF.UnitTests/TestEngine/TestPlan.cs b/QuestPDF.UnitTests/TestEngine/TestPlan.cs index 79cf5a4..b9bda9f 100644 --- a/QuestPDF.UnitTests/TestEngine/TestPlan.cs +++ b/QuestPDF.UnitTests/TestEngine/TestPlan.cs @@ -49,9 +49,9 @@ namespace QuestPDF.UnitTests.TestEngine TranslateFunc = position => { var expected = GetExpected(); - - Assert.AreEqual(expected.Position.X, position.X); - Assert.AreEqual(expected.Position.Y, position.Y); + + Assert.AreEqual(expected.Position.X, position.X, "Translate X"); + Assert.AreEqual(expected.Position.Y, position.Y, "Translate Y"); //position.Should().BeEquivalentTo(expected.Position); }, @@ -59,13 +59,13 @@ namespace QuestPDF.UnitTests.TestEngine { var expected = GetExpected(); - Assert.AreEqual(expected.Position.X, position.X); - Assert.AreEqual(expected.Position.Y, position.Y); + Assert.AreEqual(expected.Position.X, position.X, "Draw rectangle: X"); + Assert.AreEqual(expected.Position.Y, position.Y, "Draw rectangle: Y"); - Assert.AreEqual(expected.Size.Width, size.Width); - Assert.AreEqual(expected.Size.Height, size.Height); + Assert.AreEqual(expected.Size.Width, size.Width, "Draw rectangle: width"); + Assert.AreEqual(expected.Size.Height, size.Height, "Draw rectangle: height"); - Assert.AreEqual(expected.Color, color); + Assert.AreEqual(expected.Color, color, "Draw rectangle: color"); /*position.Should().BeEquivalentTo(expected.Position); size.Should().BeEquivalentTo(expected.Size); @@ -77,12 +77,12 @@ namespace QuestPDF.UnitTests.TestEngine Assert.AreEqual(expected.Text, text); - Assert.AreEqual(expected.Position.X, position.X); - Assert.AreEqual(expected.Position.Y, position.Y); + Assert.AreEqual(expected.Position.X, position.X, "Draw text: X"); + Assert.AreEqual(expected.Position.Y, position.Y, "Draw text: Y"); - Assert.AreEqual(expected.Style.Color, style.Color); - Assert.AreEqual(expected.Style.FontType, style.FontType); - Assert.AreEqual(expected.Style.Size, style.Size); + Assert.AreEqual(expected.Style.Color, style.Color, "Draw text: color"); + Assert.AreEqual(expected.Style.FontType, style.FontType, "Draw text: font"); + Assert.AreEqual(expected.Style.Size, style.Size, "Draw text: size"); /*text.Should().Be(expected.Text); position.Should().BeEquivalentTo(expected.Position); @@ -92,11 +92,11 @@ namespace QuestPDF.UnitTests.TestEngine { var expected = GetExpected(); - Assert.AreEqual(expected.Position.X, position.X); - Assert.AreEqual(expected.Position.Y, position.Y); + Assert.AreEqual(expected.Position.X, position.X, "Draw image: X"); + Assert.AreEqual(expected.Position.Y, position.Y, "Draw image: Y"); - Assert.AreEqual(expected.Size.Width, size.Width); - Assert.AreEqual(expected.Size.Height, size.Height); + Assert.AreEqual(expected.Size.Width, size.Width, "Draw image: width"); + Assert.AreEqual(expected.Size.Height, size.Height, "Draw image: height"); /*position.Should().BeEquivalentTo(expected.Position); size.Should().BeEquivalentTo(expected.Size);*/ @@ -117,8 +117,8 @@ namespace QuestPDF.UnitTests.TestEngine Assert.AreEqual(expected.ChildId, id); - Assert.AreEqual(expected.Input.Width, availableSpace.Width); - Assert.AreEqual(expected.Input.Height, availableSpace.Height); + Assert.AreEqual(expected.Input.Width, availableSpace.Width, $"Measure: width of child '{expected.ChildId}'"); + Assert.AreEqual(expected.Input.Height, availableSpace.Height, $"Measure: height of child '{expected.ChildId}'"); // id.Should().Be(expected.ChildId); // availableSpace.Should().Be(expected.Input); @@ -131,8 +131,8 @@ namespace QuestPDF.UnitTests.TestEngine Assert.AreEqual(expected.ChildId, id); - Assert.AreEqual(expected.Input.Width, availableSpace.Width); - Assert.AreEqual(expected.Input.Height, availableSpace.Height); + Assert.AreEqual(expected.Input.Width, availableSpace.Width, $"Draw: width of child '{expected.ChildId}'"); + Assert.AreEqual(expected.Input.Height, availableSpace.Height, $"Draw: width of child '{expected.ChildId}'"); /*id.Should().Be(expected.ChildId); availableSpace.Should().Be(expected.Input);*/ @@ -214,8 +214,8 @@ namespace QuestPDF.UnitTests.TestEngine if (expectedSize != null) { - Assert.AreEqual(expectedSize.Width, actualSize.Width); - Assert.AreEqual(expectedSize.Height, actualSize.Height); + Assert.AreEqual(expectedSize.Width, actualSize.Width, "Measure: width"); + Assert.AreEqual(expectedSize.Height, actualSize.Height, "Measure: height"); } return this; @@ -227,6 +227,19 @@ namespace QuestPDF.UnitTests.TestEngine return this; } + public TestPlan CheckState(Func condition) + { + Assert.IsTrue(condition(Element), "Checking condition"); + return this; + } + + public TestPlan CheckState(Func condition) where T : Element + { + Assert.IsTrue(Element is T); + Assert.IsTrue(condition(Element as T), "Checking condition"); + return this; + } + public static Element CreateUniqueElement() { return new Text diff --git a/QuestPDF/Elements/Decoration.cs b/QuestPDF/Elements/Decoration.cs index 93b15c2..4488c98 100644 --- a/QuestPDF/Elements/Decoration.cs +++ b/QuestPDF/Elements/Decoration.cs @@ -1,8 +1,66 @@ +using System; +using QuestPDF.Drawing.SpacePlan; using QuestPDF.Fluent; using QuestPDF.Infrastructure; namespace QuestPDF.Elements { + internal enum DecorationType + { + Prepend, + Append + } + + internal class SimpleDecoration : Element + { + public Element DecorationElement { get; set; } = Empty.Instance; + public Element ContentElement { get; set; } = Empty.Instance; + public DecorationType Type { get; set; } + + internal override ISpacePlan Measure(Size availableSpace) + { + var decorationMeasure = DecorationElement?.Measure(availableSpace); + + if (decorationMeasure is Wrap || decorationMeasure is PartialRender) + return new Wrap(); + + var decorationSize = decorationMeasure as Size ?? Size.Zero; + var contentMeasure = ContentElement?.Measure(new Size(availableSpace.Width, availableSpace.Height - decorationSize.Height)) ?? new FullRender(Size.Zero); + + if (contentMeasure is Wrap) + return new Wrap(); + + var contentSize = contentMeasure as Size ?? Size.Zero; + var resultSize = new Size(availableSpace.Width, decorationSize.Height + contentSize.Height); + + if (contentSize is PartialRender) + return new PartialRender(resultSize); + + if (contentSize is FullRender) + return new FullRender(resultSize); + + throw new NotSupportedException(); + } + + internal override void Draw(ICanvas canvas, Size availableSpace) + { + var decorationSize = DecorationElement?.Measure(availableSpace) as Size ?? Size.Zero; + var contentSize = new Size(availableSpace.Width, availableSpace.Height - decorationSize.Height); + + var translateHeight = Type == DecorationType.Prepend ? decorationSize.Height : contentSize.Height; + Action drawDecoration = () => DecorationElement?.Draw(canvas, new Size(availableSpace.Width, decorationSize.Height)); + Action drawContent = () => ContentElement?.Draw(canvas, 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; diff --git a/QuestPDF/Elements/Page.cs b/QuestPDF/Elements/Page.cs index 9ebe953..7c0bb95 100644 --- a/QuestPDF/Elements/Page.cs +++ b/QuestPDF/Elements/Page.cs @@ -11,11 +11,11 @@ namespace QuestPDF.Elements public void Compose(IContainer container) { - container.Decoration(section => + container.Decoration(decoration => { - section.Header().Element(Header); - section.Content().Extend().Element(Content); - section.Footer().Element(Footer); + decoration.Header().Element(Header); + decoration.Content().Extend().Element(Content); + decoration.Footer().Element(Footer); }); } } diff --git a/QuestPDF/Elements/Row.cs b/QuestPDF/Elements/Row.cs index e0abc58..118f7c6 100644 --- a/QuestPDF/Elements/Row.cs +++ b/QuestPDF/Elements/Row.cs @@ -1,6 +1,8 @@ +using System; using System.Collections.Generic; using System.Linq; using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Fluent; using QuestPDF.Infrastructure; namespace QuestPDF.Elements @@ -31,78 +33,129 @@ namespace QuestPDF.Elements } } - internal class Row : Element + internal class SimpleRow : Element { - public List Children { get; set; } = new List(); - - float? ConstantWidthSum { get; set; } - float? RelativeWidthSum { get; set; } + internal Element Left { get; set; } + internal Element Right { get; set; } internal override ISpacePlan Measure(Size availableSpace) { - var sizes = Children - .Select(x => - { - var space = GetTargetSize(x, availableSpace); - return x.Child.Measure(space); - }) - .ToList(); + var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height)) as Size; - if (sizes.Any(x => x is Wrap)) + if (leftMeasurement == null) return new Wrap(); - - var height = sizes - .Where(x => x is Size) - .Cast() - .DefaultIfEmpty(Size.Zero) - .Max(x => x.Height); - if (sizes.All(x => x is FullRender)) - return new FullRender(availableSpace.Width, height); - - if (sizes.Any(x => x is PartialRender)) - return new PartialRender(availableSpace.Width, height); + var rightMeasurement = Right.Measure(new Size(availableSpace.Width - leftMeasurement.Width, availableSpace.Height)) as Size; - return new FullRender(Size.Zero); + if (rightMeasurement == null) + return new Wrap(); + + var totalWidth = leftMeasurement.Width + rightMeasurement.Width; + var totalHeight = Math.Max(leftMeasurement.Height, rightMeasurement.Height); + + var targetSize = new Size(totalWidth, totalHeight); + + if (leftMeasurement is PartialRender || rightMeasurement is PartialRender) + return new PartialRender(targetSize); + + return new FullRender(targetSize); } internal override void Draw(ICanvas canvas, Size availableSpace) { - var targetSpace = Measure(availableSpace) as Size; - - if (targetSpace == null) - return; + var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height)); + var leftWidth = (leftMeasurement as Size)?.Width ?? 0; - var offset = 0f; + Left.Draw(canvas, new Size(leftWidth, availableSpace.Height)); + + canvas.Translate(new Position(leftWidth, 0)); + Right.Draw(canvas, new Size(availableSpace.Width - leftWidth, availableSpace.Height)); + canvas.Translate(new Position(-leftWidth, 0)); + } + } + + internal class Row : Element + { + public ICollection Children { get; internal set; } = new List(); + public float Spacing { get; set; } = 0; + + public Element Compose(float availableWidth) + { + var elements = ReduceRows(AddSpacing(Children)); + return BuildTree(elements.ToArray()); - foreach (var column in Children) - { - var space = GetTargetSize(column, targetSpace); + ICollection ReduceRows(ICollection elements) + { + var constantWidth = elements + .Where(x => x is ConstantRowElement) + .Cast() + .Sum(x => x.Width); + + var relativeWidth = elements + .Where(x => x is RelativeRowElement) + .Cast() + .Sum(x => x.Width); + + var widthPerRelativeUnit = (availableWidth - constantWidth) / relativeWidth; - canvas.Translate(new Position(offset, 0)); - column.Child.Draw(canvas, space); - canvas.Translate(new Position(-offset, 0)); + return elements + .Select(x => + { + if (x is RelativeRowElement r) + return new ConstantRowElement(r.Width * widthPerRelativeUnit) + { + Child = x.Child + }; + + return x; + }) + .Select(x => new Constrained + { + MinWidth = x.Width, + MaxWidth = x.Width, + Child = x.Child + }) + .Cast() + .ToList(); + } + + ICollection AddSpacing(ICollection elements) + { + if (Spacing < Size.Epsilon) + return elements; + + return elements + .SelectMany(x => new[] { new ConstantRowElement(Spacing), x }) + .Skip(1) + .ToList(); + } - offset += space.Width; + Element BuildTree(Span elements) + { + if (elements.IsEmpty) + return Empty.Instance; + + if (elements.Length == 1) + return elements[0]; + + var half = elements.Length / 2; + + return new SimpleRow + { + Left = BuildTree(elements.Slice(0, half)), + Right = BuildTree(elements.Slice(half)) + }; } } - private Size GetTargetSize(RowElement rowElement, Size availableSpace) + internal override ISpacePlan Measure(Size availableSpace) { - if (rowElement is ConstantRowElement) - return new Size(rowElement.Width, availableSpace.Height); + return Compose(availableSpace.Width).Measure(availableSpace); + } - ConstantWidthSum ??= Children - .Where(x => x is ConstantRowElement) - .Cast() - .Sum(x => x.Width); - - RelativeWidthSum ??= Children - .Where(x => x is RelativeRowElement) - .Cast() - .Sum(x => x.Width); - - return new Size((availableSpace.Width - ConstantWidthSum.Value) * rowElement.Width / RelativeWidthSum.Value, availableSpace.Height); + internal override void Draw(ICanvas canvas, Size availableSpace) + { + Compose(availableSpace.Width).Draw(canvas, availableSpace); } } } \ No newline at end of file diff --git a/QuestPDF/Elements/SimpleDecoration.cs b/QuestPDF/Elements/SimpleDecoration.cs deleted file mode 100644 index 496d3f7..0000000 --- a/QuestPDF/Elements/SimpleDecoration.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using QuestPDF.Drawing.SpacePlan; -using QuestPDF.Infrastructure; - -namespace QuestPDF.Elements -{ - internal enum DecorationType - { - Prepend, - Append - } - - internal class SimpleDecoration : Element - { - public Element DecorationElement { get; set; } = Empty.Instance; - public Element ContentElement { get; set; } = Empty.Instance; - public DecorationType Type { get; set; } - - internal override ISpacePlan Measure(Size availableSpace) - { - var decorationMeasure = DecorationElement?.Measure(availableSpace); - - if (decorationMeasure is Wrap || decorationMeasure is PartialRender) - return new Wrap(); - - var decorationSize = decorationMeasure as Size ?? Size.Zero; - var contentMeasure = ContentElement?.Measure(new Size(availableSpace.Width, availableSpace.Height - decorationSize.Height)) ?? new FullRender(Size.Zero); - - if (contentMeasure is Wrap) - return new Wrap(); - - var contentSize = contentMeasure as Size ?? Size.Zero; - var resultSize = new Size(availableSpace.Width, decorationSize.Height + contentSize.Height); - - if (contentSize is PartialRender) - return new PartialRender(resultSize); - - if (contentSize is FullRender) - return new FullRender(resultSize); - - throw new NotSupportedException(); - } - - internal override void Draw(ICanvas canvas, Size availableSpace) - { - var decorationSize = DecorationElement?.Measure(availableSpace) as Size ?? Size.Zero; - var contentSize = new Size(availableSpace.Width, availableSpace.Height - decorationSize.Height); - - var translateHeight = Type == DecorationType.Prepend ? decorationSize.Height : contentSize.Height; - Action drawDecoration = () => DecorationElement?.Draw(canvas, new Size(availableSpace.Width, decorationSize.Height)); - Action drawContent = () => ContentElement?.Draw(canvas, 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)); - } - } -} \ No newline at end of file diff --git a/QuestPDF/Elements/SimpleRow.cs b/QuestPDF/Elements/SimpleRow.cs deleted file mode 100644 index 4f53b41..0000000 --- a/QuestPDF/Elements/SimpleRow.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using QuestPDF.Drawing.SpacePlan; -using QuestPDF.Infrastructure; - -namespace QuestPDF.Elements -{ - internal class SimpleRow : Element - { - internal Element Left { get; set; } - internal Element Right { get; set; } - - internal override ISpacePlan Measure(Size availableSpace) - { - var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height)) as Size; - - if (leftMeasurement == null) - return new Wrap(); - - var rightMeasurement = Right.Measure(new Size(availableSpace.Width - leftMeasurement.Width, availableSpace.Height)) as Size; - - if (rightMeasurement == null) - return new Wrap(); - - var totalWidth = leftMeasurement.Width + rightMeasurement.Width; - var totalHeight = Math.Max(leftMeasurement.Height, rightMeasurement.Height); - - var targetSize = new Size(totalWidth, totalHeight); - - if (leftMeasurement is PartialRender || rightMeasurement is PartialRender) - return new PartialRender(targetSize); - - return new FullRender(targetSize); - } - - internal override void Draw(ICanvas canvas, Size availableSpace) - { - var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height)); - var leftWidth = (leftMeasurement as Size)?.Width ?? 0; - - Left.Draw(canvas, new Size(leftWidth, availableSpace.Height)); - - canvas.Translate(new Position(leftWidth, 0)); - Right.Draw(canvas, new Size(availableSpace.Width - leftWidth, availableSpace.Height)); - canvas.Translate(new Position(-leftWidth, 0)); - } - } -} \ No newline at end of file diff --git a/QuestPDF/Elements/SimpleStack.cs b/QuestPDF/Elements/SimpleStack.cs deleted file mode 100644 index e223a65..0000000 --- a/QuestPDF/Elements/SimpleStack.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using QuestPDF.Drawing.SpacePlan; -using QuestPDF.Infrastructure; - -namespace QuestPDF.Elements -{ - internal class SimpleStack : Element - { - internal Element Current { get; set; } = Empty.Instance; - internal Element Next { get; set; } = Empty.Instance; - - private bool IsFirstRendered { get; set; } = false; - - internal override ISpacePlan Measure(Size availableSpace) - { - var firstElement = IsFirstRendered ? Empty.Instance : Current; - var firstSize = firstElement.Measure(availableSpace) as Size; - - if (firstSize == null) - return new Wrap(); - - if (firstSize is PartialRender partialRender) - return partialRender; - - var spaceForSecond = new Size(availableSpace.Width, availableSpace.Height - firstSize.Height); - - var secondSize = Next.Measure(spaceForSecond) as Size; - - if (secondSize == null) - return new PartialRender(firstSize); - - var totalWidth = Math.Max(firstSize.Width, secondSize.Width); - var totalHeight = firstSize.Height + secondSize.Height; - - var targetSize = new Size(totalWidth, totalHeight); - - if (secondSize is PartialRender) - return new PartialRender(targetSize); - - return new FullRender(targetSize); - } - - internal override void Draw(ICanvas canvas, Size availableSpace) - { - var firstElement = IsFirstRendered ? Empty.Instance : Current; - - var firstMeasurement = firstElement.Measure(availableSpace); - - if (firstMeasurement is FullRender) - IsFirstRendered = true; - - var firstSize = firstMeasurement as Size; - - if (firstSize != null) - firstElement.Draw(canvas, firstSize); - - var firstHeight = firstSize?.Height ?? 0; - - var spaceForSecond = new Size(availableSpace.Width, availableSpace.Height - firstHeight); - - var secondMeasurement = Next?.Measure(spaceForSecond); - - if (secondMeasurement is Wrap) - return; - - canvas.Translate(new Position(0, firstHeight)); - Next.Draw(canvas, secondMeasurement as Size); - canvas.Translate(new Position(0, -firstHeight)); - } - } -} \ No newline at end of file diff --git a/QuestPDF/Elements/SimpleStackCache.cs b/QuestPDF/Elements/SimpleStackCache.cs deleted file mode 100644 index b84d28c..0000000 --- a/QuestPDF/Elements/SimpleStackCache.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using QuestPDF.Drawing.SpacePlan; -using QuestPDF.Infrastructure; - -namespace QuestPDF.Elements -{ - internal class SimpleStackWithCache : SimpleStack - { - private ISpacePlan? Cache { get; set; } - private Size? CacheForSize { get; set; } - - internal override ISpacePlan Measure(Size availableSpace) - { - if (Cache != null && CacheForSize != null && CacheForSize.Equals(availableSpace)) - return Cache; - - CacheForSize = availableSpace; - Cache = base.Measure(availableSpace); - return Cache; - } - - internal override void Draw(ICanvas canvas, Size availableSpace) - { - base.Draw(canvas, availableSpace); - Cache = null; - CacheForSize = null; - } - } -} \ No newline at end of file diff --git a/QuestPDF/Elements/Stack.cs b/QuestPDF/Elements/Stack.cs index dbb2e39..3a8510f 100644 --- a/QuestPDF/Elements/Stack.cs +++ b/QuestPDF/Elements/Stack.cs @@ -1,81 +1,126 @@ +using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Fluent; using QuestPDF.Infrastructure; +using IComponent = QuestPDF.Infrastructure.IComponent; +using IContainer = QuestPDF.Infrastructure.IContainer; namespace QuestPDF.Elements { - internal class Stack : Element + internal class SimpleStack : Element { - public ICollection Children { get; internal set; } = new List(); - private Queue ChildrenQueue { get; set; } = new Queue(); - - private void Initialize() - { - if (ChildrenQueue.Count == 0) - ChildrenQueue = new Queue(Children.Where(x => x != null)); - } + internal Element First { get; set; } = Empty.Instance; + internal Element Second { get; set; } = Empty.Instance; + + internal bool IsFirstRendered { get; set; } = false; internal override ISpacePlan Measure(Size availableSpace) { - Initialize(); + var firstElement = IsFirstRendered ? Empty.Instance : First; + var firstSize = firstElement.Measure(availableSpace) as Size; + + if (firstSize == null) + return new Wrap(); - if(!ChildrenQueue.Any()) - return new FullRender(Size.Zero); + if (firstSize is PartialRender partialRender) + return partialRender; + + var spaceForSecond = new Size(availableSpace.Width, availableSpace.Height - firstSize.Height); + var secondSize = Second.Measure(spaceForSecond) as Size; - var heightOnCurrentPage = 0f; + if (secondSize == null) + return new PartialRender(firstSize); - foreach (var renderer in ChildrenQueue) - { - var space = renderer.Measure(new Size(availableSpace.Width, availableSpace.Height - heightOnCurrentPage)); + var totalWidth = Math.Max(firstSize.Width, secondSize.Width); + var totalHeight = firstSize.Height + secondSize.Height; + var targetSize = new Size(totalWidth, totalHeight); - if (space is Wrap) - { - if (heightOnCurrentPage < Size.Epsilon) - return new Wrap(); - - return new PartialRender(availableSpace.Width, heightOnCurrentPage); - } - - var size = space as Size; - heightOnCurrentPage += size.Height; - - if (space is PartialRender) - return new PartialRender(availableSpace.Width, heightOnCurrentPage); - } - - return new FullRender(availableSpace.Width, heightOnCurrentPage); + if (secondSize is PartialRender) + return new PartialRender(targetSize); + + return new FullRender(targetSize); } internal override void Draw(ICanvas canvas, Size availableSpace) { - Initialize(); + var firstElement = IsFirstRendered ? Empty.Instance : First; + + var firstMeasurement = firstElement.Measure(availableSpace); + + if (firstMeasurement is FullRender) + IsFirstRendered = true; + + var firstSize = firstMeasurement as Size; + + if (firstSize != null) + firstElement.Draw(canvas, new Size(availableSpace.Width, firstSize.Height)); + + if (firstMeasurement is Wrap || firstMeasurement is PartialRender) + return; + + var firstHeight = firstSize?.Height ?? 0; + var spaceForSecond = new Size(availableSpace.Width, availableSpace.Height - firstHeight); + var secondMeasurement = Second?.Measure(spaceForSecond) as Size; + + if (secondMeasurement == null) + return; + + canvas.Translate(new Position(0, firstHeight)); + Second.Draw(canvas, new Size(availableSpace.Width, secondMeasurement.Height)); + canvas.Translate(new Position(0, -firstHeight)); - var topOffset = 0f; + if (secondMeasurement is FullRender) + IsFirstRendered = false; + } + } + + internal class Stack : IComponent + { + public ICollection Children { get; internal set; } = new List(); + public float Spacing { get; set; } = 0; + + public void Compose(IContainer container) + { + var elements = AddSpacing(Spacing, Children); - while (ChildrenQueue.Any()) + container + .PaddingBottom(-Spacing) + .Element(BuildTree(elements.ToArray())); + } + + static ICollection AddSpacing(float spacing, ICollection elements) + { + if (spacing < Size.Epsilon) + return elements; + + return elements + .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 SimpleStack { - var child = ChildrenQueue.Peek(); - - var restSpace = new Size(availableSpace.Width, availableSpace.Height - topOffset); - var space = child.Measure(restSpace); - - if (space is Wrap) - break; - - var size = space as Size; - - canvas.Translate(new Position(0, topOffset)); - child.Draw(canvas, new Size(availableSpace.Width, size.Height)); - canvas.Translate(new Position(0, -topOffset)); - - topOffset += size.Height; - - if (space is PartialRender) - break; - - ChildrenQueue.Dequeue(); - } + First = BuildTree(elements.Slice(0, half)), + Second = BuildTree(elements.Slice(half)) + }; } } } \ No newline at end of file diff --git a/QuestPDF/Elements/TreeRow.cs b/QuestPDF/Elements/TreeRow.cs deleted file mode 100644 index cc2ea1b..0000000 --- a/QuestPDF/Elements/TreeRow.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using QuestPDF.Drawing.SpacePlan; -using QuestPDF.Fluent; -using QuestPDF.Infrastructure; - -namespace QuestPDF.Elements -{ - internal class TreeRow : Element - { - public ICollection Children { get; internal set; } = new List(); - public float Spacing { get; set; } = 0; - - public Element Compose(float availableWidth) - { - var elements = ReduceRows(AddSpacing(Children)); - return BuildTree(elements.ToArray()); - - ICollection ReduceRows(ICollection elements) - { - var constantWidth = elements - .Where(x => x is ConstantRowElement) - .Cast() - .Sum(x => x.Width); - - var relativeWidth = elements - .Where(x => x is RelativeRowElement) - .Cast() - .Sum(x => x.Width); - - var widthPerRelativeUnit = (availableWidth - constantWidth) / relativeWidth; - - return elements - .Select(x => - { - if (x is RelativeRowElement r) - return new ConstantRowElement(r.Width * widthPerRelativeUnit) - { - Child = x.Child - }; - - return x; - }) - .Select(x => new Constrained - { - MinWidth = x.Width, - MaxWidth = x.Width, - Child = x.Child - }) - .Cast() - .ToList(); - } - - ICollection AddSpacing(ICollection elements) - { - if (Spacing < Size.Epsilon) - return elements; - - return elements - .SelectMany(x => new[] { new ConstantRowElement(Spacing), x }) - .Skip(1) - .ToList(); - } - - Element BuildTree(Span elements) - { - if (elements.IsEmpty) - return Empty.Instance; - - if (elements.Length == 1) - return elements[0]; - - var half = elements.Length / 2; - - return new SimpleRow - { - Left = BuildTree(elements.Slice(0, half)), - Right = BuildTree(elements.Slice(half)) - }; - } - } - - internal override ISpacePlan Measure(Size availableSpace) - { - return Compose(availableSpace.Width).Measure(availableSpace); - } - - internal override void Draw(ICanvas canvas, Size availableSpace) - { - Compose(availableSpace.Width).Draw(canvas, availableSpace); - } - } -} \ No newline at end of file diff --git a/QuestPDF/Elements/TreeStack.cs b/QuestPDF/Elements/TreeStack.cs deleted file mode 100644 index c097c47..0000000 --- a/QuestPDF/Elements/TreeStack.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using QuestPDF.Fluent; -using QuestPDF.Infrastructure; -using IComponent = QuestPDF.Infrastructure.IComponent; -using IContainer = QuestPDF.Infrastructure.IContainer; - -namespace QuestPDF.Elements -{ - internal class TreeStack : IComponent - { - public ICollection Children { get; internal set; } = new List(); - public float Spacing { get; set; } = 0; - - public void Compose(IContainer container) - { - var elements = AddSpacing(Children); - - container - .PaddingBottom(-Spacing) - .Element(BuildTree(elements.ToArray())); - - ICollection AddSpacing(ICollection elements) - { - if (Spacing < Size.Epsilon) - return elements; - - return elements - .Select(x => new Padding - { - Bottom = Spacing, - Child = x - }) - .Cast() - .ToList(); - } - - Element BuildTree(Span elements) - { - if (elements.IsEmpty) - return Empty.Instance; - - if (elements.Length == 1) - return elements[0]; - - var half = elements.Length / 2; - - return new SimpleStack - { - Current = BuildTree(elements.Slice(0, half)), - Next = BuildTree(elements.Slice(half)) - }; - } - } - } -} \ No newline at end of file diff --git a/QuestPDF/Fluent/SectionExtensions.cs b/QuestPDF/Fluent/DecorationExtensions.cs similarity index 96% rename from QuestPDF/Fluent/SectionExtensions.cs rename to QuestPDF/Fluent/DecorationExtensions.cs index dd109b3..d8cc7d6 100644 --- a/QuestPDF/Fluent/SectionExtensions.cs +++ b/QuestPDF/Fluent/DecorationExtensions.cs @@ -45,7 +45,7 @@ namespace QuestPDF.Fluent } } - public static class SectionExtensions + public static class DecorationExtensions { public static void Decoration(this IContainer element, Action handler) { diff --git a/QuestPDF/Fluent/GridExtensions.cs b/QuestPDF/Fluent/GridExtensions.cs index cc5dda6..d08942e 100644 --- a/QuestPDF/Fluent/GridExtensions.cs +++ b/QuestPDF/Fluent/GridExtensions.cs @@ -38,7 +38,7 @@ namespace QuestPDF.Fluent public void AlignCenter() => Alignment(HorizontalAlignment.Center); public void AlignRight() => Alignment(HorizontalAlignment.Right); - public IContainer Element(int columns = 1) + public IContainer Item(int columns = 1) { var container = new Container(); diff --git a/QuestPDF/Fluent/PageExtensions.cs b/QuestPDF/Fluent/PageExtensions.cs index 3a434d0..6a5c2ef 100644 --- a/QuestPDF/Fluent/PageExtensions.cs +++ b/QuestPDF/Fluent/PageExtensions.cs @@ -14,11 +14,6 @@ namespace QuestPDF.Fluent Page.Header = container; return container; } - - public void Header(Action handler) - { - handler?.Invoke(Header()); - } public IContainer Content() { @@ -27,22 +22,12 @@ namespace QuestPDF.Fluent return container; } - public void Content(Action handler) - { - handler?.Invoke(Content()); - } - public IContainer Footer() { var container = new Container(); Page.Footer = container; return container; } - - public void Footer(Action handler) - { - handler?.Invoke(Footer()); - } } public static class PageExtensions diff --git a/QuestPDF/Fluent/RowExtensions.cs b/QuestPDF/Fluent/RowExtensions.cs index ec9e8af..42755d2 100644 --- a/QuestPDF/Fluent/RowExtensions.cs +++ b/QuestPDF/Fluent/RowExtensions.cs @@ -8,19 +8,18 @@ namespace QuestPDF.Fluent { public class RowDescriptor { - private List Elements { get; } = new List(); - private float RowSpacing { get; set; } = 0; - + internal Row Row { get; } = new Row(); + public void Spacing(float value) { - RowSpacing = value; + Row.Spacing = value; } public IContainer ConstantColumn(float width) { var element = new ConstantRowElement(width); - Elements.Add(element); + Row.Children.Add(element); return element; } @@ -28,45 +27,9 @@ namespace QuestPDF.Fluent { var element = new RelativeRowElement(width); - Elements.Add(element); + Row.Children.Add(element); return element; } - - internal Element CreateRow() - { - return new TreeRow - { - Children = Elements, - Spacing = RowSpacing - }; - } - - internal Element CreateRow2() - { - if (Elements.Count == 0) - return Empty.Instance; - - if (RowSpacing <= Size.Epsilon) - return new Row - { - Children = Elements - }; - - var children = Elements - .SelectMany(x => new[] {x, new ConstantRowElement(RowSpacing) }) - .ToList(); - - var row = new Row - { - Children = children - }; - - return new Padding - { - Right = -RowSpacing, - Child = row - }; - } } public static class RowExtensions @@ -75,8 +38,7 @@ namespace QuestPDF.Fluent { var descriptor = new RowDescriptor(); handler(descriptor); - element.Element(descriptor.CreateRow()); - //element.Element(descriptor.CreateRow2()); + element.Element(descriptor.Row); } } } \ No newline at end of file diff --git a/QuestPDF/Fluent/StackExtensions.cs b/QuestPDF/Fluent/StackExtensions.cs index 4723884..0b9545b 100644 --- a/QuestPDF/Fluent/StackExtensions.cs +++ b/QuestPDF/Fluent/StackExtensions.cs @@ -8,66 +8,19 @@ namespace QuestPDF.Fluent { public class StackDescriptor { - private List Items { get; } = new List(); - private float StackSpacing { get; set; } = 0; - + internal Stack Stack { get; } = new Stack(); + public void Spacing(float value) { - StackSpacing = value; + Stack.Spacing = value; } public IContainer Item() { var container = new Container(); - Items.Add(container); + Stack.Children.Add(container); return container; } - - public void Item(Action handler) - { - handler?.Invoke(Item()); - } - - internal IComponent CreateStack() - { - return new TreeStack - { - Children = Items, - Spacing = StackSpacing - }; - } - - internal Element CreateStack2() - { - if (Items.Count == 0) - return Empty.Instance; - - if (StackSpacing <= Size.Epsilon) - return new Stack - { - Children = Items - }; - - var children = Items - .Select(x => new Padding - { - Bottom = StackSpacing, - Child = x - }) - .Cast() - .ToList(); - - var stack = new Stack - { - Children = children - }; - - return new Padding - { - Bottom = -StackSpacing, - Child = stack - }; - } } public static class StackExtensions @@ -76,7 +29,7 @@ namespace QuestPDF.Fluent { var descriptor = new StackDescriptor(); handler(descriptor); - element.Component(descriptor.CreateStack()); + element.Component(descriptor.Stack); } } } \ No newline at end of file