Compare commits

..

3 Commits

Author SHA1 Message Date
Marcin Ziąbek b6e0bd505c Merge branch 'main' into 2021.05 2021-05-03 11:39:17 +02:00
MarcinZiabek 4e3ba1d878 Fixed wrong import in QuestPDF.Examples.csproj 2021-04-16 10:22:41 +02:00
Marcin Ziąbek 6f0c6513da Bugfix: exception when providing null content into the Text element 2021-03-31 13:00:29 +02:00
23 changed files with 708 additions and 733 deletions
+10 -10
View File
@@ -19,20 +19,20 @@ namespace QuestPDF.Examples
//[ShowResult] //[ShowResult]
[ImageSize(300, 300)] [ImageSize(300, 300)]
public void Decoration(IContainer container) public void Section(IContainer container)
{ {
container container
.Background("#FFF") .Background("#FFF")
.Padding(25) .Padding(25)
.Decoration(decoration => .Decoration(section =>
{ {
decoration section
.Header() .Header()
.Background("#888") .Background("#888")
.Padding(10) .Padding(10)
.Text("Notes", TextStyle.Default.Size(16).Color("#FFF")); .Text("Notes", TextStyle.Default.Size(16).Color("#FFF"));
decoration section
.Content() .Content()
.Background("#DDD") .Background("#DDD")
.Padding(10) .Padding(10)
@@ -181,12 +181,12 @@ namespace QuestPDF.Examples
grid.Spacing(5); grid.Spacing(5);
grid.Columns(12); grid.Columns(12);
grid.Item(8).Background("#DDD").Height(50).Padding(5).Text("This is a short text", textStyle); grid.Element(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.Element(4).Background("#BBB").Padding(5).Text("Showing how to...", textStyle);
grid.Item(2).Background("#999").Height(50); grid.Element(2).Background("#999").Height(50);
grid.Item(4).Background("#AAA").Border(2).BorderColor("#666").Padding(5).Text("...generate", textStyle); grid.Element(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.Element(6).Background("#CCC").Padding(5).Text("simple grids", textStyle.Size(18).Bold());
grid.Item(8).Background("#DDD").Height(50); grid.Element(8).Background("#DDD").Height(50);
}); });
} }
@@ -20,8 +20,8 @@ namespace QuestPDF.ReportSample.Layouts
.Stack(stack => .Stack(stack =>
{ {
stack.Spacing(5); stack.Spacing(5);
stack.Item().Element(PhotoWithMaps); stack.Item(PhotoWithMaps);
stack.Item().Element(PhotoDetails); stack.Item(PhotoDetails);
}); });
} }
@@ -48,13 +48,13 @@ namespace QuestPDF.ReportSample.Layouts
{ {
grid.Columns(6); grid.Columns(6);
grid.Item().LabelCell().Text("Date", Typography.Normal); grid.Element().LabelCell().Text("Date", Typography.Normal);
grid.Item(2).ValueCell().Text(Model.Date?.ToString("g") ?? string.Empty, Typography.Normal); grid.Element(2).ValueCell().Text(Model.Date?.ToString("g") ?? string.Empty, Typography.Normal);
grid.Item().LabelCell().Text("Location", Typography.Normal); grid.Element().LabelCell().Text("Location", Typography.Normal);
grid.Item(2).ValueCell().Text(Model.Location.Format(), Typography.Normal); grid.Element(2).ValueCell().Text(Model.Location.Format(), Typography.Normal);
grid.Item().LabelCell().Text("Comments", Typography.Normal); grid.Element().LabelCell().Text("Comments", Typography.Normal);
grid.Item(5).ValueCell().Text(Model.Comments, Typography.Normal); grid.Element(5).ValueCell().Text(Model.Comments, Typography.Normal);
}); });
} }
} }
@@ -17,14 +17,14 @@ namespace QuestPDF.ReportSample.Layouts
{ {
container container
.EnsureSpace() .EnsureSpace()
.Decoration(decoration => .Decoration(section =>
{ {
decoration section
.Header() .Header()
.PaddingBottom(5) .PaddingBottom(5)
.Text(Model.Title, Typography.Headline); .Text(Model.Title, Typography.Headline);
decoration.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Stack(stack => section.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Stack(stack =>
{ {
foreach (var part in Model.Parts) foreach (var part in Model.Parts)
{ {
@@ -77,7 +77,7 @@ namespace QuestPDF.ReportSample.Layouts
grid.Spacing(5); grid.Spacing(5);
grid.Columns(3); grid.Columns(3);
model.Photos.ForEach(x => grid.Item().AspectRatio(4 / 3f).Image(Placeholders.Image)); model.Photos.ForEach(x => grid.Element().AspectRatio(4 / 3f).Image(Placeholders.Image));
}); });
} }
} }
@@ -30,8 +30,8 @@ namespace QuestPDF.ReportSample.Layouts
.PaddingHorizontal(50) .PaddingHorizontal(50)
.Page(page => .Page(page =>
{ {
page.Header().Element(ComposeHeader); page.Header(ComposeHeader);
page.Content().Element(ComposeContent); page.Content(ComposeContent);
page.Footer().AlignCenter().PageNumber("Page {number}"); page.Footer().AlignCenter().PageNumber("Page {number}");
}); });
} }
@@ -57,7 +57,7 @@ namespace QuestPDF.ReportSample.Layouts
foreach (var field in Model.HeaderFields) foreach (var field in Model.HeaderFields)
{ {
grid.Item().Stack(row => grid.Element().Stack(row =>
{ {
row.Item().AlignLeft().Text(field.Label, Typography.Normal.SemiBold()); row.Item().AlignLeft().Text(field.Label, Typography.Normal.SemiBold());
row.Item().Text(field.Value, Typography.Normal); row.Item().Text(field.Value, Typography.Normal);
@@ -16,21 +16,21 @@ namespace QuestPDF.ReportSample.Layouts
public void Compose(IContainer container) public void Compose(IContainer container)
{ {
container container
.Decoration(decoration => .Decoration(section =>
{ {
decoration section
.Header() .Header()
.PaddingBottom(5) .PaddingBottom(5)
.Text("Table of contents", Typography.Headline); .Text("Table of contents", Typography.Headline);
decoration.Content().Stack(stack => section.Content().Stack(stack =>
{ {
stack.Spacing(5); stack.Spacing(5);
for (var i = 0; i < Sections.Count; i++) for (var i = 0; i < Sections.Count; i++)
stack.Item().Element(c => DrawLink(c, i+1, Sections[i].Title)); stack.Item(c => DrawLink(c, i+1, Sections[i].Title));
stack.Item().Element(c => DrawLink(c, Sections.Count+1, "Photos")); stack.Item(c => DrawLink(c, Sections.Count+1, "Photos"));
}); });
}); });
} }
+3 -46
View File
@@ -7,10 +7,10 @@ using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests namespace QuestPDF.UnitTests
{ {
[TestFixture] [TestFixture]
public class EnsureSpaceTests public class WrapWhenLittleSpaceTests
{ {
[Test] [Test]
public void Measure_ReturnsWrap_WhenChildReturnsWrap() public void Measure_ReturnsWrap_WhenNotEnoughSpace()
{ {
TestPlan TestPlan
.For(x => new EnsureSpace .For(x => new EnsureSpace
@@ -19,54 +19,11 @@ namespace QuestPDF.UnitTests
MinHeight = 200 MinHeight = 200
}) })
.MeasureElement(new Size(400, 100)) .MeasureElement(new Size(400, 100))
.ExpectChildMeasure(new Size(400, 100), new Wrap())
.CheckMeasureResult(new Wrap()); .CheckMeasureResult(new Wrap());
} }
[Test] [Test]
public void Measure_ReturnsWrap_WhenChildReturnsPartialRender_AndNotEnoughSpace() public void Measure_Continues_WhenEnoughSpace()
{
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 TestPlan
.For(x => new EnsureSpace .For(x => new EnsureSpace
+29 -29
View File
@@ -6,7 +6,7 @@ using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests namespace QuestPDF.UnitTests
{ {
/*[TestFixture] [TestFixture]
public class GridTests public class GridTests
{ {
#region Alignment #region Alignment
@@ -29,11 +29,11 @@ namespace QuestPDF.UnitTests
{ {
grid.AlignLeft(); grid.AlignLeft();
grid.Item(6).Element(childA); grid.Element(6).Element(childA);
grid.Item(4).Element(childB); grid.Element(4).Element(childB);
grid.Item(4).Element(childC); grid.Element(4).Element(childC);
grid.Item(2).Element(childD); grid.Element(2).Element(childD);
grid.Item(8).Element(childE); grid.Element(8).Element(childE);
}); });
// assert // assert
@@ -45,20 +45,20 @@ namespace QuestPDF.UnitTests
{ {
row.RelativeColumn(6).Element(childA); row.RelativeColumn(6).Element(childA);
row.RelativeColumn(4).Element(childB); row.RelativeColumn(4).Element(childB);
row.RelativeColumn(2).Element(Empty.Instance); row.RelativeColumn(2).Element(new Empty());
}); });
stack.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeColumn(4).Element(childC); row.RelativeColumn(4).Element(childC);
row.RelativeColumn(2).Element(childD); row.RelativeColumn(2).Element(childD);
row.RelativeColumn(6).Element(Empty.Instance); row.RelativeColumn(6).Element(new Empty());
}); });
stack.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeColumn(8).Element(childE); row.RelativeColumn(8).Element(childE);
row.RelativeColumn(4).Element(Empty.Instance); row.RelativeColumn(4).Element(new Empty());
}); });
}); });
@@ -83,11 +83,11 @@ namespace QuestPDF.UnitTests
{ {
grid.AlignCenter(); grid.AlignCenter();
grid.Item(6).Element(childA); grid.Element(6).Element(childA);
grid.Item(4).Element(childB); grid.Element(4).Element(childB);
grid.Item(4).Element(childC); grid.Element(4).Element(childC);
grid.Item(2).Element(childD); grid.Element(2).Element(childD);
grid.Item(8).Element(childE); grid.Element(8).Element(childE);
}); });
// assert // assert
@@ -97,25 +97,25 @@ namespace QuestPDF.UnitTests
{ {
stack.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeColumn(1).Element(Empty.Instance); row.RelativeColumn(1).Element(new Empty());
row.RelativeColumn(6).Element(childA); row.RelativeColumn(6).Element(childA);
row.RelativeColumn(4).Element(childB); row.RelativeColumn(4).Element(childB);
row.RelativeColumn(1).Element(Empty.Instance); row.RelativeColumn(1).Element(new Empty());
}); });
stack.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeColumn(3).Element(Empty.Instance); row.RelativeColumn(3).Element(new Empty());
row.RelativeColumn(4).Element(childC); row.RelativeColumn(4).Element(childC);
row.RelativeColumn(2).Element(childD); row.RelativeColumn(2).Element(childD);
row.RelativeColumn(3).Element(Empty.Instance); row.RelativeColumn(3).Element(new Empty());
}); });
stack.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeColumn(2).Element(Empty.Instance); row.RelativeColumn(2).Element(new Empty());
row.RelativeColumn(8).Element(childE); row.RelativeColumn(8).Element(childE);
row.RelativeColumn(2).Element(Empty.Instance); row.RelativeColumn(2).Element(new Empty());
}); });
}); });
@@ -140,11 +140,11 @@ namespace QuestPDF.UnitTests
{ {
grid.AlignRight(); grid.AlignRight();
grid.Item(6).Element(childA); grid.Element(6).Element(childA);
grid.Item(4).Element(childB); grid.Element(4).Element(childB);
grid.Item(4).Element(childC); grid.Element(4).Element(childC);
grid.Item(2).Element(childD); grid.Element(2).Element(childD);
grid.Item(8).Element(childE); grid.Element(8).Element(childE);
}); });
// assert // assert
@@ -154,21 +154,21 @@ namespace QuestPDF.UnitTests
{ {
stack.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeColumn(2).Element(Empty.Instance); row.RelativeColumn(2).Element(new Empty());
row.RelativeColumn(6).Element(childA); row.RelativeColumn(6).Element(childA);
row.RelativeColumn(4).Element(childB); row.RelativeColumn(4).Element(childB);
}); });
stack.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeColumn(6).Element(Empty.Instance); row.RelativeColumn(6).Element(new Empty());
row.RelativeColumn(4).Element(childC); row.RelativeColumn(4).Element(childC);
row.RelativeColumn(2).Element(childD); row.RelativeColumn(2).Element(childD);
}); });
stack.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeColumn(4).Element(Empty.Instance); row.RelativeColumn(4).Element(new Empty());
row.RelativeColumn(8).Element(childE); row.RelativeColumn(8).Element(childE);
}); });
}); });
@@ -177,5 +177,5 @@ namespace QuestPDF.UnitTests
} }
#endregion #endregion
}*/ }
} }
+89 -93
View File
@@ -1,7 +1,8 @@
using NUnit.Framework; using System.Collections.Generic;
using QuestPDF.Drawing.SpacePlan; using FluentAssertions;
using NUnit.Framework;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Infrastructure; using QuestPDF.Fluent;
using QuestPDF.UnitTests.TestEngine; using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests namespace QuestPDF.UnitTests
@@ -9,108 +10,103 @@ namespace QuestPDF.UnitTests
[TestFixture] [TestFixture]
public class RowTests public class RowTests
{ {
#region Measure #region Spacing
[Test] [Test]
public void Measure_ReturnsWrap_WhenLeftChildReturnsWrap() public void Fluent_WithoutSpacing()
{ {
TestPlan // arrange
.For(x => new SimpleRow var structure = new Container();
var childA = TestPlan.CreateUniqueElement();
var childB = TestPlan.CreateUniqueElement();
var childC = TestPlan.CreateUniqueElement();
// act
structure
.Row(stack =>
{ {
Left = x.CreateChild("left"), stack.Spacing(0);
Right = x.CreateChild("right")
}) stack.ConstantColumn(100).Element(childA);
.MeasureElement(new Size(400, 300)) stack.RelativeColumn(2).Element(childB);
.ExpectChildMeasure("left", new Size(400, 300), new Wrap()) stack.RelativeColumn(3).Element(childC);
.CheckMeasureResult(new Wrap()); });
// assert
var expected = new Row()
{
Children = new List<RowElement>
{
new ConstantRowElement(100)
{
Child = childA
},
new RelativeRowElement(2)
{
Child = childB
},
new RelativeRowElement(3)
{
Child = childC
},
}
};
structure.Child.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering().IncludingAllRuntimeProperties());
} }
[Test] [Test]
public void Measure_ReturnsWrap_WhenRightChildReturnsWrap() public void Fluent_WithSpacing()
{ {
TestPlan // arrange
.For(x => new SimpleRow var structure = new Container();
var childA = TestPlan.CreateUniqueElement();
var childB = TestPlan.CreateUniqueElement();
var childC = TestPlan.CreateUniqueElement();
// act
structure
.Row(stack =>
{ {
Left = x.CreateChild("left"), stack.Spacing(25);
Right = x.CreateChild("right")
}) stack.ConstantColumn(100).Element(childA);
.MeasureElement(new Size(400, 300)) stack.RelativeColumn(2).Element(childB);
.ExpectChildMeasure("left", new Size(400, 300), new FullRender(250, 150)) stack.RelativeColumn(3).Element(childC);
.ExpectChildMeasure("right", new Size(150, 300), new Wrap()) });
.CheckMeasureResult(new Wrap());
} // assert
var expected = new Padding
[Test] {
public void Measure_ReturnsPartialRender_WhenLeftChildReturnsPartialRender() Right = -25,
{ Child = new Row()
TestPlan
.For(x => new SimpleRow
{ {
Left = x.CreateChild("left"), Children = new List<RowElement>
Right = x.CreateChild("right") {
}) new ConstantRowElement(100)
.MeasureElement(new Size(400, 300)) {
.ExpectChildMeasure("left", new Size(400, 300), new PartialRender(250, 150)) Child = childA
.ExpectChildMeasure("right", new Size(150, 300), new FullRender(100, 100)) },
.CheckMeasureResult(new PartialRender(350, 150)); new ConstantRowElement(25),
} new RelativeRowElement(2)
{
[Test] Child = childB
public void Measure_ReturnsPartialRender_WhenRightChildReturnsPartialRender() },
{ new ConstantRowElement(25),
TestPlan new RelativeRowElement(3)
.For(x => new SimpleRow {
{ Child = childC
Left = x.CreateChild("left"), },
Right = x.CreateChild("right") new ConstantRowElement(25)
}) }
.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)); structure.Child.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering().IncludingAllRuntimeProperties());
}
[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 #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
} }
} }
+256 -201
View File
@@ -1,6 +1,9 @@
using NUnit.Framework; using System.Collections.Generic;
using FluentAssertions;
using NUnit.Framework;
using QuestPDF.Drawing.SpacePlan; using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.TestEngine; using QuestPDF.UnitTests.TestEngine;
@@ -10,226 +13,278 @@ namespace QuestPDF.UnitTests
public class StackTests public class StackTests
{ {
#region Measure #region Measure
[Test] [Test]
public void Measure_ReturnsWrap_WhenFirstChildWraps() public void Measure_NoChildren_Empty()
{ {
TestPlan TestPlan
.For(x => new SimpleStack .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
{ {
First = x.CreateChild("first"), Children = new []
Second = x.CreateChild("second") {
x.CreateChild("a"),
x.CreateChild("b"),
x.CreateChild("c"),
x.CreateChild("d")
}
}) })
.MeasureElement(new Size(400, 300)) .MeasureElement(new Size(500, 1000))
.ExpectChildMeasure("first", new Size(400, 300), new Wrap()) .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())
.CheckMeasureResult(new Wrap()); .CheckMeasureResult(new Wrap());
} }
[Test]
public void Measure_ReturnsPartialRender_WhenFirstChildReturnsPartialRender()
{
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 PartialRender(300, 200))
.CheckMeasureResult(new PartialRender(300, 200));
}
[Test]
public void Measure_ReturnsPartialRender_WhenSecondChildWraps()
{
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 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 #endregion
#region Draw #region Combined
[Test] [Test]
public void Draw_WhenFirstChildWraps() public void WhenPageable_AndChildReturnsWrap()
{ {
TestPlan TestPlan
.For(x => new SimpleStack .For(x => new Stack
{ {
First = x.CreateChild("first"), Children = new[]
Second = x.CreateChild("second") {
x.CreateChild("a"),
x.CreateChild("b"),
x.CreateChild("c")
}
}) })
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), new Wrap()) // page 1: measure
.CheckDrawResult(); .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())
[Test] .CheckMeasureResult(new PartialRender(500, 200))
public void Draw_WhenFirstChildPartiallyRenders()
{ // page 1: draw
TestPlan .DrawElement(new Size(500, 1000))
.For(x => new SimpleStack
{ .ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new FullRender(400, 200))
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 Draw_WhenFirstChildFullyRenders_AndSecondChildWraps()
{
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 Wrap())
.CheckDrawResult();
}
[Test]
public void Draw_WhenFirstChildFullyRenders_AndSecondChildPartiallyRenders()
{
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 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) .ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("second", new Size(400, 300)) .ExpectChildDraw("a", new Size(500, 200))
.ExpectCanvasTranslate(0, 0)
.CheckState<SimpleStack>(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) .ExpectCanvasTranslate(0, 0)
.ExpectChildMeasure("b", expectedInput: new Size(500, 800), returns: new Wrap())
.CheckDrawResult() .CheckDrawResult()
.CheckState<SimpleStack>(x => !x.IsFirstRendered);
// // 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();
}
[Test]
public void WhenPageable_AndChildReturnsPartialRender()
{
TestPlan
.For(x => new Stack
{
Children = new[]
{
x.CreateChild("a"),
x.CreateChild("b"),
x.CreateChild("c")
}
})
// 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)
.CheckDrawResult();
} }
#endregion #endregion
// TODO: add tests for the spacing property #region Spacing
// TODO: add tests for the tree builder method
[Test]
public void Fluent_WithoutSpacing()
{
// arrange
var structure = new Container();
var childA = TestPlan.CreateUniqueElement();
var childB = TestPlan.CreateUniqueElement();
var childC = TestPlan.CreateUniqueElement();
// act
structure
.Stack(stack =>
{
stack.Spacing(0);
stack.Item().Element(childA);
stack.Item().Element(childB);
stack.Item().Element(childC);
});
// assert
var expected = new Stack
{
Children = new List<Element>
{
new Container
{
Child = childA
},
new Container
{
Child = childB
},
new Container
{
Child = childC
}
}
};
structure.Child.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering().IncludingAllRuntimeProperties());
}
[Test]
public void Fluent_WithSpacing()
{
// arrange
var structure = new Container();
var childA = TestPlan.CreateUniqueElement();
var childB = TestPlan.CreateUniqueElement();
var childC = TestPlan.CreateUniqueElement();
// act
structure
.Stack(stack =>
{
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
{
Children = new List<Element>
{
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());
}
#endregion
} }
} }
+23 -36
View File
@@ -49,9 +49,9 @@ namespace QuestPDF.UnitTests.TestEngine
TranslateFunc = position => TranslateFunc = position =>
{ {
var expected = GetExpected<CanvasTranslateOperationBase>(); var expected = GetExpected<CanvasTranslateOperationBase>();
Assert.AreEqual(expected.Position.X, position.X, "Translate X"); Assert.AreEqual(expected.Position.X, position.X);
Assert.AreEqual(expected.Position.Y, position.Y, "Translate Y"); Assert.AreEqual(expected.Position.Y, position.Y);
//position.Should().BeEquivalentTo(expected.Position); //position.Should().BeEquivalentTo(expected.Position);
}, },
@@ -59,13 +59,13 @@ namespace QuestPDF.UnitTests.TestEngine
{ {
var expected = GetExpected<CanvasDrawRectangleOperationBase>(); var expected = GetExpected<CanvasDrawRectangleOperationBase>();
Assert.AreEqual(expected.Position.X, position.X, "Draw rectangle: X"); Assert.AreEqual(expected.Position.X, position.X);
Assert.AreEqual(expected.Position.Y, position.Y, "Draw rectangle: Y"); Assert.AreEqual(expected.Position.Y, position.Y);
Assert.AreEqual(expected.Size.Width, size.Width, "Draw rectangle: width"); Assert.AreEqual(expected.Size.Width, size.Width);
Assert.AreEqual(expected.Size.Height, size.Height, "Draw rectangle: height"); Assert.AreEqual(expected.Size.Height, size.Height);
Assert.AreEqual(expected.Color, color, "Draw rectangle: color"); Assert.AreEqual(expected.Color, color);
/*position.Should().BeEquivalentTo(expected.Position); /*position.Should().BeEquivalentTo(expected.Position);
size.Should().BeEquivalentTo(expected.Size); size.Should().BeEquivalentTo(expected.Size);
@@ -77,12 +77,12 @@ namespace QuestPDF.UnitTests.TestEngine
Assert.AreEqual(expected.Text, text); Assert.AreEqual(expected.Text, text);
Assert.AreEqual(expected.Position.X, position.X, "Draw text: X"); Assert.AreEqual(expected.Position.X, position.X);
Assert.AreEqual(expected.Position.Y, position.Y, "Draw text: Y"); Assert.AreEqual(expected.Position.Y, position.Y);
Assert.AreEqual(expected.Style.Color, style.Color, "Draw text: color"); Assert.AreEqual(expected.Style.Color, style.Color);
Assert.AreEqual(expected.Style.FontType, style.FontType, "Draw text: font"); Assert.AreEqual(expected.Style.FontType, style.FontType);
Assert.AreEqual(expected.Style.Size, style.Size, "Draw text: size"); Assert.AreEqual(expected.Style.Size, style.Size);
/*text.Should().Be(expected.Text); /*text.Should().Be(expected.Text);
position.Should().BeEquivalentTo(expected.Position); position.Should().BeEquivalentTo(expected.Position);
@@ -92,11 +92,11 @@ namespace QuestPDF.UnitTests.TestEngine
{ {
var expected = GetExpected<CanvasDrawImageOperationBase>(); var expected = GetExpected<CanvasDrawImageOperationBase>();
Assert.AreEqual(expected.Position.X, position.X, "Draw image: X"); Assert.AreEqual(expected.Position.X, position.X);
Assert.AreEqual(expected.Position.Y, position.Y, "Draw image: Y"); Assert.AreEqual(expected.Position.Y, position.Y);
Assert.AreEqual(expected.Size.Width, size.Width, "Draw image: width"); Assert.AreEqual(expected.Size.Width, size.Width);
Assert.AreEqual(expected.Size.Height, size.Height, "Draw image: height"); Assert.AreEqual(expected.Size.Height, size.Height);
/*position.Should().BeEquivalentTo(expected.Position); /*position.Should().BeEquivalentTo(expected.Position);
size.Should().BeEquivalentTo(expected.Size);*/ size.Should().BeEquivalentTo(expected.Size);*/
@@ -117,8 +117,8 @@ namespace QuestPDF.UnitTests.TestEngine
Assert.AreEqual(expected.ChildId, id); Assert.AreEqual(expected.ChildId, id);
Assert.AreEqual(expected.Input.Width, availableSpace.Width, $"Measure: width of child '{expected.ChildId}'"); Assert.AreEqual(expected.Input.Width, availableSpace.Width);
Assert.AreEqual(expected.Input.Height, availableSpace.Height, $"Measure: height of child '{expected.ChildId}'"); Assert.AreEqual(expected.Input.Height, availableSpace.Height);
// id.Should().Be(expected.ChildId); // id.Should().Be(expected.ChildId);
// availableSpace.Should().Be(expected.Input); // availableSpace.Should().Be(expected.Input);
@@ -131,8 +131,8 @@ namespace QuestPDF.UnitTests.TestEngine
Assert.AreEqual(expected.ChildId, id); Assert.AreEqual(expected.ChildId, id);
Assert.AreEqual(expected.Input.Width, availableSpace.Width, $"Draw: width of child '{expected.ChildId}'"); Assert.AreEqual(expected.Input.Width, availableSpace.Width);
Assert.AreEqual(expected.Input.Height, availableSpace.Height, $"Draw: width of child '{expected.ChildId}'"); Assert.AreEqual(expected.Input.Height, availableSpace.Height);
/*id.Should().Be(expected.ChildId); /*id.Should().Be(expected.ChildId);
availableSpace.Should().Be(expected.Input);*/ availableSpace.Should().Be(expected.Input);*/
@@ -214,8 +214,8 @@ namespace QuestPDF.UnitTests.TestEngine
if (expectedSize != null) if (expectedSize != null)
{ {
Assert.AreEqual(expectedSize.Width, actualSize.Width, "Measure: width"); Assert.AreEqual(expectedSize.Width, actualSize.Width);
Assert.AreEqual(expectedSize.Height, actualSize.Height, "Measure: height"); Assert.AreEqual(expectedSize.Height, actualSize.Height);
} }
return this; return this;
@@ -227,19 +227,6 @@ namespace QuestPDF.UnitTests.TestEngine
return this; return this;
} }
public TestPlan CheckState(Func<Element, bool> condition)
{
Assert.IsTrue(condition(Element), "Checking condition");
return this;
}
public TestPlan CheckState<T>(Func<T, bool> condition) where T : Element
{
Assert.IsTrue(Element is T);
Assert.IsTrue(condition(Element as T), "Checking condition");
return this;
}
public static Element CreateUniqueElement() public static Element CreateUniqueElement()
{ {
return new Text return new Text
+3 -61
View File
@@ -1,71 +1,13 @@
using System;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Fluent; using QuestPDF.Fluent;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
namespace QuestPDF.Elements 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 internal class Decoration : IComponent
{ {
public Element Header { get; set; } = Empty.Instance; public Element Header { get; set; } = new Empty();
public Element Content { get; set; } = Empty.Instance; public Element Content { get; set; } = new Empty();
public Element Footer { get; set; } = Empty.Instance; public Element Footer { get; set; } = new Empty();
public void Compose(IContainer container) public void Compose(IContainer container)
{ {
-2
View File
@@ -5,8 +5,6 @@ namespace QuestPDF.Elements
{ {
internal class Empty : Element internal class Empty : Element
{ {
internal static Empty Instance { get; } = new Empty();
internal override ISpacePlan Measure(Size availableSpace) internal override ISpacePlan Measure(Size availableSpace)
{ {
return new FullRender(Size.Zero); return new FullRender(Size.Zero);
+7 -7
View File
@@ -5,17 +5,17 @@ namespace QuestPDF.Elements
{ {
internal class Page : IComponent internal class Page : IComponent
{ {
public Element Header { get; set; } = Empty.Instance; public Element Header { get; set; } = new Empty();
public Element Content { get; set; } = Empty.Instance; public Element Content { get; set; } = new Empty();
public Element Footer { get; set; } = Empty.Instance; public Element Footer { get; set; } = new Empty();
public void Compose(IContainer container) public void Compose(IContainer container)
{ {
container.Decoration(decoration => container.Decoration(section =>
{ {
decoration.Header().Element(Header); section.Header().Element(Header);
decoration.Content().Extend().Element(Content); section.Content().Extend().Element(Content);
decoration.Footer().Element(Footer); section.Footer().Element(Footer);
}); });
} }
} }
+54 -112
View File
@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using QuestPDF.Drawing.SpacePlan; using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
namespace QuestPDF.Elements namespace QuestPDF.Elements
@@ -33,134 +31,78 @@ 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));
}
}
internal class Row : Element internal class Row : Element
{ {
public ICollection<RowElement> Children { get; internal set; } = new List<RowElement>(); public List<RowElement> Children { get; set; } = new List<RowElement>();
public float Spacing { get; set; } = 0;
float? ConstantWidthSum { get; set; }
float? RelativeWidthSum { get; set; }
internal override ISpacePlan Measure(Size availableSpace) internal override ISpacePlan Measure(Size availableSpace)
{ {
return Compose(availableSpace.Width).Measure(availableSpace); var sizes = Children
.Select(x =>
{
var space = GetTargetSize(x, availableSpace);
return x.Child.Measure(space);
})
.ToList();
if (sizes.Any(x => x is Wrap))
return new Wrap();
var height = sizes
.Where(x => x is Size)
.Cast<Size>()
.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);
return new FullRender(Size.Zero);
} }
internal override void Draw(ICanvas canvas, Size availableSpace) internal override void Draw(ICanvas canvas, Size availableSpace)
{ {
Compose(availableSpace.Width).Draw(canvas, availableSpace); var targetSpace = Measure(availableSpace) as Size;
if (targetSpace == null)
return;
var offset = 0f;
foreach (var column in Children)
{
var space = GetTargetSize(column, targetSpace);
canvas.Translate(new Position(offset, 0));
column.Child.Draw(canvas, space);
canvas.Translate(new Position(-offset, 0));
offset += space.Width;
}
} }
#region structure private Size GetTargetSize(RowElement rowElement, Size availableSpace)
private Element Compose(float availableWidth)
{ {
var elements = AddSpacing(Children, Spacing); if (rowElement is ConstantRowElement)
var rowElements = ReduceRows(elements, availableWidth); return new Size(rowElement.Width, availableSpace.Height);
return BuildTree(rowElements.ToArray());
} ConstantWidthSum ??= Children
private static ICollection<Element> ReduceRows(ICollection<RowElement> elements, float availableWidth)
{
var constantWidth = elements
.Where(x => x is ConstantRowElement) .Where(x => x is ConstantRowElement)
.Cast<ConstantRowElement>() .Cast<ConstantRowElement>()
.Sum(x => x.Width); .Sum(x => x.Width);
var relativeWidth = elements RelativeWidthSum ??= Children
.Where(x => x is RelativeRowElement) .Where(x => x is RelativeRowElement)
.Cast<RelativeRowElement>() .Cast<RelativeRowElement>()
.Sum(x => x.Width); .Sum(x => x.Width);
var widthPerRelativeUnit = (availableWidth - constantWidth) / relativeWidth;
return elements return new Size((availableSpace.Width - ConstantWidthSum.Value) * rowElement.Width / RelativeWidthSum.Value, availableSpace.Height);
.Select(x =>
{
if (x is RelativeRowElement r)
return new ConstantRowElement(r.Width * widthPerRelativeUnit)
{
Child = x.Child
};
return x;
})
.Select(x => new Constrained
{
MinWidth = x.Width,
MaxWidth = x.Width,
Child = x.Child
})
.Cast<Element>()
.ToList();
} }
private static ICollection<RowElement> AddSpacing(ICollection<RowElement> elements, float spacing)
{
if (spacing < Size.Epsilon)
return elements;
return elements
.SelectMany(x => new[] { new ConstantRowElement(spacing), x })
.Skip(1)
.ToList();
}
private static Element BuildTree(Span<Element> elements)
{
if (elements.IsEmpty)
return Empty.Instance;
if (elements.Length == 1)
return elements[0];
var half = elements.Length / 2;
return new SimpleRow
{
Left = BuildTree(elements.Slice(0, half)),
Right = BuildTree(elements.Slice(half))
};
}
#endregion
} }
} }
+62
View File
@@ -0,0 +1,62 @@
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; } = new Empty();
public Element ContentElement { get; set; } = new Empty();
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));
}
}
}
+56 -101
View File
@@ -1,126 +1,81 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq; using System.Linq;
using QuestPDF.Drawing.SpacePlan; using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using IComponent = QuestPDF.Infrastructure.IComponent;
using IContainer = QuestPDF.Infrastructure.IContainer;
namespace QuestPDF.Elements namespace QuestPDF.Elements
{ {
internal class SimpleStack : Element internal class Stack : Element
{ {
internal Element First { get; set; } = Empty.Instance; public ICollection<Element?> Children { get; internal set; } = new List<Element?>();
internal Element Second { get; set; } = Empty.Instance; private Queue<Element?> ChildrenQueue { get; set; } = new Queue<Element?>();
internal bool IsFirstRendered { get; set; } = false; private void Initialize()
{
if (ChildrenQueue.Count == 0)
ChildrenQueue = new Queue<Element>(Children.Where(x => x != null));
}
internal override ISpacePlan Measure(Size availableSpace) internal override ISpacePlan Measure(Size availableSpace)
{ {
var firstElement = IsFirstRendered ? Empty.Instance : First; Initialize();
var firstSize = firstElement.Measure(availableSpace) as Size;
if (firstSize == null)
return new Wrap();
if (firstSize is PartialRender partialRender) if(!ChildrenQueue.Any())
return partialRender; return new FullRender(Size.Zero);
var spaceForSecond = new Size(availableSpace.Width, availableSpace.Height - firstSize.Height);
var secondSize = Second.Measure(spaceForSecond) as Size;
if (secondSize == null) var heightOnCurrentPage = 0f;
return new PartialRender(firstSize);
var totalWidth = Math.Max(firstSize.Width, secondSize.Width); foreach (var renderer in ChildrenQueue)
var totalHeight = firstSize.Height + secondSize.Height; {
var targetSize = new Size(totalWidth, totalHeight); var space = renderer.Measure(new Size(availableSpace.Width, availableSpace.Height - heightOnCurrentPage));
if (secondSize is PartialRender) if (space is Wrap)
return new PartialRender(targetSize); {
if (heightOnCurrentPage < Size.Epsilon)
return new FullRender(targetSize); 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);
} }
internal override void Draw(ICanvas canvas, Size availableSpace) internal override void Draw(ICanvas canvas, Size availableSpace)
{ {
var firstElement = IsFirstRendered ? Empty.Instance : First; Initialize();
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));
if (secondMeasurement is FullRender) var topOffset = 0f;
IsFirstRendered = false;
}
}
internal class Stack : IComponent
{
public ICollection<Element> Children { get; internal set; } = new List<Element>();
public float Spacing { get; set; } = 0;
public void Compose(IContainer container)
{
var elements = AddSpacing(Spacing, Children);
container while (ChildrenQueue.Any())
.PaddingBottom(-Spacing)
.Element(BuildTree(elements.ToArray()));
}
static ICollection<Element> AddSpacing(float spacing, ICollection<Element> elements)
{
if (spacing < Size.Epsilon)
return elements;
return elements
.Select(x => new Padding
{
Bottom = spacing,
Child = x
})
.Cast<Element>()
.ToList();
}
static Element BuildTree(Span<Element> elements)
{
if (elements.IsEmpty)
return Empty.Instance;
if (elements.Length == 1)
return elements[0];
var half = elements.Length / 2;
return new SimpleStack
{ {
First = BuildTree(elements.Slice(0, half)), var child = ChildrenQueue.Peek();
Second = BuildTree(elements.Slice(half))
}; 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();
}
} }
} }
} }
+1 -1
View File
@@ -163,4 +163,4 @@ namespace QuestPDF.Fluent
return element.Element(new Box()); return element.Element(new Box());
} }
} }
} }
+1 -1
View File
@@ -38,7 +38,7 @@ namespace QuestPDF.Fluent
public void AlignCenter() => Alignment(HorizontalAlignment.Center); public void AlignCenter() => Alignment(HorizontalAlignment.Center);
public void AlignRight() => Alignment(HorizontalAlignment.Right); public void AlignRight() => Alignment(HorizontalAlignment.Right);
public IContainer Item(int columns = 1) public IContainer Element(int columns = 1)
{ {
var container = new Container(); var container = new Container();
+15
View File
@@ -14,6 +14,11 @@ namespace QuestPDF.Fluent
Page.Header = container; Page.Header = container;
return container; return container;
} }
public void Header(Action<IContainer> handler)
{
handler?.Invoke(Header());
}
public IContainer Content() public IContainer Content()
{ {
@@ -22,12 +27,22 @@ namespace QuestPDF.Fluent
return container; return container;
} }
public void Content(Action<IContainer> handler)
{
handler?.Invoke(Content());
}
public IContainer Footer() public IContainer Footer()
{ {
var container = new Container(); var container = new Container();
Page.Footer = container; Page.Footer = container;
return container; return container;
} }
public void Footer(Action<IContainer> handler)
{
handler?.Invoke(Footer());
}
} }
public static class PageExtensions public static class PageExtensions
+34 -6
View File
@@ -8,18 +8,19 @@ namespace QuestPDF.Fluent
{ {
public class RowDescriptor public class RowDescriptor
{ {
internal Row Row { get; } = new Row(); private List<RowElement> Elements { get; } = new List<RowElement>();
private float RowSpacing { get; set; } = 0;
public void Spacing(float value) public void Spacing(float value)
{ {
Row.Spacing = value; RowSpacing = value;
} }
public IContainer ConstantColumn(float width) public IContainer ConstantColumn(float width)
{ {
var element = new ConstantRowElement(width); var element = new ConstantRowElement(width);
Row.Children.Add(element); Elements.Add(element);
return element; return element;
} }
@@ -27,9 +28,36 @@ namespace QuestPDF.Fluent
{ {
var element = new RelativeRowElement(width); var element = new RelativeRowElement(width);
Row.Children.Add(element); Elements.Add(element);
return element; return element;
} }
internal Element CreateRow()
{
if (Elements.Count == 0)
return new Empty();
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 public static class RowExtensions
@@ -38,7 +66,7 @@ namespace QuestPDF.Fluent
{ {
var descriptor = new RowDescriptor(); var descriptor = new RowDescriptor();
handler(descriptor); handler(descriptor);
element.Element(descriptor.Row); element.Element(descriptor.CreateRow());
} }
} }
} }
@@ -45,7 +45,7 @@ namespace QuestPDF.Fluent
} }
} }
public static class DecorationExtensions public static class SectionExtensions
{ {
public static void Decoration(this IContainer element, Action<DecorationDescriptor> handler) public static void Decoration(this IContainer element, Action<DecorationDescriptor> handler)
{ {
+43 -5
View File
@@ -8,19 +8,57 @@ namespace QuestPDF.Fluent
{ {
public class StackDescriptor public class StackDescriptor
{ {
internal Stack Stack { get; } = new Stack(); private List<Element> Items { get; } = new List<Element>();
private float StackSpacing { get; set; } = 0;
public void Spacing(float value) public void Spacing(float value)
{ {
Stack.Spacing = value; StackSpacing = value;
} }
public IContainer Item() public IContainer Item()
{ {
var container = new Container(); var container = new Container();
Stack.Children.Add(container); Items.Add(container);
return container; return container;
} }
public void Item(Action<IContainer> handler)
{
handler?.Invoke(Item());
}
internal Element CreateStack()
{
if (Items.Count == 0)
return new Empty();
if (StackSpacing <= Size.Epsilon)
return new Stack
{
Children = Items
};
var children = Items
.Select(x => new Padding
{
Bottom = StackSpacing,
Child = x
})
.Cast<Element>()
.ToList();
var stack = new Stack
{
Children = children
};
return new Padding
{
Bottom = -StackSpacing,
Child = stack
};
}
} }
public static class StackExtensions public static class StackExtensions
@@ -29,7 +67,7 @@ namespace QuestPDF.Fluent
{ {
var descriptor = new StackDescriptor(); var descriptor = new StackDescriptor();
handler(descriptor); handler(descriptor);
element.Component(descriptor.Stack); element.Element(descriptor.CreateStack());
} }
} }
} }
+1 -1
View File
@@ -5,7 +5,7 @@ namespace QuestPDF.Infrastructure
{ {
internal abstract class ContainerElement : Element, IContainer internal abstract class ContainerElement : Element, IContainer
{ {
internal Element? Child { get; set; } = Empty.Instance; internal Element? Child { get; set; } = new Empty();
IElement? IContainer.Child IElement? IContainer.Child
{ {