Compare commits
29 Commits
element-proxy
...
2022.01
| Author | SHA1 | Date | |
|---|---|---|---|
| 51508bbe0b | |||
| 62b5054a2f | |||
| 51dac22422 | |||
| 01980e5635 | |||
| 6e59fa6e3d | |||
| 4003337d01 | |||
| 5931b12b90 | |||
| 935c575221 | |||
| 18a710e2a3 | |||
| d39e93b924 | |||
| c8cc968705 | |||
| ffccbe37a4 | |||
| a6439c1883 | |||
| c0e4ec6df1 | |||
| a80258aa58 | |||
| 48c5771abb | |||
| b79122aca5 | |||
| 2fcf543817 | |||
| 7f72e50eb8 | |||
| f718cb3324 | |||
| efdc8903a5 | |||
| 647b1fd0f4 | |||
| 8a62591940 | |||
| 6da3075e7a | |||
| 0a27b80b73 | |||
| 656d1a6ecf | |||
| 5f636beb9d | |||
| 10683776bb | |||
| f2c47c4fd3 |
@@ -1,32 +0,0 @@
|
|||||||
using System.IO;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using QuestPDF.Drawing;
|
|
||||||
using QuestPDF.Examples.Engine;
|
|
||||||
using QuestPDF.Fluent;
|
|
||||||
using QuestPDF.Helpers;
|
|
||||||
using QuestPDF.Infrastructure;
|
|
||||||
|
|
||||||
namespace QuestPDF.Examples
|
|
||||||
{
|
|
||||||
public class BarCode
|
|
||||||
{
|
|
||||||
[Test]
|
|
||||||
public void Example()
|
|
||||||
{
|
|
||||||
FontManager.RegisterFontType("LibreBarcode39", File.OpenRead("LibreBarcode39-Regular.ttf"));
|
|
||||||
|
|
||||||
RenderingTest
|
|
||||||
.Create()
|
|
||||||
.PageSize(400, 100)
|
|
||||||
.ShowResults()
|
|
||||||
.Render(container =>
|
|
||||||
{
|
|
||||||
container
|
|
||||||
.Background(Colors.White)
|
|
||||||
.AlignCenter()
|
|
||||||
.AlignMiddle()
|
|
||||||
.Text("*QuestPDF*", TextStyle.Default.FontType("LibreBarcode39").Size(64));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
using NUnit.Framework;
|
using System.IO;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using QuestPDF.Drawing;
|
||||||
using QuestPDF.Examples.Engine;
|
using QuestPDF.Examples.Engine;
|
||||||
using QuestPDF.Fluent;
|
using QuestPDF.Fluent;
|
||||||
using QuestPDF.Helpers;
|
using QuestPDF.Helpers;
|
||||||
@@ -6,26 +8,25 @@ using QuestPDF.Infrastructure;
|
|||||||
|
|
||||||
namespace QuestPDF.Examples
|
namespace QuestPDF.Examples
|
||||||
{
|
{
|
||||||
[TestFixture]
|
|
||||||
public class BarcodeExamples
|
public class BarcodeExamples
|
||||||
{
|
{
|
||||||
[Test]
|
[Test]
|
||||||
public void Barcode()
|
public void Example()
|
||||||
{
|
{
|
||||||
|
FontManager.RegisterFontType("LibreBarcode39", File.OpenRead("LibreBarcode39-Regular.ttf"));
|
||||||
|
|
||||||
RenderingTest
|
RenderingTest
|
||||||
.Create()
|
.Create()
|
||||||
.PageSize(300, 300)
|
.PageSize(400, 100)
|
||||||
|
.ShowResults()
|
||||||
.Render(container =>
|
.Render(container =>
|
||||||
{
|
{
|
||||||
container
|
container
|
||||||
.Background("#FFF")
|
.Background(Colors.White)
|
||||||
.Padding(25)
|
.AlignCenter()
|
||||||
.Stack(stack =>
|
.AlignMiddle()
|
||||||
{
|
.Text("*QuestPDF*", TextStyle.Default.FontType("LibreBarcode39").Size(64));
|
||||||
stack.Item().Border(1).Background(Colors.Grey.Lighten3).Padding(5).Text("Barcode Example");
|
|
||||||
stack.Item().Border(1).Padding(5).AlignCenter().Text("*123456789*", TextStyle.Default.FontType("CarolinaBar-Demo-25E2").Size(20));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using Microcharts;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using QuestPDF.Examples.Engine;
|
||||||
|
using QuestPDF.Fluent;
|
||||||
|
using QuestPDF.Helpers;
|
||||||
|
using QuestPDF.Infrastructure;
|
||||||
|
using SkiaSharp;
|
||||||
|
|
||||||
|
namespace QuestPDF.Examples
|
||||||
|
{
|
||||||
|
public class CanvasExamples
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void BorderRadius()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.PageSize(175, 100)
|
||||||
|
.ProduceImages()
|
||||||
|
.ShowResults()
|
||||||
|
.Render(container =>
|
||||||
|
{
|
||||||
|
container
|
||||||
|
.Background(Colors.Grey.Lighten2)
|
||||||
|
.Padding(25)
|
||||||
|
.MinimalBox()
|
||||||
|
.Layers(layers =>
|
||||||
|
{
|
||||||
|
layers.Layer().Canvas((canvas, size) =>
|
||||||
|
{
|
||||||
|
DrawRoundedRectangle(Colors.White, false);
|
||||||
|
DrawRoundedRectangle(Colors.Blue.Darken2, true);
|
||||||
|
|
||||||
|
void DrawRoundedRectangle(string color, bool isStroke)
|
||||||
|
{
|
||||||
|
using var paint = new SKPaint
|
||||||
|
{
|
||||||
|
Color = SKColor.Parse(color),
|
||||||
|
IsStroke = isStroke,
|
||||||
|
StrokeWidth = 2,
|
||||||
|
IsAntialias = true
|
||||||
|
};
|
||||||
|
|
||||||
|
canvas.DrawRoundRect(0, 0, size.Width, size.Height, 20, 20, paint);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
layers
|
||||||
|
.PrimaryLayer()
|
||||||
|
.PaddingVertical(10)
|
||||||
|
.PaddingHorizontal(20)
|
||||||
|
.Text("Sample text", TextStyle.Default.Size(16).Color(Colors.Blue.Darken2).SemiBold());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using QuestPDF.Examples.Engine;
|
using QuestPDF.Examples.Engine;
|
||||||
using QuestPDF.Fluent;
|
using QuestPDF.Fluent;
|
||||||
@@ -43,19 +44,41 @@ namespace QuestPDF.Examples
|
|||||||
|
|
||||||
RenderingTest
|
RenderingTest
|
||||||
.Create()
|
.Create()
|
||||||
.PageSize(300, 300)
|
.PageSize(400, 600)
|
||||||
|
.ProduceImages()
|
||||||
.ShowResults()
|
.ShowResults()
|
||||||
.Render(container =>
|
.Render(container =>
|
||||||
{
|
{
|
||||||
container.Extend().Canvas((canvas, size) =>
|
container
|
||||||
{
|
.Background(Colors.White)
|
||||||
var bar = new BarChart
|
.Padding(25)
|
||||||
|
.Stack(stack =>
|
||||||
{
|
{
|
||||||
Entries = entries,
|
stack
|
||||||
IsAnimated = false,
|
.Item()
|
||||||
};
|
.PaddingBottom(10)
|
||||||
bar.Draw(canvas, (int)size.Width, (int)size.Height);
|
.Text("Chart example", TextStyle.Default.Size(20).SemiBold().Color(Colors.Blue.Medium));
|
||||||
});
|
|
||||||
|
stack
|
||||||
|
.Item()
|
||||||
|
.Border(1)
|
||||||
|
.ExtendHorizontal()
|
||||||
|
.Height(300)
|
||||||
|
.Canvas((canvas, size) =>
|
||||||
|
{
|
||||||
|
var chart = new BarChart
|
||||||
|
{
|
||||||
|
Entries = entries,
|
||||||
|
|
||||||
|
LabelOrientation = Orientation.Horizontal,
|
||||||
|
ValueLabelOrientation = Orientation.Horizontal,
|
||||||
|
|
||||||
|
IsAnimated = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
chart.DrawContent(canvas, (int)size.Width, (int)size.Height);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ namespace QuestPDF.Examples
|
|||||||
.PageSize(PageSizes.A4)
|
.PageSize(PageSizes.A4)
|
||||||
.ProducePdf()
|
.ProducePdf()
|
||||||
.ShowResults()
|
.ShowResults()
|
||||||
.Render(x => x.Image(new byte[] { 1, 2, 3 }));
|
.Render(x => GenerateStructure(x, 16));
|
||||||
//.Render(x => GenerateStructure(x, 16));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GenerateStructure(IContainer container, int level)
|
private void GenerateStructure(IContainer container, int level)
|
||||||
@@ -33,7 +32,7 @@ namespace QuestPDF.Examples
|
|||||||
if (level % 3 == 0)
|
if (level % 3 == 0)
|
||||||
{
|
{
|
||||||
container
|
container
|
||||||
.Border(level / 10f)
|
.Border(level / 4f)
|
||||||
.BorderColor(Colors.Black)
|
.BorderColor(Colors.Black)
|
||||||
.Row(row =>
|
.Row(row =>
|
||||||
{
|
{
|
||||||
@@ -44,7 +43,7 @@ namespace QuestPDF.Examples
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
container
|
container
|
||||||
.Border(level / 10f)
|
.Border(level / 4f)
|
||||||
.BorderColor(Colors.Black)
|
.BorderColor(Colors.Black)
|
||||||
.Stack(stack =>
|
.Stack(stack =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using QuestPDF.Drawing.Exceptions;
|
||||||
using QuestPDF.Examples.Engine;
|
using QuestPDF.Examples.Engine;
|
||||||
using QuestPDF.Fluent;
|
using QuestPDF.Fluent;
|
||||||
using QuestPDF.Helpers;
|
using QuestPDF.Helpers;
|
||||||
@@ -10,23 +11,66 @@ namespace QuestPDF.Examples
|
|||||||
[Test]
|
[Test]
|
||||||
public void Stack()
|
public void Stack()
|
||||||
{
|
{
|
||||||
RenderingTest
|
Assert.Throws<DocumentLayoutException>(() =>
|
||||||
.Create()
|
{
|
||||||
.PageSize(500, 360)
|
RenderingTest
|
||||||
.Render(container =>
|
.Create()
|
||||||
{
|
.PageSize(500, 360)
|
||||||
container
|
.Render(container =>
|
||||||
.Background(Colors.White)
|
{
|
||||||
.Padding(15)
|
container
|
||||||
.Grid(grid =>
|
.Padding(10)
|
||||||
{
|
.Width(100)
|
||||||
grid.Spacing(15);
|
.Background(Colors.Grey.Lighten3)
|
||||||
|
.DebugPointer("Example debug pointer")
|
||||||
grid.Item().Background(Colors.Grey.Medium).Text(Placeholders.LoremIpsum());
|
.Stack(x =>
|
||||||
grid.Item().DebugPointer("Test").Background(Colors.Grey.Lighten1).Height(1000); // <-- problem
|
{
|
||||||
grid.Item().Background(Colors.Grey.Lighten2).Height(150);
|
x.Item().Text("Test");
|
||||||
});
|
x.Item().Width(150);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Simple()
|
||||||
|
{
|
||||||
|
Assert.Throws<DocumentLayoutException>(() =>
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.PageSize(500, 360)
|
||||||
|
.Render(container =>
|
||||||
|
{
|
||||||
|
container
|
||||||
|
.Padding(10)
|
||||||
|
.Width(100)
|
||||||
|
.Background(Colors.Grey.Lighten3)
|
||||||
|
.Width(150)
|
||||||
|
.Text("Test");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void DebugPointer()
|
||||||
|
{
|
||||||
|
Assert.Throws<DocumentLayoutException>(() =>
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.PageSize(500, 360)
|
||||||
|
.Render(container =>
|
||||||
|
{
|
||||||
|
container
|
||||||
|
.Background(Colors.Grey.Lighten3)
|
||||||
|
.Padding(10)
|
||||||
|
.Width(100)
|
||||||
|
.DebugPointer("Example debug pointer")
|
||||||
|
.Width(150)
|
||||||
|
.Text("Example");
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using QuestPDF.Examples.Engine;
|
||||||
|
using QuestPDF.Fluent;
|
||||||
|
using QuestPDF.Helpers;
|
||||||
|
using QuestPDF.Infrastructure;
|
||||||
|
|
||||||
|
namespace QuestPDF.Examples
|
||||||
|
{
|
||||||
|
public class DefaultTextStyleExamples
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void Placeholder()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.PageSize(220, 270)
|
||||||
|
.ProduceImages()
|
||||||
|
.ShowResults()
|
||||||
|
.EnableDebugging()
|
||||||
|
.Render(container =>
|
||||||
|
{
|
||||||
|
container
|
||||||
|
.Padding(10)
|
||||||
|
.DefaultTextStyle(TextStyle.Default.Bold().Underline())
|
||||||
|
.Stack(stack =>
|
||||||
|
{
|
||||||
|
stack.Item().Text("Default style applies to all children", TextStyle.Default);
|
||||||
|
stack.Item().Text("You can override certain styles", TextStyle.Default.Underline(false).Color(Colors.Green.Darken2));
|
||||||
|
|
||||||
|
stack.Item().PaddingTop(10).Border(1).Grid(grid =>
|
||||||
|
{
|
||||||
|
grid.Columns(4);
|
||||||
|
|
||||||
|
foreach (var i in Enumerable.Range(1, 16))
|
||||||
|
{
|
||||||
|
grid.Item()
|
||||||
|
.Border(1)
|
||||||
|
.BorderColor(Colors.Grey.Lighten1)
|
||||||
|
.Background(Colors.Grey.Lighten3)
|
||||||
|
.Width(50)
|
||||||
|
.Height(50)
|
||||||
|
.AlignCenter()
|
||||||
|
.AlignMiddle()
|
||||||
|
.Text(i, TextStyle.Default.Size(16 + i / 4));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using QuestPDF.Examples.Engine;
|
||||||
|
using QuestPDF.Fluent;
|
||||||
|
using QuestPDF.Helpers;
|
||||||
|
using QuestPDF.Infrastructure;
|
||||||
|
|
||||||
|
namespace QuestPDF.Examples
|
||||||
|
{
|
||||||
|
public class DifferentHeaderOnFirstPageExample
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void Placeholder()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.PageSize(PageSizes.A6)
|
||||||
|
.ProduceImages()
|
||||||
|
.ShowResults()
|
||||||
|
.EnableDebugging()
|
||||||
|
.RenderDocument(container =>
|
||||||
|
{
|
||||||
|
container.Page(page =>
|
||||||
|
{
|
||||||
|
page.Size(PageSizes.A6);
|
||||||
|
page.Margin(30);
|
||||||
|
page.Background(Colors.White);
|
||||||
|
|
||||||
|
page.Header().Stack(stack =>
|
||||||
|
{
|
||||||
|
stack.Item().ShowOnce().Background(Colors.Blue.Lighten2).Height(60);
|
||||||
|
stack.Item().SkipOnce().Background(Colors.Green.Lighten2).Height(40);
|
||||||
|
});
|
||||||
|
|
||||||
|
page.Content().PaddingVertical(10).Stack(stack =>
|
||||||
|
{
|
||||||
|
stack.Spacing(10);
|
||||||
|
|
||||||
|
foreach (var _ in Enumerable.Range(0, 13))
|
||||||
|
stack.Item().Background(Colors.Grey.Lighten2).Height(40);
|
||||||
|
});
|
||||||
|
|
||||||
|
page.Footer().AlignCenter().Text(text =>
|
||||||
|
{
|
||||||
|
text.DefaultTextStyle(TextStyle.Default.Size(16));
|
||||||
|
|
||||||
|
text.CurrentPageNumber();
|
||||||
|
text.Span(" / ");
|
||||||
|
text.TotalPages();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -474,7 +474,7 @@ namespace QuestPDF.Examples
|
|||||||
.Padding(15)
|
.Padding(15)
|
||||||
.Border(4)
|
.Border(4)
|
||||||
.BorderColor(Colors.Blue.Medium)
|
.BorderColor(Colors.Blue.Medium)
|
||||||
//.Box()
|
//.MinimalBox()
|
||||||
.Background(Colors.Grey.Lighten2)
|
.Background(Colors.Grey.Lighten2)
|
||||||
.Padding(15)
|
.Padding(15)
|
||||||
.Text("Test of the \n box element", TextStyle.Default.Size(20));
|
.Text("Test of the \n box element", TextStyle.Default.Size(20));
|
||||||
@@ -542,7 +542,7 @@ namespace QuestPDF.Examples
|
|||||||
{
|
{
|
||||||
container
|
container
|
||||||
.Background("#FFF")
|
.Background("#FFF")
|
||||||
.Box()
|
.MinimalBox()
|
||||||
|
|
||||||
.Padding(25)
|
.Padding(25)
|
||||||
|
|
||||||
@@ -588,7 +588,7 @@ namespace QuestPDF.Examples
|
|||||||
|
|
||||||
return element;
|
return element;
|
||||||
})
|
})
|
||||||
.Box()
|
.MinimalBox()
|
||||||
.Background(Colors.White)
|
.Background(Colors.White)
|
||||||
.Padding(10)
|
.Padding(10)
|
||||||
.Text($"Rotated {turns * 90}°", TextStyle.Default.Size(16));
|
.Text($"Rotated {turns * 90}°", TextStyle.Default.Size(16));
|
||||||
@@ -686,7 +686,7 @@ namespace QuestPDF.Examples
|
|||||||
|
|
||||||
return element;
|
return element;
|
||||||
})
|
})
|
||||||
.Box()
|
.MinimalBox()
|
||||||
.Background(Colors.White)
|
.Background(Colors.White)
|
||||||
.Padding(10)
|
.Padding(10)
|
||||||
.Text($"Flipped {turns}", TextStyle.Default.Size(16));
|
.Text($"Flipped {turns}", TextStyle.Default.Size(16));
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ namespace QuestPDF.Examples.Engine
|
|||||||
private Size Size { get; set; }
|
private Size Size { get; set; }
|
||||||
private int? MaxPagesThreshold { get; set; }
|
private int? MaxPagesThreshold { get; set; }
|
||||||
private bool ShowResult { get; set; }
|
private bool ShowResult { get; set; }
|
||||||
|
private bool ApplyCaching { get; set; }
|
||||||
|
private bool ApplyDebugging { get; set; }
|
||||||
private RenderingTestResult ResultType { get; set; } = RenderingTestResult.Images;
|
private RenderingTestResult ResultType { get; set; } = RenderingTestResult.Images;
|
||||||
|
|
||||||
private RenderingTest()
|
private RenderingTest()
|
||||||
@@ -67,6 +69,24 @@ namespace QuestPDF.Examples.Engine
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RenderingTest MaxPages(int value)
|
||||||
|
{
|
||||||
|
MaxPagesThreshold = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RenderingTest EnableCaching(bool value = true)
|
||||||
|
{
|
||||||
|
ApplyCaching = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RenderingTest EnableDebugging(bool value = true)
|
||||||
|
{
|
||||||
|
ApplyDebugging = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public void Render(Action<IContainer> content)
|
public void Render(Action<IContainer> content)
|
||||||
{
|
{
|
||||||
RenderDocument(container =>
|
RenderDocument(container =>
|
||||||
@@ -78,16 +98,11 @@ namespace QuestPDF.Examples.Engine
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void MaxPages(int value)
|
|
||||||
{
|
|
||||||
MaxPagesThreshold = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RenderDocument(Action<IDocumentContainer> content)
|
public void RenderDocument(Action<IDocumentContainer> content)
|
||||||
{
|
{
|
||||||
MaxPagesThreshold ??= ResultType == RenderingTestResult.Pdf ? 1000 : 10;
|
MaxPagesThreshold ??= ResultType == RenderingTestResult.Pdf ? 1000 : 10;
|
||||||
var document = new SimpleDocument(content, MaxPagesThreshold.Value);
|
var document = new SimpleDocument(content, MaxPagesThreshold.Value, ApplyCaching, ApplyDebugging);
|
||||||
|
|
||||||
Render(document);
|
Render(document);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,15 @@ namespace QuestPDF.Examples.Engine
|
|||||||
|
|
||||||
private Action<IDocumentContainer> Content { get; }
|
private Action<IDocumentContainer> Content { get; }
|
||||||
private int MaxPages { get; }
|
private int MaxPages { get; }
|
||||||
|
private bool ApplyCaching { get; }
|
||||||
|
private bool ApplyDebugging { get; }
|
||||||
|
|
||||||
public SimpleDocument(Action<IDocumentContainer> content, int maxPages)
|
public SimpleDocument(Action<IDocumentContainer> content, int maxPages, bool applyCaching, bool applyDebugging)
|
||||||
{
|
{
|
||||||
Content = content;
|
Content = content;
|
||||||
MaxPages = maxPages;
|
MaxPages = maxPages;
|
||||||
|
ApplyCaching = applyCaching;
|
||||||
|
ApplyDebugging = applyDebugging;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DocumentMetadata GetMetadata()
|
public DocumentMetadata GetMetadata()
|
||||||
@@ -25,7 +29,9 @@ namespace QuestPDF.Examples.Engine
|
|||||||
return new DocumentMetadata()
|
return new DocumentMetadata()
|
||||||
{
|
{
|
||||||
RasterDpi = PageSizes.PointsPerInch * ImageScalingFactor,
|
RasterDpi = PageSizes.PointsPerInch * ImageScalingFactor,
|
||||||
DocumentLayoutExceptionThreshold = MaxPages
|
DocumentLayoutExceptionThreshold = MaxPages,
|
||||||
|
ApplyCaching = ApplyCaching,
|
||||||
|
ApplyDebugging = ApplyDebugging
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,12 +12,14 @@ namespace QuestPDF.Examples
|
|||||||
{
|
{
|
||||||
return container
|
return container
|
||||||
.Border(1)
|
.Border(1)
|
||||||
.Background(dark ? Colors.Grey.Lighten2 : Colors.White)
|
.BorderColor(Colors.Grey.Medium)
|
||||||
.Padding(10);
|
.Background(dark ? Colors.Grey.Lighten3 : Colors.White)
|
||||||
|
.Padding(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text, TextStyle.Default.Medium());
|
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text, TextStyle.Default.SemiBold());
|
||||||
public static IContainer ValueCell(this IContainer container) => container.Cell(false);
|
public static IContainer ValueCell(this IContainer container) => container.Cell(false);
|
||||||
|
public static void ValueCell(this IContainer container, string text) => container.ValueCell().Text(text, TextStyle.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FrameExample
|
public class FrameExample
|
||||||
@@ -36,7 +38,7 @@ namespace QuestPDF.Examples
|
|||||||
.Padding(25)
|
.Padding(25)
|
||||||
.Stack(stack =>
|
.Stack(stack =>
|
||||||
{
|
{
|
||||||
for(var i=1; i<=4; i++)
|
for(var i = 1; i <= 4; i++)
|
||||||
{
|
{
|
||||||
stack.Item().Row(row =>
|
stack.Item().Row(row =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using QuestPDF.Drawing.Exceptions;
|
||||||
using QuestPDF.Examples.Engine;
|
using QuestPDF.Examples.Engine;
|
||||||
using QuestPDF.Fluent;
|
using QuestPDF.Fluent;
|
||||||
using QuestPDF.Helpers;
|
using QuestPDF.Helpers;
|
||||||
@@ -36,12 +37,15 @@ namespace QuestPDF.Examples
|
|||||||
[Test]
|
[Test]
|
||||||
public void Exception()
|
public void Exception()
|
||||||
{
|
{
|
||||||
RenderingTest
|
Assert.Throws<DocumentComposeException>(() =>
|
||||||
.Create()
|
{
|
||||||
.PageSize(PageSizes.A5)
|
RenderingTest
|
||||||
.ProducePdf()
|
.Create()
|
||||||
.ShowResults()
|
.PageSize(PageSizes.A2)
|
||||||
.Render(page => page.Image("non_existent.png"));
|
.ProducePdf()
|
||||||
|
.ShowResults()
|
||||||
|
.Render(page => page.Image("non_existent.png"));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,7 @@ namespace QuestPDF.Examples
|
|||||||
decoration
|
decoration
|
||||||
.Content()
|
.Content()
|
||||||
.PaddingTop(25)
|
.PaddingTop(25)
|
||||||
//.Box()
|
//.MinimalBox()
|
||||||
.Border(1)
|
.Border(1)
|
||||||
.Background(Colors.Grey.Lighten2)
|
.Background(Colors.Grey.Lighten2)
|
||||||
.Inlined(inlined =>
|
.Inlined(inlined =>
|
||||||
@@ -94,7 +94,7 @@ namespace QuestPDF.Examples
|
|||||||
{
|
{
|
||||||
container
|
container
|
||||||
.Padding(20)
|
.Padding(20)
|
||||||
.Box()
|
.MinimalBox()
|
||||||
.Border(1)
|
.Border(1)
|
||||||
.Background(Colors.Grey.Lighten4)
|
.Background(Colors.Grey.Lighten4)
|
||||||
.Inlined(inlined =>
|
.Inlined(inlined =>
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ namespace QuestPDF.Examples
|
|||||||
RenderingTest
|
RenderingTest
|
||||||
.Create()
|
.Create()
|
||||||
.PageSize(350, 280)
|
.PageSize(350, 280)
|
||||||
|
.ProducePdf()
|
||||||
|
.ShowResults()
|
||||||
.Render(container =>
|
.Render(container =>
|
||||||
{
|
{
|
||||||
container
|
container
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
|
||||||
<PackageReference Include="microcharts" Version="0.9.5.9" />
|
<PackageReference Include="microcharts" Version="0.9.5.9" />
|
||||||
<PackageReference Include="nunit" Version="3.13.2" />
|
<PackageReference Include="nunit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
using NUnit.Framework;
|
||||||
|
using QuestPDF.Examples.Engine;
|
||||||
|
using QuestPDF.Fluent;
|
||||||
|
using QuestPDF.Helpers;
|
||||||
|
using QuestPDF.Infrastructure;
|
||||||
|
|
||||||
|
namespace QuestPDF.Examples
|
||||||
|
{
|
||||||
|
public class RowExamples
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void ColumnTypes()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.ProducePdf()
|
||||||
|
.PageSize(650, 300)
|
||||||
|
.ShowResults()
|
||||||
|
.Render(container =>
|
||||||
|
{
|
||||||
|
container
|
||||||
|
.Padding(25)
|
||||||
|
.MinimalBox()
|
||||||
|
.Border(1)
|
||||||
|
.Stack(stack =>
|
||||||
|
{
|
||||||
|
stack.Item().LabelCell("Total width: 600px");
|
||||||
|
|
||||||
|
stack.Item().Row(row =>
|
||||||
|
{
|
||||||
|
row.ConstantColumn(150).ValueCell("150px");
|
||||||
|
row.ConstantColumn(100).ValueCell("100px");
|
||||||
|
row.RelativeColumn(4).ValueCell("200px");
|
||||||
|
row.RelativeColumn(3).ValueCell("150px");
|
||||||
|
});
|
||||||
|
|
||||||
|
stack.Item().Row(row =>
|
||||||
|
{
|
||||||
|
row.ConstantColumn(100).ValueCell("100px");
|
||||||
|
row.ConstantColumn(50).ValueCell("50px");
|
||||||
|
row.Column(constantWidth: 100, relativeWidth: 4).ValueCell("350px");
|
||||||
|
row.RelativeColumn(2).ValueCell("100px");
|
||||||
|
row.RelativeColumn(1).ValueCell("50px");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Stability()
|
||||||
|
{
|
||||||
|
// up to version 2021.12, this code would always result with the infinite layout exception
|
||||||
|
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.ProducePdf()
|
||||||
|
.MaxPages(100)
|
||||||
|
.PageSize(250, 150)
|
||||||
|
.ShowResults()
|
||||||
|
.Render(container =>
|
||||||
|
{
|
||||||
|
container
|
||||||
|
.Padding(25)
|
||||||
|
.Row(row =>
|
||||||
|
{
|
||||||
|
row.RelativeColumn().Stack(stack =>
|
||||||
|
{
|
||||||
|
stack.Item().ShowOnce().Element(CreateBox).Text("X");
|
||||||
|
stack.Item().Element(CreateBox).Text("1");
|
||||||
|
stack.Item().Element(CreateBox).Text("2");
|
||||||
|
});
|
||||||
|
|
||||||
|
row.RelativeColumn().Stack(stack =>
|
||||||
|
{
|
||||||
|
stack.Item().Element(CreateBox).Text("1");
|
||||||
|
stack.Item().Element(CreateBox).Text("2");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
static IContainer CreateBox(IContainer container)
|
||||||
|
{
|
||||||
|
return container
|
||||||
|
.ExtendHorizontal()
|
||||||
|
.ExtendVertical()
|
||||||
|
.Background(Colors.Grey.Lighten4)
|
||||||
|
.Border(1)
|
||||||
|
.AlignCenter()
|
||||||
|
.AlignMiddle()
|
||||||
|
.ShowOnce();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,465 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using QuestPDF.Drawing;
|
||||||
|
using QuestPDF.Examples.Engine;
|
||||||
|
using QuestPDF.Fluent;
|
||||||
|
using QuestPDF.Helpers;
|
||||||
|
using QuestPDF.Infrastructure;
|
||||||
|
using IContainer = QuestPDF.Infrastructure.IContainer;
|
||||||
|
|
||||||
|
namespace QuestPDF.Examples
|
||||||
|
{
|
||||||
|
public class TableExamples
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void BasicPlacement()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.ProduceImages()
|
||||||
|
.PageSize(220, 220)
|
||||||
|
.ShowResults()
|
||||||
|
.Render(container =>
|
||||||
|
{
|
||||||
|
container
|
||||||
|
.Padding(10)
|
||||||
|
.MinimalBox()
|
||||||
|
.Border(1)
|
||||||
|
.Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(columns =>
|
||||||
|
{
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.RelativeColumn();
|
||||||
|
});
|
||||||
|
|
||||||
|
// by using custom 'Element' method, we can reuse visual configuration
|
||||||
|
table.Cell().Row(1).Column(4).Element(Block).Text("A");
|
||||||
|
table.Cell().Row(2).Column(2).Element(Block).Text("B");
|
||||||
|
table.Cell().Row(3).Column(3).Element(Block).Text("C");
|
||||||
|
table.Cell().Row(4).Column(1).Element(Block).Text("D");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void DefaultCellStyle()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.ProduceImages()
|
||||||
|
.PageSize(220, 120)
|
||||||
|
.ShowResults()
|
||||||
|
.Render(container =>
|
||||||
|
{
|
||||||
|
container
|
||||||
|
.Padding(10)
|
||||||
|
.MinimalBox()
|
||||||
|
.Border(1)
|
||||||
|
.DefaultTextStyle(TextStyle.Default.Size(16))
|
||||||
|
.Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(columns =>
|
||||||
|
{
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.RelativeColumn();
|
||||||
|
});
|
||||||
|
|
||||||
|
table.Cell().Row(1).Column(1).Element(Block).Text("A");
|
||||||
|
table.Cell().Row(2).Column(2).Element(Block).Text("B");
|
||||||
|
table.Cell().Row(1).Column(3).Element(Block).Text("C");
|
||||||
|
table.Cell().Row(2).Column(4).Element(Block).Text("D");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ColumnsDefinition()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.ProduceImages()
|
||||||
|
.PageSize(320, 80)
|
||||||
|
.ShowResults()
|
||||||
|
.Render(container =>
|
||||||
|
{
|
||||||
|
container
|
||||||
|
.Padding(10)
|
||||||
|
.Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(columns =>
|
||||||
|
{
|
||||||
|
columns.ConstantColumn(50);
|
||||||
|
columns.ConstantColumn(100);
|
||||||
|
columns.RelativeColumn(2);
|
||||||
|
columns.RelativeColumn(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
table.Cell().ColumnSpan(4).LabelCell("Total width: 300px");
|
||||||
|
table.Cell().ValueCell("50px");
|
||||||
|
table.Cell().ValueCell("100px");
|
||||||
|
table.Cell().ValueCell("100px");
|
||||||
|
table.Cell().ValueCell("150px");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void PartialAutoPlacement()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.ProduceImages()
|
||||||
|
.PageSize(220, 220)
|
||||||
|
.ShowResults()
|
||||||
|
.Render(container =>
|
||||||
|
{
|
||||||
|
container
|
||||||
|
.Padding(10)
|
||||||
|
.MinimalBox()
|
||||||
|
.Border(1)
|
||||||
|
.Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(columns =>
|
||||||
|
{
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.RelativeColumn();
|
||||||
|
});
|
||||||
|
|
||||||
|
table.Cell().Element(Block).Text("A");
|
||||||
|
table.Cell().Row(2).Column(2).Element(Block).Text("B");
|
||||||
|
table.Cell().Element(Block).Text("C");
|
||||||
|
table.Cell().Row(3).Column(3).Element(Block).Text("D");
|
||||||
|
table.Cell().ColumnSpan(2).Element(Block).Text("E");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ExtendLastCellsToTableBottom()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.ProduceImages()
|
||||||
|
.PageSize(220, 170)
|
||||||
|
.ShowResults()
|
||||||
|
.Render(container =>
|
||||||
|
{
|
||||||
|
container
|
||||||
|
.Padding(10)
|
||||||
|
.MinimalBox()
|
||||||
|
.Border(1)
|
||||||
|
.Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(columns =>
|
||||||
|
{
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.RelativeColumn();
|
||||||
|
});
|
||||||
|
|
||||||
|
table.ExtendLastCellsToTableBottom();
|
||||||
|
|
||||||
|
table.Cell().Row(1).Column(1).Element(Block).Text("A");
|
||||||
|
table.Cell().Row(3).Column(1).Element(Block).Text("B");
|
||||||
|
table.Cell().Row(2).Column(2).Element(Block).Text("C");
|
||||||
|
table.Cell().Row(3).Column(3).Element(Block).Text("D");
|
||||||
|
table.Cell().Row(2).RowSpan(2).Column(4).Element(Block).Text("E");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Overlapping()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.ProduceImages()
|
||||||
|
.PageSize(170, 170)
|
||||||
|
.ShowResults()
|
||||||
|
.Render(container =>
|
||||||
|
{
|
||||||
|
container
|
||||||
|
.Padding(10)
|
||||||
|
.MinimalBox()
|
||||||
|
.Border(1)
|
||||||
|
.Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(columns =>
|
||||||
|
{
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.RelativeColumn();
|
||||||
|
});
|
||||||
|
|
||||||
|
table.Cell().Row(1).RowSpan(3).Column(1).ColumnSpan(3).Background(Colors.Grey.Lighten3).MinHeight(150);
|
||||||
|
table.Cell().Row(1).RowSpan(2).Column(1).ColumnSpan(2).Background(Colors.Grey.Lighten1).MinHeight(100);
|
||||||
|
table.Cell().Row(3).Column(3).Background(Colors.Grey.Darken1).MinHeight(50);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Spans()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.ProduceImages()
|
||||||
|
.PageSize(220, 220)
|
||||||
|
.ShowResults()
|
||||||
|
.Render(container =>
|
||||||
|
{
|
||||||
|
container
|
||||||
|
.Padding(10)
|
||||||
|
.Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(columns =>
|
||||||
|
{
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.RelativeColumn();
|
||||||
|
});
|
||||||
|
|
||||||
|
table.Cell().RowSpan(2).ColumnSpan(2).Element(Block).Text("1");
|
||||||
|
table.Cell().ColumnSpan(2).Element(Block).Text("2");
|
||||||
|
table.Cell().Element(Block).Text("3");
|
||||||
|
table.Cell().Element(Block).Text("4");
|
||||||
|
table.Cell().RowSpan(2).Element(Block).Text("5");
|
||||||
|
table.Cell().ColumnSpan(2).Element(Block).Text("6");
|
||||||
|
table.Cell().RowSpan(2).Element(Block).Text("7");
|
||||||
|
table.Cell().Element(Block).Text("8");
|
||||||
|
table.Cell().Element(Block).Text("9");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Stability()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.ProduceImages()
|
||||||
|
.PageSize(300, 300)
|
||||||
|
.ShowResults()
|
||||||
|
.Render(container =>
|
||||||
|
{
|
||||||
|
container
|
||||||
|
.Padding(10)
|
||||||
|
.Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(columns =>
|
||||||
|
{
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.RelativeColumn();
|
||||||
|
});
|
||||||
|
|
||||||
|
table.Cell().RowSpan(4).Element(Block).Text("1");
|
||||||
|
|
||||||
|
table.Cell().RowSpan(2).Element(Block).Text("2");
|
||||||
|
table.Cell().RowSpan(1).Element(Block).Text("3");
|
||||||
|
table.Cell().RowSpan(1).Element(Block).Text("4");
|
||||||
|
|
||||||
|
table.Cell().RowSpan(2).Element(Block).Text("5");
|
||||||
|
table.Cell().RowSpan(1).Element(Block).Text("6");
|
||||||
|
table.Cell().RowSpan(1).Element(Block).Text("7");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TableHeader()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.ProduceImages()
|
||||||
|
.PageSize(500, 200)
|
||||||
|
.ShowResults()
|
||||||
|
.EnableDebugging()
|
||||||
|
.Render(container =>
|
||||||
|
{
|
||||||
|
var pageSizes = new List<(string name, double width, double height)>()
|
||||||
|
{
|
||||||
|
("Letter (ANSI A)", 8.5f, 11),
|
||||||
|
("Legal", 8.5f, 14),
|
||||||
|
("Ledger (ANSI B)", 11, 17),
|
||||||
|
("Tabloid (ANSI B)", 17, 11),
|
||||||
|
("ANSI C", 22, 17),
|
||||||
|
("ANSI D", 34, 22),
|
||||||
|
("ANSI E", 44, 34)
|
||||||
|
};
|
||||||
|
|
||||||
|
const int inchesToPoints = 72;
|
||||||
|
|
||||||
|
container
|
||||||
|
.Padding(10)
|
||||||
|
.MinimalBox()
|
||||||
|
.Border(1)
|
||||||
|
.Table(table =>
|
||||||
|
{
|
||||||
|
IContainer DefaultCellStyle(IContainer container, string backgroundColor)
|
||||||
|
{
|
||||||
|
return container
|
||||||
|
.Border(1)
|
||||||
|
.BorderColor(Colors.Grey.Lighten1)
|
||||||
|
.Background(backgroundColor)
|
||||||
|
.PaddingVertical(5)
|
||||||
|
.PaddingHorizontal(10)
|
||||||
|
.AlignCenter()
|
||||||
|
.AlignMiddle();
|
||||||
|
}
|
||||||
|
|
||||||
|
table.ColumnsDefinition(columns =>
|
||||||
|
{
|
||||||
|
columns.RelativeColumn();
|
||||||
|
|
||||||
|
columns.ConstantColumn(75);
|
||||||
|
columns.ConstantColumn(75);
|
||||||
|
|
||||||
|
columns.ConstantColumn(75);
|
||||||
|
columns.ConstantColumn(75);
|
||||||
|
});
|
||||||
|
|
||||||
|
table.Header(header =>
|
||||||
|
{
|
||||||
|
header.Cell().RowSpan(2).Element(CellStyle).ExtendHorizontal().AlignLeft().Text("Document type");
|
||||||
|
|
||||||
|
header.Cell().ColumnSpan(2).Element(CellStyle).Text("Inches");
|
||||||
|
header.Cell().ColumnSpan(2).Element(CellStyle).Text("Points");
|
||||||
|
|
||||||
|
header.Cell().Element(CellStyle).Text("Width");
|
||||||
|
header.Cell().Element(CellStyle).Text("Height");
|
||||||
|
|
||||||
|
header.Cell().Element(CellStyle).Text("Width");
|
||||||
|
header.Cell().Element(CellStyle).Text("Height");
|
||||||
|
|
||||||
|
// you can extend already existing styles by creating additional methods
|
||||||
|
IContainer CellStyle(IContainer container) => DefaultCellStyle(container, Colors.Grey.Lighten3);
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (var page in pageSizes)
|
||||||
|
{
|
||||||
|
table.Cell().Element(CellStyle).ExtendHorizontal().AlignLeft().Text(page.name);
|
||||||
|
|
||||||
|
// inches
|
||||||
|
table.Cell().Element(CellStyle).Text(page.width);
|
||||||
|
table.Cell().Element(CellStyle).Text(page.height);
|
||||||
|
|
||||||
|
// points
|
||||||
|
table.Cell().Element(CellStyle).Text(page.width * inchesToPoints);
|
||||||
|
table.Cell().Element(CellStyle).Text(page.height * inchesToPoints);
|
||||||
|
|
||||||
|
IContainer CellStyle(IContainer container) => DefaultCellStyle(container, Colors.White);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void PerformanceText_TemperatureReport()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.ProducePdf()
|
||||||
|
.PageSize(PageSizes.A4)
|
||||||
|
.MaxPages(10_000)
|
||||||
|
.EnableCaching()
|
||||||
|
.EnableDebugging(false)
|
||||||
|
.ShowResults()
|
||||||
|
.Render(container => GeneratePerformanceStructure(container, 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void GeneratePerformanceStructure(IContainer container, int repeats)
|
||||||
|
{
|
||||||
|
container
|
||||||
|
.Padding(25)
|
||||||
|
//.Background(Colors.Blue.Lighten2)
|
||||||
|
.MinimalBox()
|
||||||
|
.Border(1)
|
||||||
|
//.Background(Colors.Red.Lighten2)
|
||||||
|
.Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(columns =>
|
||||||
|
{
|
||||||
|
columns.ConstantColumn(100);
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.ConstantColumn(100);
|
||||||
|
columns.RelativeColumn();
|
||||||
|
});
|
||||||
|
|
||||||
|
table.ExtendLastCellsToTableBottom();
|
||||||
|
|
||||||
|
foreach (var i in Enumerable.Range(0, repeats))
|
||||||
|
{
|
||||||
|
table.Cell().RowSpan(3).LabelCell("Project");
|
||||||
|
table.Cell().RowSpan(3).ShowEntire().ValueCell(Placeholders.Sentence());
|
||||||
|
|
||||||
|
table.Cell().LabelCell("Report number");
|
||||||
|
table.Cell().ValueCell(i.ToString());
|
||||||
|
|
||||||
|
table.Cell().LabelCell("Date");
|
||||||
|
table.Cell().ValueCell(Placeholders.ShortDate());
|
||||||
|
|
||||||
|
table.Cell().LabelCell("Inspector");
|
||||||
|
table.Cell().ValueCell("Marcin Ziąbek");
|
||||||
|
|
||||||
|
table.Cell().ColumnSpan(2).LabelCell("Morning weather");
|
||||||
|
table.Cell().ColumnSpan(2).LabelCell("Evening weather");
|
||||||
|
|
||||||
|
table.Cell().ValueCell("Time");
|
||||||
|
table.Cell().ValueCell("7:13");
|
||||||
|
|
||||||
|
table.Cell().ValueCell("Time");
|
||||||
|
table.Cell().ValueCell("18:25");
|
||||||
|
|
||||||
|
table.Cell().ValueCell("Description");
|
||||||
|
table.Cell().ValueCell("Sunny");
|
||||||
|
|
||||||
|
table.Cell().ValueCell("Description");
|
||||||
|
table.Cell().ValueCell("Windy");
|
||||||
|
|
||||||
|
table.Cell().ValueCell("Wind");
|
||||||
|
table.Cell().ValueCell("Mild");
|
||||||
|
|
||||||
|
table.Cell().ValueCell("Wind");
|
||||||
|
table.Cell().ValueCell("Strong");
|
||||||
|
|
||||||
|
table.Cell().ValueCell("Temperature");
|
||||||
|
table.Cell().ValueCell("17°C");
|
||||||
|
|
||||||
|
table.Cell().ValueCell("Temperature");
|
||||||
|
table.Cell().ValueCell("32°C");
|
||||||
|
|
||||||
|
table.Cell().LabelCell("Remarks");
|
||||||
|
table.Cell().ColumnSpan(3).ValueCell(Placeholders.Paragraph());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// this method uses a higher order function to define a custom and dynamic style
|
||||||
|
static IContainer Block(IContainer container)
|
||||||
|
{
|
||||||
|
return container
|
||||||
|
.Border(1)
|
||||||
|
.Background(Colors.Grey.Lighten3)
|
||||||
|
.MinWidth(50)
|
||||||
|
.MinHeight(50)
|
||||||
|
.AlignCenter()
|
||||||
|
.AlignMiddle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ namespace QuestPDF.Examples
|
|||||||
{
|
{
|
||||||
container
|
container
|
||||||
.Padding(5)
|
.Padding(5)
|
||||||
.Box()
|
.MinimalBox()
|
||||||
.Border(1)
|
.Border(1)
|
||||||
.Padding(10)
|
.Padding(10)
|
||||||
.Text(text =>
|
.Text(text =>
|
||||||
@@ -48,7 +48,7 @@ namespace QuestPDF.Examples
|
|||||||
{
|
{
|
||||||
container
|
container
|
||||||
.Padding(5)
|
.Padding(5)
|
||||||
.Box()
|
.MinimalBox()
|
||||||
.Border(1)
|
.Border(1)
|
||||||
.Padding(10)
|
.Padding(10)
|
||||||
.Text(text =>
|
.Text(text =>
|
||||||
@@ -76,7 +76,7 @@ namespace QuestPDF.Examples
|
|||||||
{
|
{
|
||||||
container
|
container
|
||||||
.Padding(5)
|
.Padding(5)
|
||||||
.Box()
|
.MinimalBox()
|
||||||
.Border(1)
|
.Border(1)
|
||||||
.Padding(10)
|
.Padding(10)
|
||||||
.Text(text =>
|
.Text(text =>
|
||||||
@@ -108,7 +108,7 @@ namespace QuestPDF.Examples
|
|||||||
container
|
container
|
||||||
.Padding(20)
|
.Padding(20)
|
||||||
.Padding(10)
|
.Padding(10)
|
||||||
.Box()
|
.MinimalBox()
|
||||||
.Border(1)
|
.Border(1)
|
||||||
.Padding(5)
|
.Padding(5)
|
||||||
.Padding(10)
|
.Padding(10)
|
||||||
@@ -138,7 +138,7 @@ namespace QuestPDF.Examples
|
|||||||
container
|
container
|
||||||
.Padding(20)
|
.Padding(20)
|
||||||
.Padding(10)
|
.Padding(10)
|
||||||
.Box()
|
.MinimalBox()
|
||||||
.Border(1)
|
.Border(1)
|
||||||
.Padding(5)
|
.Padding(5)
|
||||||
.Padding(10)
|
.Padding(10)
|
||||||
@@ -167,7 +167,7 @@ namespace QuestPDF.Examples
|
|||||||
container
|
container
|
||||||
.Padding(20)
|
.Padding(20)
|
||||||
.Padding(10)
|
.Padding(10)
|
||||||
.Box()
|
.MinimalBox()
|
||||||
.Border(1)
|
.Border(1)
|
||||||
.Padding(5)
|
.Padding(5)
|
||||||
.Padding(10)
|
.Padding(10)
|
||||||
@@ -230,7 +230,7 @@ namespace QuestPDF.Examples
|
|||||||
container
|
container
|
||||||
.Padding(20)
|
.Padding(20)
|
||||||
.Padding(10)
|
.Padding(10)
|
||||||
.Box()
|
.MinimalBox()
|
||||||
.Border(1)
|
.Border(1)
|
||||||
.Padding(5)
|
.Padding(5)
|
||||||
.Padding(10)
|
.Padding(10)
|
||||||
|
|||||||
@@ -31,12 +31,12 @@ namespace QuestPDF.ReportSample.Layouts
|
|||||||
stack.Item().ShowOnce().Padding(5).AlignMiddle().Row(row =>
|
stack.Item().ShowOnce().Padding(5).AlignMiddle().Row(row =>
|
||||||
{
|
{
|
||||||
row.RelativeColumn(2).AlignMiddle().Text("PRIMARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
|
row.RelativeColumn(2).AlignMiddle().Text("PRIMARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
|
||||||
row.RelativeColumn(1).AlignRight().Box().AlignMiddle().Background(Colors.Blue.Darken2).Padding(30);
|
row.RelativeColumn(1).AlignRight().MinimalBox().AlignMiddle().Background(Colors.Blue.Darken2).Padding(30);
|
||||||
});
|
});
|
||||||
stack.Item().SkipOnce().Padding(5).Row(row =>
|
stack.Item().SkipOnce().Padding(5).Row(row =>
|
||||||
{
|
{
|
||||||
row.RelativeColumn(2).Text("SECONDARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
|
row.RelativeColumn(2).Text("SECONDARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
|
||||||
row.RelativeColumn(1).AlignRight().Box().Background(Colors.Blue.Lighten4).Padding(15);
|
row.RelativeColumn(1).AlignRight().MinimalBox().Background(Colors.Blue.Lighten4).Padding(15);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using NUnit.Framework;
|
|||||||
using QuestPDF.Drawing;
|
using QuestPDF.Drawing;
|
||||||
using QuestPDF.Drawing.Proxy;
|
using QuestPDF.Drawing.Proxy;
|
||||||
using QuestPDF.Elements;
|
using QuestPDF.Elements;
|
||||||
|
using QuestPDF.Helpers;
|
||||||
using QuestPDF.Infrastructure;
|
using QuestPDF.Infrastructure;
|
||||||
using QuestPDF.ReportSample.Layouts;
|
using QuestPDF.ReportSample.Layouts;
|
||||||
|
|
||||||
@@ -55,7 +56,7 @@ namespace QuestPDF.ReportSample
|
|||||||
var sw = new Stopwatch();
|
var sw = new Stopwatch();
|
||||||
sw.Start();
|
sw.Start();
|
||||||
|
|
||||||
Content.HandleVisitor(x =>
|
Content.VisitChildren(x =>
|
||||||
{
|
{
|
||||||
if (x is ICacheable)
|
if (x is ICacheable)
|
||||||
x.CreateProxy(y => new CacheProxy(y));
|
x.CreateProxy(y => new CacheProxy(y));
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ namespace QuestPDF.ReportSample
|
|||||||
[Test]
|
[Test]
|
||||||
public void GenerateAndShowPdf()
|
public void GenerateAndShowPdf()
|
||||||
{
|
{
|
||||||
|
ImagePlaceholder.Solid = true;
|
||||||
|
|
||||||
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.pdf");
|
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.pdf");
|
||||||
Report.GeneratePdf(path);
|
Report.GeneratePdf(path);
|
||||||
Process.Start("explorer.exe", path);
|
Process.Start("explorer.exe", path);
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ namespace QuestPDF.UnitTests
|
|||||||
public class BoxTests
|
public class BoxTests
|
||||||
{
|
{
|
||||||
[Test]
|
[Test]
|
||||||
public void Measure() => SimpleContainerTests.Measure<Box>();
|
public void Measure() => SimpleContainerTests.Measure<MinimalBox>();
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Draw_Wrap()
|
public void Draw_Wrap()
|
||||||
{
|
{
|
||||||
TestPlan
|
TestPlan
|
||||||
.For(x => new Box
|
.For(x => new MinimalBox
|
||||||
{
|
{
|
||||||
Child = x.CreateChild()
|
Child = x.CreateChild()
|
||||||
})
|
})
|
||||||
@@ -29,7 +29,7 @@ namespace QuestPDF.UnitTests
|
|||||||
public void Measure_PartialRender()
|
public void Measure_PartialRender()
|
||||||
{
|
{
|
||||||
TestPlan
|
TestPlan
|
||||||
.For(x => new Box
|
.For(x => new MinimalBox
|
||||||
{
|
{
|
||||||
Child = x.CreateChild()
|
Child = x.CreateChild()
|
||||||
})
|
})
|
||||||
@@ -43,7 +43,7 @@ namespace QuestPDF.UnitTests
|
|||||||
public void Measure_FullRender()
|
public void Measure_FullRender()
|
||||||
{
|
{
|
||||||
TestPlan
|
TestPlan
|
||||||
.For(x => new Box
|
.For(x => new MinimalBox
|
||||||
{
|
{
|
||||||
Child = x.CreateChild()
|
Child = x.CreateChild()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ namespace QuestPDF.UnitTests.TestEngine
|
|||||||
|
|
||||||
public TestPlan CheckMeasureResult(SpacePlan expected)
|
public TestPlan CheckMeasureResult(SpacePlan expected)
|
||||||
{
|
{
|
||||||
Element.HandleVisitor(x => x?.Initialize(null, Canvas));
|
Element.VisitChildren(x => x?.Initialize(null, Canvas));
|
||||||
|
|
||||||
var actual = Element.Measure(OperationInput);
|
var actual = Element.Measure(OperationInput);
|
||||||
|
|
||||||
@@ -228,7 +228,7 @@ namespace QuestPDF.UnitTests.TestEngine
|
|||||||
|
|
||||||
public TestPlan CheckDrawResult()
|
public TestPlan CheckDrawResult()
|
||||||
{
|
{
|
||||||
Element.HandleVisitor(x => x?.Initialize(null, Canvas));
|
Element.VisitChildren(x => x?.Initialize(null, Canvas));
|
||||||
Element.Draw(OperationInput);
|
Element.Draw(OperationInput);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -271,10 +271,10 @@ namespace QuestPDF.UnitTests.TestEngine
|
|||||||
availableSpace ??= new Size(400, 300);
|
availableSpace ??= new Size(400, 300);
|
||||||
|
|
||||||
var canvas = new FreeCanvas();
|
var canvas = new FreeCanvas();
|
||||||
value.HandleVisitor(x => x.Initialize(null, canvas));
|
value.VisitChildren(x => x.Initialize(null, canvas));
|
||||||
var valueMeasure = value.Measure(availableSpace.Value);
|
var valueMeasure = value.Measure(availableSpace.Value);
|
||||||
|
|
||||||
expected.HandleVisitor(x => x.Initialize(null, canvas));
|
expected.VisitChildren(x => x.Initialize(null, canvas));
|
||||||
var expectedMeasure = expected.Measure(availableSpace.Value);
|
var expectedMeasure = expected.Measure(availableSpace.Value);
|
||||||
|
|
||||||
valueMeasure.Should().BeEquivalentTo(expectedMeasure);
|
valueMeasure.Should().BeEquivalentTo(expectedMeasure);
|
||||||
@@ -285,11 +285,11 @@ namespace QuestPDF.UnitTests.TestEngine
|
|||||||
availableSpace ??= new Size(400, 300);
|
availableSpace ??= new Size(400, 300);
|
||||||
|
|
||||||
var valueCanvas = new OperationRecordingCanvas();
|
var valueCanvas = new OperationRecordingCanvas();
|
||||||
value.HandleVisitor(x => x.Initialize(null, valueCanvas));
|
value.VisitChildren(x => x.Initialize(null, valueCanvas));
|
||||||
value.Draw(availableSpace.Value);
|
value.Draw(availableSpace.Value);
|
||||||
|
|
||||||
var expectedCanvas = new OperationRecordingCanvas();
|
var expectedCanvas = new OperationRecordingCanvas();
|
||||||
expected.HandleVisitor(x => x.Initialize(null, expectedCanvas));
|
expected.VisitChildren(x => x.Initialize(null, expectedCanvas));
|
||||||
expected.Draw(availableSpace.Value);
|
expected.Draw(availableSpace.Value);
|
||||||
|
|
||||||
valueCanvas.Operations.Should().BeEquivalentTo(expectedCanvas.Operations);
|
valueCanvas.Operations.Should().BeEquivalentTo(expectedCanvas.Operations);
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ using QuestPDF.Drawing.Proxy;
|
|||||||
using QuestPDF.Elements;
|
using QuestPDF.Elements;
|
||||||
using QuestPDF.Elements.Text;
|
using QuestPDF.Elements.Text;
|
||||||
using QuestPDF.Elements.Text.Items;
|
using QuestPDF.Elements.Text.Items;
|
||||||
|
using QuestPDF.Fluent;
|
||||||
|
using QuestPDF.Helpers;
|
||||||
using QuestPDF.Infrastructure;
|
using QuestPDF.Infrastructure;
|
||||||
|
|
||||||
namespace QuestPDF.Drawing
|
namespace QuestPDF.Drawing
|
||||||
@@ -42,20 +44,15 @@ namespace QuestPDF.Drawing
|
|||||||
var container = new DocumentContainer();
|
var container = new DocumentContainer();
|
||||||
document.Compose(container);
|
document.Compose(container);
|
||||||
var content = container.Compose();
|
var content = container.Compose();
|
||||||
|
ApplyDefaultTextStyle(content, TextStyle.LibraryDefault);
|
||||||
|
|
||||||
var metadata = document.GetMetadata();
|
var metadata = document.GetMetadata();
|
||||||
var pageContext = new PageContext();
|
var pageContext = new PageContext();
|
||||||
|
|
||||||
DebuggingState debuggingState = null;
|
var debuggingState = metadata.ApplyDebugging ? ApplyDebugging(content) : null;
|
||||||
|
|
||||||
if (System.Diagnostics.Debugger.IsAttached)
|
if (metadata.ApplyCaching)
|
||||||
{
|
|
||||||
debuggingState = ApplyDebugging(content);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ApplyCaching(content);
|
ApplyCaching(content);
|
||||||
}
|
|
||||||
|
|
||||||
RenderPass(pageContext, new FreeCanvas(), content, metadata, debuggingState);
|
RenderPass(pageContext, new FreeCanvas(), content, metadata, debuggingState);
|
||||||
RenderPass(pageContext, canvas, content, metadata, debuggingState);
|
RenderPass(pageContext, canvas, content, metadata, debuggingState);
|
||||||
@@ -64,8 +61,8 @@ namespace QuestPDF.Drawing
|
|||||||
internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DocumentMetadata documentMetadata, DebuggingState? debuggingState)
|
internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DocumentMetadata documentMetadata, DebuggingState? debuggingState)
|
||||||
where TCanvas : ICanvas, IRenderingCanvas
|
where TCanvas : ICanvas, IRenderingCanvas
|
||||||
{
|
{
|
||||||
content.HandleVisitor(x => x?.Initialize(pageContext, canvas));
|
content.VisitChildren(x => x?.Initialize(pageContext, canvas));
|
||||||
content.HandleVisitor(x => (x as IStateResettable)?.ResetState());
|
content.VisitChildren(x => (x as IStateResettable)?.ResetState());
|
||||||
|
|
||||||
canvas.BeginDocument();
|
canvas.BeginDocument();
|
||||||
|
|
||||||
@@ -128,7 +125,7 @@ namespace QuestPDF.Drawing
|
|||||||
|
|
||||||
private static void ApplyCaching(Container content)
|
private static void ApplyCaching(Container content)
|
||||||
{
|
{
|
||||||
content.HandleVisitor(x =>
|
content.VisitChildren(x =>
|
||||||
{
|
{
|
||||||
if (x is ICacheable)
|
if (x is ICacheable)
|
||||||
x.CreateProxy(y => new CacheProxy(y));
|
x.CreateProxy(y => new CacheProxy(y));
|
||||||
@@ -139,7 +136,7 @@ namespace QuestPDF.Drawing
|
|||||||
{
|
{
|
||||||
var debuggingState = new DebuggingState();
|
var debuggingState = new DebuggingState();
|
||||||
|
|
||||||
content.HandleVisitor(x =>
|
content.VisitChildren(x =>
|
||||||
{
|
{
|
||||||
x.CreateProxy(y => new DebuggingProxy(debuggingState, y));
|
x.CreateProxy(y => new DebuggingProxy(debuggingState, y));
|
||||||
});
|
});
|
||||||
@@ -147,26 +144,38 @@ namespace QuestPDF.Drawing
|
|||||||
return debuggingState;
|
return debuggingState;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void ApplyDefaultTextStyle(this Element content, TextStyle documentDefaultTextStyle)
|
private static void ApplyDefaultTextStyle(this Element? content, TextStyle documentDefaultTextStyle)
|
||||||
{
|
{
|
||||||
documentDefaultTextStyle.ApplyGlobalStyle(TextStyle.LibraryDefault);
|
if (content == null)
|
||||||
|
return;
|
||||||
|
|
||||||
content.HandleVisitor(element =>
|
if (content is TextBlock textBlock)
|
||||||
{
|
{
|
||||||
var text = element as TextBlock;
|
foreach (var textBlockItem in textBlock.Items)
|
||||||
|
|
||||||
if (text == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var child in text.Children)
|
|
||||||
{
|
{
|
||||||
if (child is TextBlockSpan textSpan)
|
if (textBlockItem is TextBlockSpan textSpan)
|
||||||
|
{
|
||||||
textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle);
|
textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle);
|
||||||
|
}
|
||||||
if (child is TextBlockElement textElement)
|
else if (textBlockItem is TextBlockElement textElement)
|
||||||
|
{
|
||||||
ApplyDefaultTextStyle(textElement.Element, documentDefaultTextStyle);
|
ApplyDefaultTextStyle(textElement.Element, documentDefaultTextStyle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetTextStyle = documentDefaultTextStyle;
|
||||||
|
|
||||||
|
if (content is DefaultTextStyle defaultTextStyleElement)
|
||||||
|
{
|
||||||
|
defaultTextStyleElement.TextStyle.ApplyParentStyle(documentDefaultTextStyle);
|
||||||
|
targetTextStyle = defaultTextStyleElement.TextStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var child in content.GetChildren())
|
||||||
|
ApplyDefaultTextStyle(child, targetTextStyle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,10 @@ namespace QuestPDF.Drawing
|
|||||||
/// (likely due to infinite layout), the exception is thrown.
|
/// (likely due to infinite layout), the exception is thrown.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int DocumentLayoutExceptionThreshold { get; set; } = 250;
|
public int DocumentLayoutExceptionThreshold { get; set; } = 250;
|
||||||
|
|
||||||
|
public bool ApplyCaching { get; set; } = !System.Diagnostics.Debugger.IsAttached;
|
||||||
|
public bool ApplyDebugging { get; set; } = System.Diagnostics.Debugger.IsAttached;
|
||||||
|
|
||||||
public static DocumentMetadata Default => new DocumentMetadata();
|
public static DocumentMetadata Default => new DocumentMetadata();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,7 @@ namespace QuestPDF.Drawing.Proxy
|
|||||||
title += debugPointer.Highlight ? " 🌟" : string.Empty;
|
title += debugPointer.Highlight ? " 🌟" : string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.SpacePlan.Type == SpacePlanType.Wrap)
|
if (item.SpacePlan.Type != SpacePlanType.FullRender)
|
||||||
title = "🔥 " + title;
|
title = "🔥 " + title;
|
||||||
|
|
||||||
builder.AppendLine(indent + title);
|
builder.AppendLine(indent + title);
|
||||||
@@ -109,7 +109,7 @@ namespace QuestPDF.Drawing.Proxy
|
|||||||
Value = x.GetValue(element)
|
Value = x.GetValue(element)
|
||||||
})
|
})
|
||||||
.Where(x => !(x.Value is IElement))
|
.Where(x => !(x.Value is IElement))
|
||||||
.Where(x => !(x.Value is IEnumerable<IElement>))
|
.Where(x => x.Value is string || !(x.Value is IEnumerable))
|
||||||
.Where(x => !(x.Value is TextStyle))
|
.Where(x => !(x.Value is TextStyle))
|
||||||
.Select(x => $"{x.Property}: {FormatValue(x.Value)}");
|
.Select(x => $"{x.Property}: {FormatValue(x.Value)}");
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ namespace QuestPDF.Drawing
|
|||||||
if (Type == SpacePlanType.Wrap)
|
if (Type == SpacePlanType.Wrap)
|
||||||
return Type.ToString();
|
return Type.ToString();
|
||||||
|
|
||||||
return $"{Type} | width {Width:N2} | height {Height:N2}";
|
return $"{Type} (Width: {Width:N3}, Height: {Height:N3})";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static implicit operator Size(SpacePlan spacePlan)
|
public static implicit operator Size(SpacePlan spacePlan)
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ namespace QuestPDF.Elements
|
|||||||
if (Handler == null || skiaCanvas == null)
|
if (Handler == null || skiaCanvas == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var currentMatrix = skiaCanvas.TotalMatrix;
|
var originalMatrix = skiaCanvas.TotalMatrix;
|
||||||
Handler.Invoke(skiaCanvas, availableSpace);
|
Handler.Invoke(skiaCanvas, availableSpace);
|
||||||
skiaCanvas.SetMatrix(currentMatrix);
|
skiaCanvas.SetMatrix(originalMatrix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,7 @@ namespace QuestPDF.Elements
|
|||||||
.Layer()
|
.Layer()
|
||||||
.ShowIf(!string.IsNullOrWhiteSpace(Text))
|
.ShowIf(!string.IsNullOrWhiteSpace(Text))
|
||||||
.AlignCenter()
|
.AlignCenter()
|
||||||
.Box()
|
.MinimalBox()
|
||||||
.Background(Colors.White)
|
.Background(Colors.White)
|
||||||
.Padding(2)
|
.Padding(2)
|
||||||
.Text(Text, TextStyle.Default.Color(Color).FontType(Fonts.Consolas).Size(8));
|
.Text(Text, TextStyle.Default.Color(Color).FontType(Fonts.Consolas).Size(8));
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using QuestPDF.Drawing;
|
using QuestPDF.Drawing;
|
||||||
using QuestPDF.Fluent;
|
using QuestPDF.Fluent;
|
||||||
using QuestPDF.Infrastructure;
|
using QuestPDF.Infrastructure;
|
||||||
@@ -17,12 +18,10 @@ namespace QuestPDF.Elements
|
|||||||
public Element ContentElement { get; set; } = Empty.Instance;
|
public Element ContentElement { get; set; } = Empty.Instance;
|
||||||
public DecorationType Type { get; set; }
|
public DecorationType Type { get; set; }
|
||||||
|
|
||||||
internal override void HandleVisitor(Action<Element?> visit)
|
internal override IEnumerable<Element?> GetChildren()
|
||||||
{
|
{
|
||||||
DecorationElement?.HandleVisitor(visit);
|
yield return DecorationElement;
|
||||||
ContentElement?.HandleVisitor(visit);
|
yield return ContentElement;
|
||||||
|
|
||||||
base.HandleVisitor(visit);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void CreateProxy(Func<Element, Element> create)
|
internal override void CreateProxy(Func<Element, Element> create)
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using QuestPDF.Drawing;
|
||||||
|
using QuestPDF.Infrastructure;
|
||||||
|
|
||||||
|
namespace QuestPDF.Elements
|
||||||
|
{
|
||||||
|
internal class DefaultTextStyle : ContainerElement
|
||||||
|
{
|
||||||
|
public TextStyle TextStyle { get; set; } = TextStyle.Default;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,12 +44,11 @@ namespace QuestPDF.Elements
|
|||||||
ChildrenQueue = new Queue<InlinedElement>(Elements);
|
ChildrenQueue = new Queue<InlinedElement>(Elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void HandleVisitor(Action<Element?> visit)
|
internal override IEnumerable<Element?> GetChildren()
|
||||||
{
|
{
|
||||||
Elements.ForEach(x => x.HandleVisitor(visit));
|
return Elements;
|
||||||
base.HandleVisitor(visit);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override SpacePlan Measure(Size availableSpace)
|
internal override SpacePlan Measure(Size availableSpace)
|
||||||
{
|
{
|
||||||
if (!ChildrenQueue.Any())
|
if (!ChildrenQueue.Any())
|
||||||
|
|||||||
@@ -15,12 +15,11 @@ namespace QuestPDF.Elements
|
|||||||
{
|
{
|
||||||
public List<Layer> Children { get; set; } = new List<Layer>();
|
public List<Layer> Children { get; set; } = new List<Layer>();
|
||||||
|
|
||||||
internal override void HandleVisitor(Action<Element?> visit)
|
internal override IEnumerable<Element?> GetChildren()
|
||||||
{
|
{
|
||||||
Children.ForEach(x => x.HandleVisitor(visit));
|
return Children;
|
||||||
base.HandleVisitor(visit);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override SpacePlan Measure(Size availableSpace)
|
internal override SpacePlan Measure(Size availableSpace)
|
||||||
{
|
{
|
||||||
return Children
|
return Children
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using QuestPDF.Infrastructure;
|
|||||||
|
|
||||||
namespace QuestPDF.Elements
|
namespace QuestPDF.Elements
|
||||||
{
|
{
|
||||||
internal class Box : ContainerElement
|
internal class MinimalBox : ContainerElement
|
||||||
{
|
{
|
||||||
internal override void Draw(Size availableSpace)
|
internal override void Draw(Size availableSpace)
|
||||||
{
|
{
|
||||||
@@ -40,6 +40,8 @@ namespace QuestPDF.Elements
|
|||||||
.PaddingTop(MarginTop)
|
.PaddingTop(MarginTop)
|
||||||
.PaddingBottom(MarginBottom)
|
.PaddingBottom(MarginBottom)
|
||||||
|
|
||||||
|
.DefaultTextStyle(DefaultTextStyle)
|
||||||
|
|
||||||
.Decoration(decoration =>
|
.Decoration(decoration =>
|
||||||
{
|
{
|
||||||
decoration
|
decoration
|
||||||
@@ -60,8 +62,6 @@ namespace QuestPDF.Elements
|
|||||||
.Element(Footer);
|
.Element(Footer);
|
||||||
});
|
});
|
||||||
|
|
||||||
(container as Element).ApplyDefaultTextStyle(DefaultTextStyle);
|
|
||||||
|
|
||||||
bool IsClose(float x, float y)
|
bool IsClose(float x, float y)
|
||||||
{
|
{
|
||||||
return Math.Abs(x - y) < Size.Epsilon;
|
return Math.Abs(x - y) < Size.Epsilon;
|
||||||
|
|||||||
+48
-53
@@ -6,9 +6,16 @@ using QuestPDF.Infrastructure;
|
|||||||
|
|
||||||
namespace QuestPDF.Elements
|
namespace QuestPDF.Elements
|
||||||
{
|
{
|
||||||
internal abstract class RowElement : Constrained
|
internal class RowElement : Constrained
|
||||||
{
|
{
|
||||||
public float Size { get; protected set; }
|
public float ConstantSize { get; }
|
||||||
|
public float RelativeSize { get; }
|
||||||
|
|
||||||
|
public RowElement(float constantSize, float relativeSize)
|
||||||
|
{
|
||||||
|
ConstantSize = constantSize;
|
||||||
|
RelativeSize = relativeSize;
|
||||||
|
}
|
||||||
|
|
||||||
public void SetWidth(float width)
|
public void SetWidth(float width)
|
||||||
{
|
{
|
||||||
@@ -17,62 +24,59 @@ namespace QuestPDF.Elements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class ConstantRowElement : RowElement
|
internal class BinaryRow : Element, ICacheable, IStateResettable
|
||||||
{
|
|
||||||
public ConstantRowElement(float size)
|
|
||||||
{
|
|
||||||
Size = size;
|
|
||||||
SetWidth(size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class RelativeRowElement : RowElement
|
|
||||||
{
|
|
||||||
public RelativeRowElement(float size)
|
|
||||||
{
|
|
||||||
Size = size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class BinaryRow : Element, ICacheable
|
|
||||||
{
|
{
|
||||||
internal Element Left { get; set; }
|
internal Element Left { get; set; }
|
||||||
internal Element Right { get; set; }
|
internal Element Right { get; set; }
|
||||||
|
|
||||||
internal override void HandleVisitor(Action<Element?> visit)
|
private bool IsLeftRendered { get; set; }
|
||||||
|
private bool IsRightRendered { get; set; }
|
||||||
|
|
||||||
|
public void ResetState()
|
||||||
{
|
{
|
||||||
Left.HandleVisitor(visit);
|
IsLeftRendered = false;
|
||||||
Right.HandleVisitor(visit);
|
IsRightRendered = false;
|
||||||
|
|
||||||
base.HandleVisitor(visit);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal override IEnumerable<Element?> GetChildren()
|
||||||
|
{
|
||||||
|
yield return Left;
|
||||||
|
yield return Right;
|
||||||
|
}
|
||||||
|
|
||||||
internal override void CreateProxy(Func<Element?, Element?> create)
|
internal override void CreateProxy(Func<Element?, Element?> create)
|
||||||
{
|
{
|
||||||
Left = create(Left);
|
Left = create(Left);
|
||||||
Right = create(Right);
|
Right = create(Right);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override SpacePlan Measure(Size availableSpace)
|
internal override SpacePlan Measure(Size availableSpace)
|
||||||
{
|
{
|
||||||
var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height));
|
var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height));
|
||||||
|
|
||||||
if (leftMeasurement.Type == SpacePlanType.Wrap)
|
if (leftMeasurement.Type == SpacePlanType.Wrap)
|
||||||
return SpacePlan.Wrap();
|
return SpacePlan.Wrap();
|
||||||
|
|
||||||
|
if (leftMeasurement.Type == SpacePlanType.FullRender)
|
||||||
|
IsLeftRendered = true;
|
||||||
|
|
||||||
var rightMeasurement = Right.Measure(new Size(availableSpace.Width - leftMeasurement.Width, availableSpace.Height));
|
var rightMeasurement = Right.Measure(new Size(availableSpace.Width - leftMeasurement.Width, availableSpace.Height));
|
||||||
|
|
||||||
if (rightMeasurement.Type == SpacePlanType.Wrap)
|
if (rightMeasurement.Type == SpacePlanType.Wrap)
|
||||||
return SpacePlan.Wrap();
|
return SpacePlan.Wrap();
|
||||||
|
|
||||||
|
if (leftMeasurement.Type == SpacePlanType.FullRender)
|
||||||
|
IsRightRendered = true;
|
||||||
|
|
||||||
var totalWidth = leftMeasurement.Width + rightMeasurement.Width;
|
var totalWidth = leftMeasurement.Width + rightMeasurement.Width;
|
||||||
var totalHeight = Math.Max(leftMeasurement.Height, rightMeasurement.Height);
|
var totalHeight = Math.Max(leftMeasurement.Height, rightMeasurement.Height);
|
||||||
|
|
||||||
var targetSize = new Size(totalWidth, totalHeight);
|
var targetSize = new Size(totalWidth, totalHeight);
|
||||||
|
|
||||||
if (leftMeasurement.Type == SpacePlanType.PartialRender || rightMeasurement.Type == SpacePlanType.PartialRender)
|
if ((!IsLeftRendered && leftMeasurement.Type == SpacePlanType.PartialRender) ||
|
||||||
|
(!IsRightRendered && rightMeasurement.Type == SpacePlanType.PartialRender))
|
||||||
return SpacePlan.PartialRender(targetSize);
|
return SpacePlan.PartialRender(targetSize);
|
||||||
|
|
||||||
return SpacePlan.FullRender(targetSize);
|
return SpacePlan.FullRender(targetSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,16 +97,15 @@ namespace QuestPDF.Elements
|
|||||||
{
|
{
|
||||||
public float Spacing { get; set; } = 0;
|
public float Spacing { get; set; } = 0;
|
||||||
|
|
||||||
public ICollection<RowElement> Children { get; internal set; } = new List<RowElement>();
|
public ICollection<RowElement> Items { get; internal set; } = new List<RowElement>();
|
||||||
private Element? RootElement { get; set; }
|
private Element? RootElement { get; set; }
|
||||||
|
|
||||||
internal override void HandleVisitor(Action<Element?> visit)
|
internal override IEnumerable<Element?> GetChildren()
|
||||||
{
|
{
|
||||||
if (RootElement == null)
|
if (RootElement == null)
|
||||||
ComposeTree();
|
ComposeTree();
|
||||||
|
|
||||||
RootElement.HandleVisitor(visit);
|
yield return RootElement;
|
||||||
base.HandleVisitor(visit);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override SpacePlan Measure(Size availableSpace)
|
internal override SpacePlan Measure(Size availableSpace)
|
||||||
@@ -121,31 +124,23 @@ namespace QuestPDF.Elements
|
|||||||
|
|
||||||
private void ComposeTree()
|
private void ComposeTree()
|
||||||
{
|
{
|
||||||
Children = AddSpacing(Children, Spacing);
|
Items = AddSpacing(Items, Spacing);
|
||||||
|
|
||||||
var elements = Children.Cast<Element>().ToArray();
|
var elements = Items.Cast<Element>().ToArray();
|
||||||
RootElement = BuildTree(elements);
|
RootElement = BuildTree(elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateElementsWidth(float availableWidth)
|
private void UpdateElementsWidth(float availableWidth)
|
||||||
{
|
{
|
||||||
var constantWidth = Children
|
var constantWidth = Items.Sum(x => x.ConstantSize);
|
||||||
.Where(x => x is ConstantRowElement)
|
var relativeWidth = Items.Sum(x => x.RelativeSize);
|
||||||
.Cast<ConstantRowElement>()
|
|
||||||
.Sum(x => x.Size);
|
|
||||||
|
|
||||||
var relativeWidth = Children
|
|
||||||
.Where(x => x is RelativeRowElement)
|
|
||||||
.Cast<RelativeRowElement>()
|
|
||||||
.Sum(x => x.Size);
|
|
||||||
|
|
||||||
var widthPerRelativeUnit = (availableWidth - constantWidth) / relativeWidth;
|
var widthPerRelativeUnit = (relativeWidth > 0) ? (availableWidth - constantWidth) / relativeWidth : 0;
|
||||||
|
|
||||||
Children
|
foreach (var row in Items)
|
||||||
.Where(x => x is RelativeRowElement)
|
{
|
||||||
.Cast<RelativeRowElement>()
|
row.SetWidth(row.ConstantSize + row.RelativeSize * widthPerRelativeUnit);
|
||||||
.ToList()
|
}
|
||||||
.ForEach(x => x.SetWidth(x.Size * widthPerRelativeUnit));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ICollection<RowElement> AddSpacing(ICollection<RowElement> elements, float spacing)
|
private static ICollection<RowElement> AddSpacing(ICollection<RowElement> elements, float spacing)
|
||||||
@@ -154,7 +149,7 @@ namespace QuestPDF.Elements
|
|||||||
return elements;
|
return elements;
|
||||||
|
|
||||||
return elements
|
return elements
|
||||||
.SelectMany(x => new[] { new ConstantRowElement(spacing), x })
|
.SelectMany(x => new[] { new RowElement(spacing, 0), x })
|
||||||
.Skip(1)
|
.Skip(1)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,14 +16,12 @@ namespace QuestPDF.Elements
|
|||||||
|
|
||||||
internal bool IsFirstRendered { get; set; } = false;
|
internal bool IsFirstRendered { get; set; } = false;
|
||||||
|
|
||||||
internal override void HandleVisitor(Action<Element?> visit)
|
internal override IEnumerable<Element?> GetChildren()
|
||||||
{
|
{
|
||||||
First?.HandleVisitor(visit);
|
yield return First;
|
||||||
Second?.HandleVisitor(visit);
|
yield return Second;
|
||||||
|
|
||||||
base.HandleVisitor(visit);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResetState()
|
public void ResetState()
|
||||||
{
|
{
|
||||||
IsFirstRendered = false;
|
IsFirstRendered = false;
|
||||||
@@ -97,12 +95,12 @@ namespace QuestPDF.Elements
|
|||||||
|
|
||||||
internal class Stack : IComponent
|
internal class Stack : IComponent
|
||||||
{
|
{
|
||||||
public ICollection<Element> Children { get; } = new List<Element>();
|
public ICollection<Element> Items { get; } = new List<Element>();
|
||||||
public float Spacing { get; set; } = 0;
|
public float Spacing { get; set; } = 0;
|
||||||
|
|
||||||
public void Compose(IContainer container)
|
public void Compose(IContainer container)
|
||||||
{
|
{
|
||||||
var elements = AddSpacing(Spacing, Children);
|
var elements = AddSpacing(Spacing, Items);
|
||||||
|
|
||||||
container
|
container
|
||||||
.PaddingBottom(-Spacing)
|
.PaddingBottom(-Spacing)
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace QuestPDF.Elements.Table
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This dictionary allows to access key that does not exist.
|
||||||
|
/// Instead of throwing an exception, it returns a default value.
|
||||||
|
/// </summary>
|
||||||
|
internal class DynamicDictionary<TKey, TValue>
|
||||||
|
{
|
||||||
|
private TValue Default { get; }
|
||||||
|
private IDictionary<TKey, TValue> Dictionary { get; } = new Dictionary<TKey, TValue>();
|
||||||
|
|
||||||
|
public DynamicDictionary()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public DynamicDictionary(TValue defaultValue)
|
||||||
|
{
|
||||||
|
Default = defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TValue this[TKey key]
|
||||||
|
{
|
||||||
|
get => Dictionary.TryGetValue(key, out var value) ? value : Default;
|
||||||
|
set => Dictionary[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<KeyValuePair<TKey, TValue>> Items => Dictionary.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using QuestPDF.Infrastructure;
|
||||||
|
|
||||||
|
namespace QuestPDF.Elements.Table
|
||||||
|
{
|
||||||
|
public interface ITableCellContainer : IContainer
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,277 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using QuestPDF.Drawing;
|
||||||
|
using QuestPDF.Fluent;
|
||||||
|
using QuestPDF.Infrastructure;
|
||||||
|
|
||||||
|
namespace QuestPDF.Elements.Table
|
||||||
|
{
|
||||||
|
internal class Table : Element, IStateResettable
|
||||||
|
{
|
||||||
|
public List<TableColumnDefinition> Columns { get; set; } = new List<TableColumnDefinition>();
|
||||||
|
public List<TableCell> Cells { get; set; } = new List<TableCell>();
|
||||||
|
public bool ExtendLastCellsToTableBottom { get; set; }
|
||||||
|
|
||||||
|
// cache for efficient cell finding
|
||||||
|
// index of first array - number of row
|
||||||
|
// nested array - collection of all cells starting at given row
|
||||||
|
private TableCell[][] OrderedCells { get; set; }
|
||||||
|
|
||||||
|
private int StartingRowsCount { get; set; }
|
||||||
|
private int RowsCount { get; set; }
|
||||||
|
private int CurrentRow { get; set; }
|
||||||
|
|
||||||
|
internal override IEnumerable<Element?> GetChildren()
|
||||||
|
{
|
||||||
|
return Cells;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResetState()
|
||||||
|
{
|
||||||
|
if (StartingRowsCount == default)
|
||||||
|
StartingRowsCount = Cells.Select(x => x.Row).DefaultIfEmpty(0).Max();
|
||||||
|
|
||||||
|
if (RowsCount == default)
|
||||||
|
RowsCount = Cells.Select(x => x.Row + x.RowSpan - 1).DefaultIfEmpty(0).Max();
|
||||||
|
|
||||||
|
if (OrderedCells == default)
|
||||||
|
{
|
||||||
|
var groups = Cells
|
||||||
|
.GroupBy(x => x.Row)
|
||||||
|
.ToDictionary(x => x.Key, x => x.OrderBy(y => y.Column).ToArray());
|
||||||
|
|
||||||
|
OrderedCells = Enumerable
|
||||||
|
.Range(0, RowsCount + 1)
|
||||||
|
.Select(x => groups.TryGetValue(x, out var output) ? output : Array.Empty<TableCell>())
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
Cells.ForEach(x => x.IsRendered = false);
|
||||||
|
CurrentRow = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override SpacePlan Measure(Size availableSpace)
|
||||||
|
{
|
||||||
|
if (!Cells.Any())
|
||||||
|
return SpacePlan.FullRender(Size.Zero);
|
||||||
|
|
||||||
|
UpdateColumnsWidth(availableSpace.Width);
|
||||||
|
var renderingCommands = PlanLayout(availableSpace);
|
||||||
|
|
||||||
|
if (!renderingCommands.Any())
|
||||||
|
return SpacePlan.Wrap();
|
||||||
|
|
||||||
|
var width = Columns.Sum(x => x.Width);
|
||||||
|
var height = renderingCommands.Max(x => x.Offset.Y + x.Size.Height);
|
||||||
|
var tableSize = new Size(width, height);
|
||||||
|
|
||||||
|
if (tableSize.Width > availableSpace.Width + Size.Epsilon)
|
||||||
|
return SpacePlan.Wrap();
|
||||||
|
|
||||||
|
return FindLastRenderedRow(renderingCommands) == StartingRowsCount
|
||||||
|
? SpacePlan.FullRender(tableSize)
|
||||||
|
: SpacePlan.PartialRender(tableSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Draw(Size availableSpace)
|
||||||
|
{
|
||||||
|
UpdateColumnsWidth(availableSpace.Width);
|
||||||
|
var renderingCommands = PlanLayout(availableSpace);
|
||||||
|
|
||||||
|
foreach (var command in renderingCommands)
|
||||||
|
{
|
||||||
|
if (command.Measurement.Type == SpacePlanType.FullRender)
|
||||||
|
command.Cell.IsRendered = true;
|
||||||
|
|
||||||
|
Canvas.Translate(command.Offset);
|
||||||
|
command.Cell.Draw(command.Size);
|
||||||
|
Canvas.Translate(command.Offset.Reverse());
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentRow = FindLastRenderedRow(renderingCommands) + 1;
|
||||||
|
|
||||||
|
var isFullRender = FindLastRenderedRow(renderingCommands) == StartingRowsCount;
|
||||||
|
|
||||||
|
if (isFullRender)
|
||||||
|
ResetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int FindLastRenderedRow(ICollection<TableCellRenderingCommand> commands)
|
||||||
|
{
|
||||||
|
return commands
|
||||||
|
.GroupBy(x => x.Cell.Row)
|
||||||
|
.Where(x => x.All(y => y.Measurement.Type == SpacePlanType.FullRender))
|
||||||
|
.Select(x => x.Key)
|
||||||
|
.DefaultIfEmpty(0)
|
||||||
|
.Max();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateColumnsWidth(float availableWidth)
|
||||||
|
{
|
||||||
|
var constantWidth = Columns.Sum(x => x.ConstantSize);
|
||||||
|
var relativeWidth = Columns.Sum(x => x.RelativeSize);
|
||||||
|
|
||||||
|
var widthPerRelativeUnit = (relativeWidth > 0) ? (availableWidth - constantWidth) / relativeWidth : 0;
|
||||||
|
|
||||||
|
foreach (var column in Columns)
|
||||||
|
{
|
||||||
|
column.Width = column.ConstantSize + column.RelativeSize * widthPerRelativeUnit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ICollection<TableCellRenderingCommand> PlanLayout(Size availableSpace)
|
||||||
|
{
|
||||||
|
var columnOffsets = GetColumnLeftOffsets(Columns);
|
||||||
|
|
||||||
|
var commands = GetRenderingCommands();
|
||||||
|
|
||||||
|
if (!commands.Any())
|
||||||
|
return commands;
|
||||||
|
|
||||||
|
var tableHeight = commands.Max(cell => cell.Offset.Y + cell.Size.Height);
|
||||||
|
|
||||||
|
if (ExtendLastCellsToTableBottom)
|
||||||
|
AdjustLastCellSizes(tableHeight, commands);
|
||||||
|
|
||||||
|
return commands;
|
||||||
|
|
||||||
|
static float[] GetColumnLeftOffsets(IList<TableColumnDefinition> columns)
|
||||||
|
{
|
||||||
|
var cellOffsets = new float[columns.Count + 1];
|
||||||
|
cellOffsets[0] = 0;
|
||||||
|
|
||||||
|
foreach (var column in Enumerable.Range(1, cellOffsets.Length - 1))
|
||||||
|
cellOffsets[column] = columns[column - 1].Width + cellOffsets[column - 1];
|
||||||
|
|
||||||
|
return cellOffsets;
|
||||||
|
}
|
||||||
|
|
||||||
|
ICollection<TableCellRenderingCommand> GetRenderingCommands()
|
||||||
|
{
|
||||||
|
var rowBottomOffsets = new DynamicDictionary<int, float>();
|
||||||
|
|
||||||
|
var cellsToTry = Enumerable
|
||||||
|
.Range(CurrentRow, RowsCount - CurrentRow + 1)
|
||||||
|
.SelectMany(x => OrderedCells[x]);
|
||||||
|
|
||||||
|
var currentRow = CurrentRow;
|
||||||
|
var maxRenderingRow = RowsCount;
|
||||||
|
|
||||||
|
var commands = new List<TableCellRenderingCommand>();
|
||||||
|
|
||||||
|
foreach (var cell in cellsToTry)
|
||||||
|
{
|
||||||
|
// update position of previous row
|
||||||
|
if (cell.Row > currentRow)
|
||||||
|
{
|
||||||
|
rowBottomOffsets[currentRow] = Math.Max(rowBottomOffsets[currentRow], rowBottomOffsets[currentRow - 1]);
|
||||||
|
|
||||||
|
if (rowBottomOffsets[currentRow - 1] > availableSpace.Height + Size.Epsilon)
|
||||||
|
break;
|
||||||
|
|
||||||
|
foreach (var row in Enumerable.Range(cell.Row, cell.Row - currentRow))
|
||||||
|
rowBottomOffsets[row] = Math.Max(rowBottomOffsets[row - 1], rowBottomOffsets[row]);
|
||||||
|
|
||||||
|
currentRow = cell.Row;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cell visibility optimizations
|
||||||
|
if (cell.Row > maxRenderingRow)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (cell.IsRendered)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// calculate cell position / size
|
||||||
|
var topOffset = rowBottomOffsets[cell.Row - 1];
|
||||||
|
|
||||||
|
var availableWidth = GetCellWidth(cell);
|
||||||
|
var availableHeight = availableSpace.Height - topOffset;
|
||||||
|
var availableCellSize = new Size(availableWidth, availableHeight);
|
||||||
|
|
||||||
|
var cellSize = cell.Measure(availableCellSize);
|
||||||
|
|
||||||
|
// corner case: if cell within the row is not fully rendered, do not attempt to render next row
|
||||||
|
if (cellSize.Type == SpacePlanType.PartialRender)
|
||||||
|
{
|
||||||
|
maxRenderingRow = Math.Min(maxRenderingRow, cell.Row + cell.RowSpan - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// corner case: if cell within the row want to wrap to the next page, do not attempt to render this row
|
||||||
|
if (cellSize.Type == SpacePlanType.Wrap)
|
||||||
|
{
|
||||||
|
maxRenderingRow = Math.Min(maxRenderingRow, cell.Row - 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update position of the last row that cell occupies
|
||||||
|
var bottomRow = cell.Row + cell.RowSpan - 1;
|
||||||
|
rowBottomOffsets[bottomRow] = Math.Max(rowBottomOffsets[bottomRow], topOffset + cellSize.Height);
|
||||||
|
|
||||||
|
// accept cell to be rendered
|
||||||
|
commands.Add(new TableCellRenderingCommand()
|
||||||
|
{
|
||||||
|
Cell = cell,
|
||||||
|
Measurement = cellSize,
|
||||||
|
Size = new Size(availableWidth, cellSize.Height),
|
||||||
|
Offset = new Position(columnOffsets[cell.Column - 1], topOffset)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!commands.Any())
|
||||||
|
return commands;
|
||||||
|
|
||||||
|
var maxRow = commands.Select(x => x.Cell).Max(x => x.Row + x.RowSpan);
|
||||||
|
|
||||||
|
foreach (var row in Enumerable.Range(CurrentRow, maxRow - CurrentRow))
|
||||||
|
rowBottomOffsets[row] = Math.Max(rowBottomOffsets[row - 1], rowBottomOffsets[row]);
|
||||||
|
|
||||||
|
AdjustCellSizes(commands, rowBottomOffsets);
|
||||||
|
|
||||||
|
// corner case: reject cell if other cells within the same row are rejected
|
||||||
|
return commands.Where(x => x.Cell.Row <= maxRenderingRow).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// corner sase: if two cells end up on the same row (a.Row + a.RowSpan = b.Row + b.RowSpan),
|
||||||
|
// bottom edges of their bounding boxes should be at the same level
|
||||||
|
static void AdjustCellSizes(ICollection<TableCellRenderingCommand> commands, DynamicDictionary<int, float> rowBottomOffsets)
|
||||||
|
{
|
||||||
|
foreach (var command in commands)
|
||||||
|
{
|
||||||
|
var lastRow = command.Cell.Row + command.Cell.RowSpan - 1;
|
||||||
|
var height = rowBottomOffsets[lastRow] - command.Offset.Y;
|
||||||
|
|
||||||
|
command.Size = new Size(command.Size.Width, height);
|
||||||
|
command.Offset = new Position(command.Offset.X, rowBottomOffsets[command.Cell.Row - 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// corner sase: all cells, that are last ones in their respective columns, should take all remaining space
|
||||||
|
static void AdjustLastCellSizes(float tableHeight, ICollection<TableCellRenderingCommand> commands)
|
||||||
|
{
|
||||||
|
var columnsCount = commands.Select(x => x.Cell).Max(x => x.Column + x.ColumnSpan - 1);
|
||||||
|
|
||||||
|
foreach (var column in Enumerable.Range(1, columnsCount))
|
||||||
|
{
|
||||||
|
var lastCellInColumn = commands
|
||||||
|
.Where(x => x.Cell.Column <= column && column < x.Cell.Column + x.Cell.ColumnSpan)
|
||||||
|
.OrderByDescending(x => x.Cell.Row + x.Cell.RowSpan)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (lastCellInColumn == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
lastCellInColumn.Size = new Size(lastCellInColumn.Size.Width, tableHeight - lastCellInColumn.Offset.Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float GetCellWidth(TableCell cell)
|
||||||
|
{
|
||||||
|
return columnOffsets[cell.Column + cell.ColumnSpan - 1] - columnOffsets[cell.Column - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
namespace QuestPDF.Elements.Table
|
||||||
|
{
|
||||||
|
internal class TableCell : Container, ITableCellContainer
|
||||||
|
{
|
||||||
|
public int Row { get; set; } = 0;
|
||||||
|
public int RowSpan { get; set; } = 1;
|
||||||
|
|
||||||
|
public int Column { get; set; } = 0;
|
||||||
|
public int ColumnSpan { get; set; } = 1;
|
||||||
|
|
||||||
|
public bool IsRendered { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using QuestPDF.Drawing;
|
||||||
|
using QuestPDF.Infrastructure;
|
||||||
|
|
||||||
|
namespace QuestPDF.Elements.Table
|
||||||
|
{
|
||||||
|
internal class TableCellRenderingCommand
|
||||||
|
{
|
||||||
|
public TableCell Cell { get; set; }
|
||||||
|
public SpacePlan Measurement { get; set; }
|
||||||
|
public Size Size { get; set; }
|
||||||
|
public Position Offset { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
namespace QuestPDF.Elements.Table
|
||||||
|
{
|
||||||
|
internal class TableColumnDefinition
|
||||||
|
{
|
||||||
|
public float ConstantSize { get; }
|
||||||
|
public float RelativeSize { get; }
|
||||||
|
|
||||||
|
internal float Width { get; set; }
|
||||||
|
|
||||||
|
public TableColumnDefinition(float constantSize, float relativeSize)
|
||||||
|
{
|
||||||
|
ConstantSize = constantSize;
|
||||||
|
RelativeSize = relativeSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using QuestPDF.Fluent;
|
||||||
|
|
||||||
|
namespace QuestPDF.Elements.Table
|
||||||
|
{
|
||||||
|
static class TableLayoutValidator
|
||||||
|
{
|
||||||
|
public static void PlanCellPositions(this Table table)
|
||||||
|
{
|
||||||
|
PlanCellPositions(table.Columns.Count, table.Cells);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PlanCellPositions(int columnsCount, ICollection<TableCell> cells)
|
||||||
|
{
|
||||||
|
var cellsWindow = new List<TableCell>();
|
||||||
|
(int x, int y) currentLocation = (1, 1);
|
||||||
|
|
||||||
|
foreach (var cell in cells)
|
||||||
|
{
|
||||||
|
if (cellsWindow.Count > Math.Max(columnsCount, 16))
|
||||||
|
{
|
||||||
|
cellsWindow = cellsWindow
|
||||||
|
.Where(x => x.Row + x.RowSpan > currentLocation.y)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
SetPartialLocation(cell);
|
||||||
|
|
||||||
|
if (cell.HasLocation())
|
||||||
|
{
|
||||||
|
cellsWindow.Add(cell);
|
||||||
|
currentLocation = (cell.Column, cell.Row);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var location in GenerateCoordinates(columnsCount, currentLocation))
|
||||||
|
{
|
||||||
|
if (location.x + cell.ColumnSpan - 1 > columnsCount)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
cell.Column = location.x;
|
||||||
|
cell.Row = location.y;
|
||||||
|
|
||||||
|
if (cell.CollidesWithAnyOf(cellsWindow))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
cellsWindow.Add(cell);
|
||||||
|
currentLocation = (cell.Column, cell.Row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<(int x, int y)> GenerateCoordinates(int columnsCount, (int x, int y) startPosition)
|
||||||
|
{
|
||||||
|
if (startPosition.x > columnsCount)
|
||||||
|
throw new ArgumentException();
|
||||||
|
|
||||||
|
foreach (var x in Enumerable.Range(startPosition.x, columnsCount - startPosition.x + 1))
|
||||||
|
yield return (x, startPosition.y);
|
||||||
|
|
||||||
|
foreach (var y in Enumerable.Range(startPosition.y + 1, 1_000_000))
|
||||||
|
foreach (var x in Enumerable.Range(1, columnsCount))
|
||||||
|
yield return (x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CollidesWith(this TableCell cell, TableCell neighbour)
|
||||||
|
{
|
||||||
|
return cell.Column < neighbour.Column + neighbour.ColumnSpan &&
|
||||||
|
cell.Column + cell.ColumnSpan > neighbour.Column &&
|
||||||
|
cell.Row < neighbour.Row + neighbour.RowSpan &&
|
||||||
|
cell.RowSpan + cell.Row > neighbour.Row;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CollidesWithAnyOf(this TableCell cell, ICollection<TableCell> neighbours)
|
||||||
|
{
|
||||||
|
return neighbours.Any(cell.CollidesWith);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetPartialLocation(this TableCell cell)
|
||||||
|
{
|
||||||
|
if (cell.Row == default && cell.Column == default)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (cell.Row == default)
|
||||||
|
cell.Row = 1;
|
||||||
|
|
||||||
|
if (cell.Column == default)
|
||||||
|
cell.Column = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HasLocation(this TableCell cell)
|
||||||
|
{
|
||||||
|
return cell.Row != 0 && cell.Column != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using QuestPDF.Drawing.Exceptions;
|
||||||
|
using QuestPDF.Fluent;
|
||||||
|
|
||||||
|
namespace QuestPDF.Elements.Table
|
||||||
|
{
|
||||||
|
static class TableLayoutPlanner
|
||||||
|
{
|
||||||
|
public static void ValidateCellPositions(this Table table)
|
||||||
|
{
|
||||||
|
ValidateCellPositions(table.Columns.Count, table.Cells);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ValidateCellPositions(int columnsCount, ICollection<TableCell> cells)
|
||||||
|
{
|
||||||
|
const string prefix = "Detected issue in table cells configuration.";
|
||||||
|
|
||||||
|
foreach (var cell in cells)
|
||||||
|
{
|
||||||
|
if (cell.Column < 1)
|
||||||
|
throw new DocumentComposeException($"{prefix} A cell column position should be greater or equal to 1. Got {cell.Column}.");
|
||||||
|
|
||||||
|
if (cell.Row < 1)
|
||||||
|
throw new DocumentComposeException($"{prefix} A cell row position should be greater or equal to 1. Got {cell.Row}.");
|
||||||
|
|
||||||
|
if (cell.Column > columnsCount)
|
||||||
|
throw new DocumentComposeException($"{prefix} Cell starts at column that does not exist. Cell details: {GetCellDetails(cell)}.");
|
||||||
|
|
||||||
|
if (cell.Column + cell.ColumnSpan - 1 > columnsCount)
|
||||||
|
throw new DocumentComposeException($"{prefix} Table cell location is incorrect. Cell spans over columns that do not exist. Cell details: {GetCellDetails(cell)}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
string GetCellDetails(TableCell cell)
|
||||||
|
{
|
||||||
|
return $"Row {cell.Row}, Column {cell.Column}, RowSpan {cell.RowSpan}, ColumnSpan {cell.ColumnSpan}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using QuestPDF.Drawing;
|
using QuestPDF.Drawing;
|
||||||
using QuestPDF.Elements.Text.Calculation;
|
using QuestPDF.Elements.Text.Calculation;
|
||||||
|
using QuestPDF.Helpers;
|
||||||
using QuestPDF.Infrastructure;
|
using QuestPDF.Infrastructure;
|
||||||
|
|
||||||
namespace QuestPDF.Elements.Text.Items
|
namespace QuestPDF.Elements.Text.Items
|
||||||
@@ -10,8 +11,8 @@ namespace QuestPDF.Elements.Text.Items
|
|||||||
|
|
||||||
public TextMeasurementResult? Measure(TextMeasurementRequest request)
|
public TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||||
{
|
{
|
||||||
Element.HandleVisitor(x => (x as IStateResettable)?.ResetState());
|
Element.VisitChildren(x => (x as IStateResettable)?.ResetState());
|
||||||
Element.HandleVisitor(x => x.Initialize(request.PageContext, request.Canvas));
|
Element.VisitChildren(x => x.Initialize(request.PageContext, request.Canvas));
|
||||||
|
|
||||||
var measurement = Element.Measure(new Size(request.AvailableWidth, Size.Max.Height));
|
var measurement = Element.Measure(new Size(request.AvailableWidth, Size.Max.Height));
|
||||||
|
|
||||||
@@ -35,8 +36,8 @@ namespace QuestPDF.Elements.Text.Items
|
|||||||
|
|
||||||
public void Draw(TextDrawingRequest request)
|
public void Draw(TextDrawingRequest request)
|
||||||
{
|
{
|
||||||
Element.HandleVisitor(x => (x as IStateResettable)?.ResetState());
|
Element.VisitChildren(x => (x as IStateResettable)?.ResetState());
|
||||||
Element.HandleVisitor(x => x.Initialize(request.PageContext, request.Canvas));
|
Element.VisitChildren(x => x.Initialize(request.PageContext, request.Canvas));
|
||||||
|
|
||||||
request.Canvas.Translate(new Position(0, request.TotalAscent));
|
request.Canvas.Translate(new Position(0, request.TotalAscent));
|
||||||
Element.Draw(new Size(request.TextSize.Width, -request.TotalAscent));
|
Element.Draw(new Size(request.TextSize.Width, -request.TotalAscent));
|
||||||
|
|||||||
@@ -11,14 +11,16 @@ namespace QuestPDF.Elements.Text
|
|||||||
internal class TextBlock : Element, IStateResettable
|
internal class TextBlock : Element, IStateResettable
|
||||||
{
|
{
|
||||||
public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
|
public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
|
||||||
public List<ITextBlockItem> Children { get; set; } = new List<ITextBlockItem>();
|
public List<ITextBlockItem> Items { get; set; } = new List<ITextBlockItem>();
|
||||||
|
|
||||||
public Queue<ITextBlockItem> RenderingQueue { get; set; }
|
public string Text => string.Join(" ", Items.Where(x => x is TextBlockSpan).Cast<TextBlockSpan>().Select(x => x.Text));
|
||||||
public int CurrentElementIndex { get; set; }
|
|
||||||
|
private Queue<ITextBlockItem> RenderingQueue { get; set; }
|
||||||
|
private int CurrentElementIndex { get; set; }
|
||||||
|
|
||||||
public void ResetState()
|
public void ResetState()
|
||||||
{
|
{
|
||||||
RenderingQueue = new Queue<ITextBlockItem>(Children);
|
RenderingQueue = new Queue<ITextBlockItem>(Items);
|
||||||
CurrentElementIndex = 0;
|
CurrentElementIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +32,7 @@ namespace QuestPDF.Elements.Text
|
|||||||
var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height).ToList();
|
var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height).ToList();
|
||||||
|
|
||||||
if (!lines.Any())
|
if (!lines.Any())
|
||||||
return SpacePlan.PartialRender(Size.Zero);
|
return SpacePlan.Wrap();
|
||||||
|
|
||||||
var width = lines.Max(x => x.Width);
|
var width = lines.Max(x => x.Width);
|
||||||
var height = lines.Sum(x => x.LineHeight);
|
var height = lines.Sum(x => x.LineHeight);
|
||||||
|
|||||||
@@ -136,14 +136,29 @@ namespace QuestPDF.Fluent
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: deprecated Box method in QuestPDF 2022.1
|
||||||
|
[Obsolete("This element has been renamed. Please use the MinimalBox method.")]
|
||||||
public static IContainer Box(this IContainer element)
|
public static IContainer Box(this IContainer element)
|
||||||
{
|
{
|
||||||
return element.Element(new Box());
|
return element.Element(new MinimalBox());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IContainer MinimalBox(this IContainer element)
|
||||||
|
{
|
||||||
|
return element.Element(new MinimalBox());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IContainer Unconstrained(this IContainer element)
|
public static IContainer Unconstrained(this IContainer element)
|
||||||
{
|
{
|
||||||
return element.Element(new Unconstrained());
|
return element.Element(new Unconstrained());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IContainer DefaultTextStyle(this IContainer element, TextStyle textStyle)
|
||||||
|
{
|
||||||
|
return element.Element(new DefaultTextStyle
|
||||||
|
{
|
||||||
|
TextStyle = textStyle
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,17 +15,19 @@ namespace QuestPDF.Fluent
|
|||||||
|
|
||||||
public IContainer ConstantColumn(float width)
|
public IContainer ConstantColumn(float width)
|
||||||
{
|
{
|
||||||
var element = new ConstantRowElement(width);
|
return Column(constantWidth: width);
|
||||||
|
|
||||||
Row.Children.Add(element);
|
|
||||||
return element;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IContainer RelativeColumn(float width = 1)
|
public IContainer RelativeColumn(float width = 1)
|
||||||
{
|
{
|
||||||
var element = new RelativeRowElement(width);
|
return Column(relativeWidth: width);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IContainer Column(float constantWidth = 0, float relativeWidth = 0)
|
||||||
|
{
|
||||||
|
var element = new RowElement(constantWidth, relativeWidth);
|
||||||
|
|
||||||
Row.Children.Add(element);
|
Row.Items.Add(element);
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace QuestPDF.Fluent
|
|||||||
public IContainer Item()
|
public IContainer Item()
|
||||||
{
|
{
|
||||||
var container = new Container();
|
var container = new Container();
|
||||||
Stack.Children.Add(container);
|
Stack.Items.Add(container);
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,162 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using QuestPDF.Drawing.Exceptions;
|
||||||
|
using QuestPDF.Elements;
|
||||||
|
using QuestPDF.Elements.Table;
|
||||||
|
using QuestPDF.Infrastructure;
|
||||||
|
|
||||||
|
namespace QuestPDF.Fluent
|
||||||
|
{
|
||||||
|
public class TableColumnsDefinitionDescriptor
|
||||||
|
{
|
||||||
|
internal List<TableColumnDefinition> Columns { get; } = new();
|
||||||
|
|
||||||
|
public void ConstantColumn(float width)
|
||||||
|
{
|
||||||
|
ComplexColumn(constantWidth: width);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RelativeColumn(float width = 1)
|
||||||
|
{
|
||||||
|
ComplexColumn(relativeWidth: width);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ComplexColumn(float constantWidth = 0, float relativeWidth = 0)
|
||||||
|
{
|
||||||
|
var columnDefinition = new TableColumnDefinition(constantWidth, relativeWidth);
|
||||||
|
Columns.Add(columnDefinition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TableCellDescriptor
|
||||||
|
{
|
||||||
|
private ICollection<TableCell> Cells { get; }
|
||||||
|
|
||||||
|
internal TableCellDescriptor(ICollection<TableCell> cells)
|
||||||
|
{
|
||||||
|
Cells = cells;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITableCellContainer Cell()
|
||||||
|
{
|
||||||
|
var cell = new TableCell();
|
||||||
|
Cells.Add(cell);
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TableDescriptor
|
||||||
|
{
|
||||||
|
internal List<TableColumnDefinition> Columns { get; private set; }
|
||||||
|
|
||||||
|
private Table HeaderTable { get; } = new();
|
||||||
|
private Table ContentTable { get; } = new();
|
||||||
|
private Table FooterTable { get; } = new();
|
||||||
|
|
||||||
|
public void ColumnsDefinition(Action<TableColumnsDefinitionDescriptor> handler)
|
||||||
|
{
|
||||||
|
var descriptor = new TableColumnsDefinitionDescriptor();
|
||||||
|
handler(descriptor);
|
||||||
|
|
||||||
|
HeaderTable.Columns = descriptor.Columns;
|
||||||
|
ContentTable.Columns = descriptor.Columns;
|
||||||
|
FooterTable.Columns = descriptor.Columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ExtendLastCellsToTableBottom()
|
||||||
|
{
|
||||||
|
ContentTable.ExtendLastCellsToTableBottom = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Header(Action<TableCellDescriptor> handler)
|
||||||
|
{
|
||||||
|
var descriptor = new TableCellDescriptor(HeaderTable.Cells);
|
||||||
|
handler(descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Footer(Action<TableCellDescriptor> handler)
|
||||||
|
{
|
||||||
|
var descriptor = new TableCellDescriptor(FooterTable.Cells);
|
||||||
|
handler(descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITableCellContainer Cell()
|
||||||
|
{
|
||||||
|
var cell = new TableCell();
|
||||||
|
ContentTable.Cells.Add(cell);
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal IElement CreateElement()
|
||||||
|
{
|
||||||
|
var container = new Container();
|
||||||
|
|
||||||
|
ConfigureTable(HeaderTable);
|
||||||
|
ConfigureTable(ContentTable);
|
||||||
|
ConfigureTable(FooterTable);
|
||||||
|
|
||||||
|
container
|
||||||
|
.Decoration(decoration =>
|
||||||
|
{
|
||||||
|
decoration.Header().Element(HeaderTable);
|
||||||
|
decoration.Content().Element(ContentTable);
|
||||||
|
decoration.Footer().Element(FooterTable);
|
||||||
|
});
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureTable(Table table)
|
||||||
|
{
|
||||||
|
if (!table.Columns.Any())
|
||||||
|
throw new DocumentComposeException($"Table should have at least one column. Please call the '{nameof(ColumnsDefinition)}' method to define columns.");
|
||||||
|
|
||||||
|
table.PlanCellPositions();
|
||||||
|
table.ValidateCellPositions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TableExtensions
|
||||||
|
{
|
||||||
|
public static void Table(this IContainer element, Action<TableDescriptor> handler)
|
||||||
|
{
|
||||||
|
var descriptor = new TableDescriptor();
|
||||||
|
handler(descriptor);
|
||||||
|
element.Element(descriptor.CreateElement());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TableCellExtensions
|
||||||
|
{
|
||||||
|
private static ITableCellContainer TableCell(this ITableCellContainer element, Action<TableCell> handler)
|
||||||
|
{
|
||||||
|
if (element is TableCell tableCell)
|
||||||
|
handler(tableCell);
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ITableCellContainer Column(this ITableCellContainer tableCellContainer, uint value)
|
||||||
|
{
|
||||||
|
return tableCellContainer.TableCell(x => x.Column = (int)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ITableCellContainer ColumnSpan(this ITableCellContainer tableCellContainer, uint value)
|
||||||
|
{
|
||||||
|
return tableCellContainer.TableCell(x => x.ColumnSpan = (int)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ITableCellContainer Row(this ITableCellContainer tableCellContainer, uint value)
|
||||||
|
{
|
||||||
|
return tableCellContainer.TableCell(x => x.Row = (int)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ITableCellContainer RowSpan(this ITableCellContainer tableCellContainer, uint value)
|
||||||
|
{
|
||||||
|
return tableCellContainer.TableCell(x => x.RowSpan = (int)value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,7 +47,7 @@ namespace QuestPDF.Fluent
|
|||||||
if (!TextBlocks.Any())
|
if (!TextBlocks.Any())
|
||||||
TextBlocks.Add(new TextBlock());
|
TextBlocks.Add(new TextBlock());
|
||||||
|
|
||||||
TextBlocks.Last().Children.Add(item);
|
TextBlocks.Last().Items.Add(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Span(string? text, TextStyle? style = null)
|
public void Span(string? text, TextStyle? style = null)
|
||||||
@@ -73,7 +73,7 @@ namespace QuestPDF.Fluent
|
|||||||
.Skip(1)
|
.Skip(1)
|
||||||
.Select(x => new TextBlock
|
.Select(x => new TextBlock
|
||||||
{
|
{
|
||||||
Children = new List<ITextBlockItem> { x }
|
Items = new List<ITextBlockItem> { x }
|
||||||
})
|
})
|
||||||
.ToList()
|
.ToList()
|
||||||
.ForEach(TextBlocks.Add);
|
.ForEach(TextBlocks.Add);
|
||||||
@@ -167,17 +167,14 @@ namespace QuestPDF.Fluent
|
|||||||
Element = container
|
Element = container
|
||||||
});
|
});
|
||||||
|
|
||||||
return container.AlignBottom().Box();
|
return container.AlignBottom().MinimalBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Compose(IContainer container)
|
internal void Compose(IContainer container)
|
||||||
{
|
{
|
||||||
TextBlocks.ToList().ForEach(x => x.Alignment = Alignment);
|
TextBlocks.ToList().ForEach(x => x.Alignment = Alignment);
|
||||||
|
|
||||||
foreach (var textBlockSpan in TextBlocks.SelectMany(x => x.Children).Where(x => x is TextBlockSpan).Cast<TextBlockSpan>())
|
|
||||||
textBlockSpan.Style.ApplyParentStyle(DefaultStyle);
|
|
||||||
|
|
||||||
container.Stack(stack =>
|
container.DefaultTextStyle(DefaultStyle).Stack(stack =>
|
||||||
{
|
{
|
||||||
stack.Spacing(Spacing);
|
stack.Spacing(Spacing);
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using QuestPDF.Infrastructure;
|
||||||
|
|
||||||
namespace QuestPDF.Helpers
|
namespace QuestPDF.Helpers
|
||||||
{
|
{
|
||||||
@@ -41,5 +43,13 @@ namespace QuestPDF.Helpers
|
|||||||
{
|
{
|
||||||
return Regex.Replace(text, @"([a-z])([A-Z])", "$1 $2");
|
return Regex.Replace(text, @"([a-z])([A-Z])", "$1 $2");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static void VisitChildren(this Element? element, Action<Element?> handler)
|
||||||
|
{
|
||||||
|
foreach (var child in element.GetChildren().Where(x => x != null))
|
||||||
|
VisitChildren(child, handler);
|
||||||
|
|
||||||
|
handler(element);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@ namespace QuestPDF.Helpers
|
|||||||
{
|
{
|
||||||
public static class Placeholders
|
public static class Placeholders
|
||||||
{
|
{
|
||||||
public static readonly Random Random = new Random();
|
public static readonly Random Random = new Random(3); // 3
|
||||||
|
|
||||||
#region Word Cache
|
#region Word Cache
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using QuestPDF.Drawing;
|
using QuestPDF.Drawing;
|
||||||
using QuestPDF.Elements;
|
using QuestPDF.Elements;
|
||||||
|
|
||||||
@@ -14,10 +15,9 @@ namespace QuestPDF.Infrastructure
|
|||||||
set => Child = value as Element;
|
set => Child = value as Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void HandleVisitor(Action<Element?> visit)
|
internal override IEnumerable<Element?> GetChildren()
|
||||||
{
|
{
|
||||||
Child?.HandleVisitor(visit);
|
yield return Child;
|
||||||
base.HandleVisitor(visit);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void CreateProxy(Func<Element?, Element?> create)
|
internal override void CreateProxy(Func<Element?, Element?> create)
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ namespace QuestPDF.Infrastructure
|
|||||||
internal IPageContext PageContext { get; set; }
|
internal IPageContext PageContext { get; set; }
|
||||||
internal ICanvas Canvas { get; set; }
|
internal ICanvas Canvas { get; set; }
|
||||||
|
|
||||||
internal virtual void HandleVisitor(Action<Element?> visit)
|
internal virtual IEnumerable<Element?> GetChildren()
|
||||||
{
|
{
|
||||||
visit(this);
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Initialize(IPageContext pageContext, ICanvas canvas)
|
internal void Initialize(IPageContext pageContext, ICanvas canvas)
|
||||||
@@ -28,10 +28,5 @@ namespace QuestPDF.Infrastructure
|
|||||||
|
|
||||||
internal abstract SpacePlan Measure(Size availableSpace);
|
internal abstract SpacePlan Measure(Size availableSpace);
|
||||||
internal abstract void Draw(Size availableSpace);
|
internal abstract void Draw(Size availableSpace);
|
||||||
|
|
||||||
protected virtual IEnumerable<string> GetDebugInformation()
|
|
||||||
{
|
|
||||||
yield break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,5 +17,7 @@
|
|||||||
{
|
{
|
||||||
return new Position(-X, -Y);
|
return new Position(-X, -Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override string ToString() => $"(Left: {X:N3}, Top: {Y:N3})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
public readonly struct Size
|
public readonly struct Size
|
||||||
{
|
{
|
||||||
public const float Epsilon = 0.001f;
|
public const float Epsilon = 0.001f;
|
||||||
|
public const float Infinity = float.PositiveInfinity;
|
||||||
|
|
||||||
public readonly float Width;
|
public readonly float Width;
|
||||||
public readonly float Height;
|
public readonly float Height;
|
||||||
@@ -16,6 +17,6 @@
|
|||||||
Height = height;
|
Height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"(W: {Width}, H: {Height})";
|
public override string ToString() => $"(Width: {Width:N3}, Height: {Height:N3})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,10 +4,10 @@
|
|||||||
<Authors>MarcinZiabek</Authors>
|
<Authors>MarcinZiabek</Authors>
|
||||||
<Company>CodeFlint</Company>
|
<Company>CodeFlint</Company>
|
||||||
<PackageId>QuestPDF</PackageId>
|
<PackageId>QuestPDF</PackageId>
|
||||||
<Version>2021.12.0-alpha0</Version>
|
<Version>2022.1.0-beta4</Version>
|
||||||
<PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
|
<PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
|
||||||
<PackageReleaseNotes>$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt"))</PackageReleaseNotes>
|
<PackageReleaseNotes>$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt"))</PackageReleaseNotes>
|
||||||
<LangVersion>8</LangVersion>
|
<LangVersion>9</LangVersion>
|
||||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
<PackageIcon>Logo.png</PackageIcon>
|
<PackageIcon>Logo.png</PackageIcon>
|
||||||
<PackageIconUrl>https://www.questpdf.com/images/package-logo.png</PackageIconUrl>
|
<PackageIconUrl>https://www.questpdf.com/images/package-logo.png</PackageIconUrl>
|
||||||
|
|||||||
@@ -1 +1,7 @@
|
|||||||
- Added support for generating documents in the XPS file format.
|
Introduced new element: `Table` - a great way to construct complex document structures, e.g. reports. This element covers all cases offered by combination of the `Stack` and the `Row` elements. Additionally, it provides support for more complex layouts and corner cases. Updating to the `Table` element can greatly simplify your code 😁
|
||||||
|
|
||||||
|
Other changes:
|
||||||
|
- Added new element `DefaultTextStyle` - it allows set new text style to all its children,
|
||||||
|
- Improved the default paging behavior for the `Row` element. In some minor corner cases it might cause infinite layout exceptions and confuse developers.
|
||||||
|
- Improved the `Row` element: added new type of column that combines constant and relative widths.
|
||||||
|
- Fixed default page sizes for: Letter and Legal.
|
||||||
|
|||||||
Reference in New Issue
Block a user