Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8551e1f33f | |||
| 3d0ca07b43 | |||
| a67fa754af | |||
| 208235ad1e | |||
| f24416474a | |||
| 01c2e8d3f5 | |||
| b3cef238ec | |||
| aede62c135 | |||
| db9db95dcc | |||
| 92263685d7 | |||
| ba4be521aa | |||
| f6967dfe8b | |||
| ff3dfe7831 | |||
| 5a1990d857 | |||
| 1b9b164583 | |||
| 9c64a6bc8d | |||
| 5402e5fedc | |||
| 2c14f91ebe | |||
| b533a32b61 | |||
| 262bd3c182 | |||
| fdf8e3eb1a | |||
| fcff989ab2 | |||
| 054e7a5f20 | |||
| a92cc25fb1 | |||
| 8db283d559 | |||
| 456c7a735b | |||
| 5bf2a9a3d3 | |||
| 8a64abaac2 | |||
| 68df390d0e | |||
| d73c694452 | |||
| 69e268733c | |||
| 657bbe2aac | |||
| 4479a49ad0 | |||
| 63288dfb60 | |||
| fc325d8b76 | |||
| 5ffeaa3075 | |||
| fe431fbc12 | |||
| e0de408821 | |||
| 8ce6afef5a | |||
| 1c70b20028 | |||
| 3aef4ec4c9 | |||
| 3649607bd9 | |||
| 9a6b11ab85 | |||
| ac29ed4794 | |||
| 5f7da8b5b3 | |||
| 78eef45ce2 | |||
| 5424d9f0a0 | |||
| 946fe91a98 | |||
| b5839d3989 | |||
| 2ca6d1cb47 | |||
| 49a3371af7 | |||
| 3f3ada6d8a | |||
| b96c67a8af | |||
| b9fbb9267f | |||
| 272b0fc11a | |||
| b2cc7bbd36 | |||
| f8057a840c | |||
| a2ce31022c | |||
| ba8b35b1c0 | |||
| 648a359d15 | |||
| cb493562fc |
@@ -13,11 +13,11 @@ namespace QuestPDF.Examples
|
||||
[Test]
|
||||
public void Example()
|
||||
{
|
||||
FontManager.RegisterFontType("LibreBarcode39", File.OpenRead("LibreBarcode39-Regular.ttf"));
|
||||
FontManager.RegisterFont(File.OpenRead("LibreBarcode39-Regular.ttf"));
|
||||
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(400, 100)
|
||||
.PageSize(400, 200)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
@@ -25,7 +25,9 @@ namespace QuestPDF.Examples
|
||||
.Background(Colors.White)
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.Text("*QuestPDF*", TextStyle.Default.FontType("LibreBarcode39").Size(64));
|
||||
.Text("*QuestPDF*")
|
||||
.FontFamily("Libre Barcode 39")
|
||||
.FontSize(64);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,10 @@ namespace QuestPDF.Examples
|
||||
.PrimaryLayer()
|
||||
.PaddingVertical(10)
|
||||
.PaddingHorizontal(20)
|
||||
.Text("Sample text", TextStyle.Default.Size(16).Color(Colors.Blue.Darken2).SemiBold());
|
||||
.Text("Sample text")
|
||||
.FontSize(16)
|
||||
.FontColor(Colors.Blue.Darken2)
|
||||
.SemiBold();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -52,14 +52,17 @@ namespace QuestPDF.Examples
|
||||
container
|
||||
.Background(Colors.White)
|
||||
.Padding(25)
|
||||
.Stack(stack =>
|
||||
.Column(column =>
|
||||
{
|
||||
stack
|
||||
column
|
||||
.Item()
|
||||
.PaddingBottom(10)
|
||||
.Text("Chart example", TextStyle.Default.Size(20).SemiBold().Color(Colors.Blue.Medium));
|
||||
.Text("Chart example")
|
||||
.FontSize(20)
|
||||
.SemiBold()
|
||||
.FontColor(Colors.Blue.Medium);
|
||||
|
||||
stack
|
||||
column
|
||||
.Item()
|
||||
.Border(1)
|
||||
.ExtendHorizontal()
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class ColumnExamples
|
||||
{
|
||||
[Test]
|
||||
public void Column()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ShowResults()
|
||||
.ProducePdf()
|
||||
.Render(container =>
|
||||
{
|
||||
container.Column(column =>
|
||||
{
|
||||
foreach (var i in Enumerable.Range(0, 10))
|
||||
column.Item().Element(Block);
|
||||
|
||||
static void Block(IContainer container)
|
||||
{
|
||||
container
|
||||
.Width(72)
|
||||
.Height(3.5f, Unit.Inch)
|
||||
.Height(1.5f, Unit.Inch)
|
||||
.Background(Placeholders.BackgroundColor());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Stability_NoItems()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProducePdf()
|
||||
.MaxPages(100)
|
||||
.PageSize(250, 150)
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.Column(column => { });
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ namespace QuestPDF.Examples
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(x => GenerateStructure(x, 16));
|
||||
.Render(x => GenerateStructure(x, 12));
|
||||
}
|
||||
|
||||
private void GenerateStructure(IContainer container, int level)
|
||||
@@ -36,8 +36,8 @@ namespace QuestPDF.Examples
|
||||
.BorderColor(Colors.Black)
|
||||
.Row(row =>
|
||||
{
|
||||
row.RelativeColumn().Element(x => GenerateStructure(x, level));
|
||||
row.RelativeColumn().Element(x => GenerateStructure(x, level));
|
||||
row.RelativeItem().Element(x => GenerateStructure(x, level));
|
||||
row.RelativeItem().Element(x => GenerateStructure(x, level));
|
||||
});
|
||||
}
|
||||
else
|
||||
@@ -45,10 +45,10 @@ namespace QuestPDF.Examples
|
||||
container
|
||||
.Border(level / 4f)
|
||||
.BorderColor(Colors.Black)
|
||||
.Stack(stack =>
|
||||
.Column(column =>
|
||||
{
|
||||
stack.Item().Element(x => GenerateStructure(x, level));
|
||||
stack.Item().Element(x => GenerateStructure(x, level));
|
||||
column.Item().Element(x => GenerateStructure(x, level));
|
||||
column.Item().Element(x => GenerateStructure(x, level));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,10 +22,10 @@ namespace QuestPDF.Examples
|
||||
|
||||
page.Header().Text("Header");
|
||||
|
||||
page.Content().PaddingVertical(10).Border(1).Padding(10).Stack(stack =>
|
||||
page.Content().PaddingVertical(10).Border(1).Padding(10).Column(column =>
|
||||
{
|
||||
foreach (var index in Enumerable.Range(1, 100))
|
||||
stack.Item().Text($"Line {index}", TextStyle.Default.Color(Placeholders.Color()));
|
||||
column.Item().Text($"Line {index}").FontColor(Placeholders.Color());
|
||||
});
|
||||
|
||||
page.Footer().Text("Footer");
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace QuestPDF.Examples
|
||||
public class DebuggingTesting
|
||||
{
|
||||
[Test]
|
||||
public void Stack()
|
||||
public void Column()
|
||||
{
|
||||
Assert.Throws<DocumentLayoutException>(() =>
|
||||
{
|
||||
@@ -23,7 +23,7 @@ namespace QuestPDF.Examples
|
||||
.Width(100)
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.DebugPointer("Example debug pointer")
|
||||
.Stack(x =>
|
||||
.Column(x =>
|
||||
{
|
||||
x.Item().Text("Test");
|
||||
x.Item().Width(150);
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.RenderDocument(container =>
|
||||
{
|
||||
@@ -24,13 +24,13 @@ namespace QuestPDF.Examples
|
||||
|
||||
page.Margin(20);
|
||||
page.Size(PageSizes.A4);
|
||||
page.Background(Colors.White);
|
||||
page.PageColor(Colors.White);
|
||||
|
||||
page.Content().Stack(stack =>
|
||||
page.Content().Column(column =>
|
||||
{
|
||||
stack.Item().Text(Placeholders.Sentence());
|
||||
column.Item().Text(Placeholders.Sentence());
|
||||
|
||||
stack.Item().Text(text =>
|
||||
column.Item().Text(text =>
|
||||
{
|
||||
// text in this block is additionally semibold
|
||||
text.DefaultTextStyle(TextStyle.Default.SemiBold());
|
||||
@@ -38,7 +38,7 @@ namespace QuestPDF.Examples
|
||||
text.Line(Placeholders.Sentence());
|
||||
|
||||
// this text has size 20 but also semibold and red
|
||||
text.Span(Placeholders.Sentence(), TextStyle.Default.Color(Colors.Red.Medium));
|
||||
text.Span(Placeholders.Sentence()).FontColor(Colors.Red.Medium);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,12 +24,12 @@ namespace QuestPDF.Examples
|
||||
container
|
||||
.Padding(10)
|
||||
.DefaultTextStyle(TextStyle.Default.Bold().Underline())
|
||||
.Stack(stack =>
|
||||
.Column(column =>
|
||||
{
|
||||
stack.Item().Text("Default style applies to all children", TextStyle.Default);
|
||||
stack.Item().Text("You can override certain styles", TextStyle.Default.Underline(false).Color(Colors.Green.Darken2));
|
||||
column.Item().Text("Default style applies to all children");
|
||||
column.Item().Text("You can override certain styles").Underline(false).FontColor(Colors.Green.Darken2);
|
||||
|
||||
stack.Item().PaddingTop(10).Border(1).Grid(grid =>
|
||||
column.Item().PaddingTop(10).Border(1).Grid(grid =>
|
||||
{
|
||||
grid.Columns(4);
|
||||
|
||||
@@ -43,7 +43,8 @@ namespace QuestPDF.Examples
|
||||
.Height(50)
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.Text(i, TextStyle.Default.Size(16 + i / 4));
|
||||
.Text(i)
|
||||
.FontSize(16 + i / 4);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,21 +23,21 @@ namespace QuestPDF.Examples
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A6);
|
||||
page.Margin(30);
|
||||
page.Background(Colors.White);
|
||||
page.Margin(5);
|
||||
page.PageColor(Colors.White);
|
||||
|
||||
page.Header().Stack(stack =>
|
||||
page.Header().Column(column =>
|
||||
{
|
||||
stack.Item().ShowOnce().Background(Colors.Blue.Lighten2).Height(60);
|
||||
stack.Item().SkipOnce().Background(Colors.Green.Lighten2).Height(40);
|
||||
column.Item().ShowOnce().Background(Colors.Blue.Lighten2).Height(60);
|
||||
column.Item().SkipOnce().Background(Colors.Green.Lighten2).Height(40);
|
||||
});
|
||||
|
||||
page.Content().PaddingVertical(10).Stack(stack =>
|
||||
page.Content().PaddingVertical(10).Column(column =>
|
||||
{
|
||||
stack.Spacing(10);
|
||||
column.Spacing(10);
|
||||
|
||||
foreach (var _ in Enumerable.Range(0, 13))
|
||||
stack.Item().Background(Colors.Grey.Lighten2).Height(40);
|
||||
column.Item().Background(Colors.Grey.Lighten2).Height(40);
|
||||
});
|
||||
|
||||
page.Footer().AlignCenter().Text(text =>
|
||||
|
||||
@@ -40,10 +40,12 @@ namespace QuestPDF.Examples
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
decoration
|
||||
.Header()
|
||||
.Before()
|
||||
.Background(Colors.Grey.Medium)
|
||||
.Padding(10)
|
||||
.Text("Notes", TextStyle.Default.Size(16).Color("#FFF"));
|
||||
.Text("Notes")
|
||||
.FontSize(16)
|
||||
.FontColor("#FFF");
|
||||
|
||||
decoration
|
||||
.Content()
|
||||
@@ -66,27 +68,27 @@ namespace QuestPDF.Examples
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(20)
|
||||
.Stack(stack =>
|
||||
.Column(column =>
|
||||
{
|
||||
stack.Item()
|
||||
column.Item()
|
||||
.PaddingBottom(10)
|
||||
.AlignCenter()
|
||||
.Text("This Row element is 700pt wide");
|
||||
|
||||
stack.Item().Row(row =>
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
row.ConstantColumn(100)
|
||||
row.ConstantItem(100)
|
||||
.Background(Colors.Grey.Lighten1)
|
||||
.Padding(10)
|
||||
.ExtendVertical()
|
||||
.Text("This column is 100 pt wide");
|
||||
|
||||
row.RelativeColumn()
|
||||
row.RelativeItem()
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Padding(10)
|
||||
.Text("This column takes 1/3 of the available space (200pt)");
|
||||
|
||||
row.RelativeColumn(2)
|
||||
row.RelativeItem(2)
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.Padding(10)
|
||||
.Text("This column takes 2/3 of the available space (400pt)");
|
||||
@@ -109,15 +111,15 @@ namespace QuestPDF.Examples
|
||||
.Row(row =>
|
||||
{
|
||||
row.Spacing(20);
|
||||
row.RelativeColumn(2).Border(1).Background(Colors.Grey.Lighten1);
|
||||
row.RelativeColumn(3).Border(1).Background(Colors.Grey.Lighten2);
|
||||
row.RelativeColumn(4).Border(1).Background(Colors.Grey.Lighten3);
|
||||
row.RelativeItem(2).Border(1).Background(Colors.Grey.Lighten1);
|
||||
row.RelativeItem(3).Border(1).Background(Colors.Grey.Lighten2);
|
||||
row.RelativeItem(4).Border(1).Background(Colors.Grey.Lighten3);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Stack()
|
||||
public void column()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
@@ -127,13 +129,13 @@ namespace QuestPDF.Examples
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(15)
|
||||
.Stack(stack =>
|
||||
.Column(column =>
|
||||
{
|
||||
stack.Spacing(15);
|
||||
column.Spacing(15);
|
||||
|
||||
stack.Item().Background(Colors.Grey.Medium).Height(50);
|
||||
stack.Item().Background(Colors.Grey.Lighten1).Height(100);
|
||||
stack.Item().Background(Colors.Grey.Lighten2).Height(150);
|
||||
column.Item().Background(Colors.Grey.Medium).Height(50);
|
||||
column.Item().Background(Colors.Grey.Lighten1).Height(100);
|
||||
column.Item().Background(Colors.Grey.Lighten2).Height(150);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -267,12 +269,12 @@ namespace QuestPDF.Examples
|
||||
layers
|
||||
.PrimaryLayer()
|
||||
.Padding(25)
|
||||
.Stack(stack =>
|
||||
.Column(column =>
|
||||
{
|
||||
stack.Spacing(5);
|
||||
column.Spacing(5);
|
||||
|
||||
foreach (var _ in Enumerable.Range(0, 7))
|
||||
stack.Item().Text(Placeholders.Sentence());
|
||||
column.Item().Text(Placeholders.Sentence());
|
||||
});
|
||||
|
||||
// layer above the main content
|
||||
@@ -280,12 +282,15 @@ namespace QuestPDF.Examples
|
||||
.Layer()
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.Text("Watermark", TextStyle.Default.Size(48).Bold().Color(Colors.Green.Lighten3));
|
||||
.Text("Watermark")
|
||||
.FontSize(48)
|
||||
.Bold()
|
||||
.FontColor(Colors.Green.Lighten3);
|
||||
|
||||
layers
|
||||
.Layer()
|
||||
.AlignBottom()
|
||||
.Text(text => text.CurrentPageNumber(TextStyle.Default.Size(16).Color(Colors.Green.Medium)));
|
||||
.Text(text => text.CurrentPageNumber().FontSize(16).FontColor(Colors.Green.Medium));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -304,16 +309,16 @@ namespace QuestPDF.Examples
|
||||
// {
|
||||
// page.Header().PageNumber("Page {pdf:currentPage}");
|
||||
//
|
||||
// page.Content().Height(300).Stack(content =>
|
||||
// page.Content().Height(300).column(content =>
|
||||
// {
|
||||
// content.Item().Height(200).Background(Colors.Grey.Lighten2);
|
||||
//
|
||||
// content.Item().EnsureSpace(100).Stack(stack =>
|
||||
// content.Item().EnsureSpace(100).column(column =>
|
||||
// {
|
||||
// stack.Spacing(10);
|
||||
// column.Spacing(10);
|
||||
//
|
||||
// foreach (var _ in Enumerable.Range(0, 4))
|
||||
// stack.Item().Height(50).Background(Colors.Green.Lighten1);
|
||||
// column.Item().Height(50).Background(Colors.Green.Lighten1);
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
@@ -378,7 +383,7 @@ namespace QuestPDF.Examples
|
||||
.Row(row =>
|
||||
{
|
||||
foreach (var color in colors)
|
||||
row.RelativeColumn().Background(color);
|
||||
row.RelativeItem().Background(color);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -416,7 +421,9 @@ namespace QuestPDF.Examples
|
||||
.Border(1)
|
||||
.BorderColor(Colors.Grey.Medium)
|
||||
.Padding(10)
|
||||
.Text(font, TextStyle.Default.FontType(font).Size(16));
|
||||
.Text(font)
|
||||
.FontFamily(font)
|
||||
.FontSize(16);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -437,10 +444,10 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
layers.Layer().Text("Something else");
|
||||
|
||||
layers.PrimaryLayer().Stack(stack =>
|
||||
layers.PrimaryLayer().Column(column =>
|
||||
{
|
||||
stack.Item().PaddingTop(20).Text("Text 1");
|
||||
stack.Item().PaddingTop(40).Text("Text 2");
|
||||
column.Item().PaddingTop(20).Text("Text 1");
|
||||
column.Item().PaddingTop(40).Text("Text 2");
|
||||
});
|
||||
|
||||
layers.Layer().Canvas((canvas, size) =>
|
||||
@@ -456,7 +463,7 @@ namespace QuestPDF.Examples
|
||||
});
|
||||
|
||||
layers.Layer().Background("#8F00").Extend();
|
||||
layers.Layer().PaddingTop(40).Text("It works!", TextStyle.Default.Size(24));
|
||||
layers.Layer().PaddingTop(40).Text("It works!").FontSize(24);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -477,7 +484,7 @@ namespace QuestPDF.Examples
|
||||
//.MinimalBox()
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Padding(15)
|
||||
.Text("Test of the \n box element", TextStyle.Default.Size(20));
|
||||
.Text("Test of the \n box element").FontSize(20);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -501,13 +508,14 @@ namespace QuestPDF.Examples
|
||||
.SemiBold();
|
||||
|
||||
decoration
|
||||
.Header()
|
||||
.Before()
|
||||
.PaddingBottom(10)
|
||||
.Text("Example: scale component", headerFontStyle);
|
||||
.Text("Example: scale component")
|
||||
.Style(headerFontStyle);
|
||||
|
||||
decoration
|
||||
.Content()
|
||||
.Stack(stack =>
|
||||
.Column(column =>
|
||||
{
|
||||
var scales = new[] { 0.8f, 0.9f, 1.1f, 1.2f };
|
||||
|
||||
@@ -519,13 +527,14 @@ namespace QuestPDF.Examples
|
||||
|
||||
var fontStyle = TextStyle.Default.Size(16);
|
||||
|
||||
stack
|
||||
column
|
||||
.Item()
|
||||
.Border(1)
|
||||
.Background(fontColor)
|
||||
.Scale(scale)
|
||||
.Padding(5)
|
||||
.Text($"Content with {scale} scale.", fontStyle);
|
||||
.Text($"Content with {scale} scale.")
|
||||
.Style(fontStyle);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -555,7 +564,8 @@ namespace QuestPDF.Examples
|
||||
.BorderColor(Colors.Green.Darken1)
|
||||
|
||||
.Padding(50)
|
||||
.Text("Moved text", TextStyle.Default.Size(25));
|
||||
.Text("Moved text")
|
||||
.FontSize(25);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -591,7 +601,8 @@ namespace QuestPDF.Examples
|
||||
.MinimalBox()
|
||||
.Background(Colors.White)
|
||||
.Padding(10)
|
||||
.Text($"Rotated {turns * 90}°", TextStyle.Default.Size(16));
|
||||
.Text($"Rotated {turns * 90}°")
|
||||
.FontSize(16);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -689,7 +700,8 @@ namespace QuestPDF.Examples
|
||||
.MinimalBox()
|
||||
.Background(Colors.White)
|
||||
.Padding(10)
|
||||
.Text($"Flipped {turns}", TextStyle.Default.Size(16));
|
||||
.Text($"Flipped {turns}")
|
||||
.FontSize(16);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -708,14 +720,14 @@ namespace QuestPDF.Examples
|
||||
.Border(2)
|
||||
.Row(row =>
|
||||
{
|
||||
row.ConstantColumn(25)
|
||||
row.ConstantItem(25)
|
||||
.Border(1)
|
||||
.RotateLeft()
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.Text("Sample text");
|
||||
|
||||
row.RelativeColumn().Border(1).Padding(5).Text(Placeholders.Paragraph());
|
||||
row.RelativeItem().Border(1).Padding(5).Text(Placeholders.Paragraph());
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -730,12 +742,12 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.PaddingLeft(75)
|
||||
.Stack(stack =>
|
||||
.PaddingLeft(50)
|
||||
.Column(column =>
|
||||
{
|
||||
stack.Item().Width(300).Height(150).Background(Colors.Blue.Lighten4);
|
||||
column.Item().Width(300).Height(150).Background(Colors.Blue.Lighten4);
|
||||
|
||||
stack
|
||||
column
|
||||
.Item()
|
||||
|
||||
// creates an infinite space for its child
|
||||
@@ -751,7 +763,7 @@ namespace QuestPDF.Examples
|
||||
|
||||
.Background(Colors.Blue.Darken1);
|
||||
|
||||
stack.Item().Width(300).Height(150).Background(Colors.Blue.Lighten3);
|
||||
column.Item().Width(300).Height(150).Background(Colors.Blue.Lighten3);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -766,13 +778,13 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.Stack(stack =>
|
||||
.Column(column =>
|
||||
{
|
||||
stack.Item().Row(row =>
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn().LabelCell("Label 1");
|
||||
row.RelativeItem().LabelCell("Label 1");
|
||||
|
||||
row.RelativeColumn(3).Grid(grid =>
|
||||
row.RelativeItem(3).Grid(grid =>
|
||||
{
|
||||
grid.Columns(3);
|
||||
|
||||
@@ -784,11 +796,11 @@ namespace QuestPDF.Examples
|
||||
});
|
||||
});
|
||||
|
||||
stack.Item().Row(row =>
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn().ValueCell().Text("Value 1");
|
||||
row.RelativeItem().ValueCell().Text("Value 1");
|
||||
|
||||
row.RelativeColumn(3).Grid(grid =>
|
||||
row.RelativeItem(3).Grid(grid =>
|
||||
{
|
||||
grid.Columns(3);
|
||||
|
||||
@@ -800,10 +812,10 @@ namespace QuestPDF.Examples
|
||||
});
|
||||
});
|
||||
|
||||
stack.Item().Row(row =>
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn().LabelCell("Label 6");
|
||||
row.RelativeColumn().ValueCell().Text("Value 6");
|
||||
row.RelativeItem().LabelCell("Label 6");
|
||||
row.RelativeItem().ValueCell().Text("Value 6");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,6 +23,8 @@ namespace QuestPDF.Examples.Engine
|
||||
private bool ApplyCaching { get; set; }
|
||||
private bool ApplyDebugging { get; set; }
|
||||
private RenderingTestResult ResultType { get; set; } = RenderingTestResult.Images;
|
||||
|
||||
private bool ShowingResultsEnabled = true;
|
||||
|
||||
private RenderingTest()
|
||||
{
|
||||
@@ -114,7 +116,7 @@ namespace QuestPDF.Examples.Engine
|
||||
Func<int, string> fileNameSchema = i => $"{FileNamePrefix}-${i}.png";
|
||||
document.GenerateImages(fileNameSchema);
|
||||
|
||||
if (ShowResult)
|
||||
if (ShowResult && ShowingResultsEnabled)
|
||||
Process.Start("explorer", fileNameSchema(0));
|
||||
}
|
||||
|
||||
@@ -123,7 +125,7 @@ namespace QuestPDF.Examples.Engine
|
||||
var fileName = $"{FileNamePrefix}.pdf";
|
||||
document.GeneratePdf(fileName);
|
||||
|
||||
if (ShowResult)
|
||||
if (ShowResult && ShowingResultsEnabled)
|
||||
Process.Start("explorer", fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,19 +21,19 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
page.Margin(20);
|
||||
page.Size(PageSizes.A7.Landscape());
|
||||
page.Background(Colors.White);
|
||||
page.PageColor(Colors.White);
|
||||
|
||||
page.Header().Text("With ensure space", TextStyle.Default.SemiBold());
|
||||
page.Header().Text("With ensure space").SemiBold();
|
||||
|
||||
page.Content().Stack(stack =>
|
||||
page.Content().Column(column =>
|
||||
{
|
||||
stack
|
||||
column
|
||||
.Item()
|
||||
.ExtendHorizontal()
|
||||
.Height(75)
|
||||
.Background(Colors.Grey.Lighten2);
|
||||
|
||||
stack
|
||||
column
|
||||
.Item()
|
||||
.EnsureSpace(100)
|
||||
.Text(Placeholders.LoremIpsum());
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class ExecutionOrderExamples
|
||||
{
|
||||
[Test]
|
||||
public void Example()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(400, 300)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.DefaultTextStyle(TextStyle.Default.Size(18))
|
||||
.Padding(25)
|
||||
.Row(row =>
|
||||
{
|
||||
row.Spacing(25);
|
||||
|
||||
row.RelativeItem()
|
||||
.Border(1)
|
||||
.Padding(15)
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Text("Lorem ipsum");
|
||||
|
||||
row.RelativeItem()
|
||||
.Border(1)
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Padding(15)
|
||||
.Text("dolor sit amet");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Example2()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(200, 200)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.Border(2)
|
||||
.Width(150)
|
||||
.Height(150)
|
||||
|
||||
.Background(Colors.Blue.Lighten2)
|
||||
.PaddingTop(50)
|
||||
|
||||
.Background(Colors.Green.Lighten2)
|
||||
.PaddingRight(50)
|
||||
|
||||
.Background(Colors.Red.Lighten2)
|
||||
.PaddingBottom(50)
|
||||
|
||||
.Background(Colors.Amber.Lighten2)
|
||||
.PaddingLeft(50)
|
||||
|
||||
.Background(Colors.Grey.Lighten2);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,9 @@ namespace QuestPDF.Examples
|
||||
.Padding(5);
|
||||
}
|
||||
|
||||
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text, TextStyle.Default.SemiBold());
|
||||
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text).SemiBold();
|
||||
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 static void ValueCell(this IContainer container, string text) => container.ValueCell().Text(text);
|
||||
}
|
||||
|
||||
public class FrameExample
|
||||
@@ -36,14 +36,14 @@ namespace QuestPDF.Examples
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(25)
|
||||
.Stack(stack =>
|
||||
.Column(column =>
|
||||
{
|
||||
for(var i = 1; i <= 4; i++)
|
||||
{
|
||||
stack.Item().Row(row =>
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(2).LabelCell(Placeholders.Label());
|
||||
row.RelativeColumn(3).ValueCell().Text(Placeholders.Paragraph());
|
||||
row.RelativeItem(2).LabelCell(Placeholders.Label());
|
||||
row.RelativeItem(3).ValueCell().Text(Placeholders.Paragraph());
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -19,17 +19,17 @@ namespace QuestPDF.Examples
|
||||
.ShowResults()
|
||||
.Render(page =>
|
||||
{
|
||||
page.Padding(25).Stack(stack =>
|
||||
page.Padding(25).Column(column =>
|
||||
{
|
||||
stack.Spacing(25);
|
||||
column.Spacing(25);
|
||||
|
||||
stack.Item().Image("logo.png");
|
||||
column.Item().Image("logo.png");
|
||||
|
||||
var binaryData = File.ReadAllBytes("logo.png");
|
||||
stack.Item().Image(binaryData);
|
||||
column.Item().Image(binaryData);
|
||||
|
||||
using var stream = new FileStream("logo.png", FileMode.Open);
|
||||
stack.Item().Image(stream);
|
||||
column.Item().Image(stream);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace QuestPDF.Examples
|
||||
.Padding(25)
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
decoration.Header().Text(text =>
|
||||
decoration.Before().Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(TextStyle.Default.Size(20));
|
||||
|
||||
@@ -74,7 +74,8 @@ namespace QuestPDF.Examples
|
||||
.PrimaryLayer()
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.Text(sizeText, TextStyle.Default.Size(15));
|
||||
.Text(sizeText)
|
||||
.FontSize(15);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.IO;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class LineExamples
|
||||
{
|
||||
[Test]
|
||||
public void LineHorizontal()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A5)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(15)
|
||||
.MinimalBox()
|
||||
.DefaultTextStyle(TextStyle.Default.Size(16))
|
||||
.Column(column =>
|
||||
{
|
||||
column.Item().Text("Above text");
|
||||
column.Item().PaddingVertical(5).LineHorizontal(1).LineColor(Colors.Grey.Medium);
|
||||
column.Item().Text("Below text");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LineVertical()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A5)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(15)
|
||||
.DefaultTextStyle(TextStyle.Default.Size(16))
|
||||
.Row(row =>
|
||||
{
|
||||
row.AutoItem().Text("Left text");
|
||||
row.AutoItem().PaddingHorizontal(10).LineVertical(1).LineColor(Colors.Grey.Medium);
|
||||
row.AutoItem().Text("Right text");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ namespace QuestPDF.Examples
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(25)
|
||||
.Stack(column =>
|
||||
.Column(column =>
|
||||
{
|
||||
column.Spacing(10);
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Diagnostics;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class MinimalApiExamples
|
||||
{
|
||||
[Test]
|
||||
public void MinimalApi()
|
||||
{
|
||||
Document
|
||||
.Create(container =>
|
||||
{
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A4);
|
||||
page.Margin(2, Unit.Centimetre);
|
||||
page.PageColor(Colors.White);
|
||||
page.DefaultTextStyle(x => x.FontSize(20));
|
||||
|
||||
page.Header()
|
||||
.Text("Hello PDF!")
|
||||
.SemiBold().FontSize(36).FontColor(Colors.Blue.Medium);
|
||||
|
||||
page.Content()
|
||||
.PaddingVertical(1, Unit.Centimetre)
|
||||
.Column(x =>
|
||||
{
|
||||
x.Spacing(20);
|
||||
|
||||
x.Item().Text(Placeholders.LoremIpsum());
|
||||
x.Item().Image(Placeholders.Image(200, 100));
|
||||
});
|
||||
|
||||
page.Footer()
|
||||
.AlignCenter()
|
||||
.Text(x =>
|
||||
{
|
||||
x.Span("Page ");
|
||||
x.CurrentPageNumber();
|
||||
});
|
||||
});
|
||||
})
|
||||
.GeneratePdf("hello.pdf");
|
||||
|
||||
Process.Start("explorer.exe", "hello.pdf");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,8 @@ namespace QuestPDF.Examples
|
||||
.Background("FFF")
|
||||
.Padding(5)
|
||||
.AlignCenter()
|
||||
.Text("Sample text", TextStyle.Default.FontType("Segoe UI emoji"));
|
||||
.Text("Sample text")
|
||||
.FontFamily("Segoe UI emoji");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -63,7 +64,7 @@ namespace QuestPDF.Examples
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Stack(column =>
|
||||
.Column(column =>
|
||||
{
|
||||
column
|
||||
.Item()
|
||||
@@ -113,14 +114,14 @@ namespace QuestPDF.Examples
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Stack(column =>
|
||||
.Column(column =>
|
||||
{
|
||||
column
|
||||
.Item()
|
||||
.Height(150)
|
||||
.Row(row =>
|
||||
{
|
||||
row.RelativeColumn()
|
||||
row.RelativeItem()
|
||||
.Extend()
|
||||
.Background("FFF")
|
||||
|
||||
@@ -128,7 +129,7 @@ namespace QuestPDF.Examples
|
||||
.Width(50)
|
||||
.Background("444");
|
||||
|
||||
row.RelativeColumn()
|
||||
row.RelativeItem()
|
||||
.Extend()
|
||||
.Background("BBB")
|
||||
|
||||
@@ -142,7 +143,7 @@ namespace QuestPDF.Examples
|
||||
.Height(150)
|
||||
.Row(row =>
|
||||
{
|
||||
row.RelativeColumn()
|
||||
row.RelativeItem()
|
||||
.Extend()
|
||||
.Background("BBB")
|
||||
|
||||
@@ -150,7 +151,7 @@ namespace QuestPDF.Examples
|
||||
.Width(50)
|
||||
.Background("444");
|
||||
|
||||
row.RelativeColumn()
|
||||
row.RelativeItem()
|
||||
.Extend()
|
||||
.Background("BBB")
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class PageBackgroundForegroundExample
|
||||
{
|
||||
[Test]
|
||||
public void PageBackgroundForeground()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.MaxPages(100)
|
||||
.ShowResults()
|
||||
.RenderDocument(document =>
|
||||
{
|
||||
document.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A4);
|
||||
page.Margin(1, Unit.Inch);
|
||||
page.DefaultTextStyle(TextStyle.Default.FontSize(16));
|
||||
page.PageColor(Colors.White);
|
||||
|
||||
const string transparentBlue = "#662196f3";
|
||||
|
||||
page.Background()
|
||||
.AlignTop()
|
||||
.ExtendHorizontal()
|
||||
.Height(200)
|
||||
.Background(transparentBlue);
|
||||
|
||||
page.Foreground()
|
||||
.AlignBottom()
|
||||
.ExtendHorizontal()
|
||||
.Height(250)
|
||||
.Background(transparentBlue);
|
||||
|
||||
page.Header().Text("Background and foreground").Bold().FontColor(Colors.Blue.Darken2).FontSize(36);
|
||||
|
||||
page.Content().PaddingVertical(25).Column(column =>
|
||||
{
|
||||
column.Spacing(25);
|
||||
|
||||
foreach (var i in Enumerable.Range(0, 100))
|
||||
column.Item().Background(Colors.Grey.Lighten2).Height(75);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ namespace QuestPDF.Examples
|
||||
public class RowExamples
|
||||
{
|
||||
[Test]
|
||||
public void ColumnTypes()
|
||||
public void ItemTypes()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
@@ -22,25 +22,24 @@ namespace QuestPDF.Examples
|
||||
.Padding(25)
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Stack(stack =>
|
||||
.Column(column =>
|
||||
{
|
||||
stack.Item().LabelCell("Total width: 600px");
|
||||
column.Item().LabelCell("Total width: 600px");
|
||||
|
||||
stack.Item().Row(row =>
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
row.ConstantColumn(150).ValueCell("150px");
|
||||
row.ConstantColumn(100).ValueCell("100px");
|
||||
row.RelativeColumn(4).ValueCell("200px");
|
||||
row.RelativeColumn(3).ValueCell("150px");
|
||||
row.ConstantItem(150).ValueCell("150px");
|
||||
row.ConstantItem(100).ValueCell("100px");
|
||||
row.RelativeItem(4).ValueCell("200px");
|
||||
row.RelativeItem(3).ValueCell("150px");
|
||||
});
|
||||
|
||||
stack.Item().Row(row =>
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
row.ConstantColumn(100).ValueCell("100px");
|
||||
row.ConstantColumn(50).ValueCell("50px");
|
||||
row.RelativeColumn(3).ValueCell("300px");
|
||||
row.RelativeColumn(1).ValueCell("100px");
|
||||
row.ConstantColumn(50).ValueCell("50px");
|
||||
row.ConstantItem(100).ValueCell("100px");
|
||||
row.ConstantItem(50).ValueCell("50px");
|
||||
row.RelativeItem(2).ValueCell("100px");
|
||||
row.RelativeItem(1).ValueCell("50px");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -63,17 +62,17 @@ namespace QuestPDF.Examples
|
||||
.Padding(25)
|
||||
.Row(row =>
|
||||
{
|
||||
row.RelativeColumn().Stack(stack =>
|
||||
row.RelativeItem().Column(column =>
|
||||
{
|
||||
stack.Item().ShowOnce().Element(CreateBox).Text("X");
|
||||
stack.Item().Element(CreateBox).Text("1");
|
||||
stack.Item().Element(CreateBox).Text("2");
|
||||
column.Item().ShowOnce().Element(CreateBox).Text("X");
|
||||
column.Item().Element(CreateBox).Text("1");
|
||||
column.Item().Element(CreateBox).Text("2");
|
||||
});
|
||||
|
||||
row.RelativeColumn().Stack(stack =>
|
||||
row.RelativeItem().Column(column =>
|
||||
{
|
||||
stack.Item().Element(CreateBox).Text("1");
|
||||
stack.Item().Element(CreateBox).Text("2");
|
||||
column.Item().Element(CreateBox).Text("1");
|
||||
column.Item().Element(CreateBox).Text("2");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -90,5 +89,21 @@ namespace QuestPDF.Examples
|
||||
.ShowOnce();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Stability_NoItems()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProducePdf()
|
||||
.MaxPages(100)
|
||||
.PageSize(250, 150)
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.Row(row => { });
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class ScaleToFitExamples
|
||||
{
|
||||
[Test]
|
||||
public void ScaleToFit()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container.Padding(25).Column(column =>
|
||||
{
|
||||
var text = Placeholders.Paragraph();
|
||||
|
||||
foreach (var i in Enumerable.Range(2, 5))
|
||||
{
|
||||
column
|
||||
.Item()
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Padding(5)
|
||||
.Width(i * 40)
|
||||
.Height(i * 20)
|
||||
.ScaleToFit()
|
||||
.Text(text);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,20 +21,20 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
page.Margin(20);
|
||||
page.Size(PageSizes.A7.Landscape());
|
||||
page.Background(Colors.White);
|
||||
page.PageColor(Colors.White);
|
||||
|
||||
page.Header().Text("With show once", TextStyle.Default.SemiBold());
|
||||
page.Header().Text("With show once").SemiBold();
|
||||
|
||||
page.Content().PaddingVertical(5).Row(row =>
|
||||
{
|
||||
row.RelativeColumn()
|
||||
row.RelativeItem()
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Border(1)
|
||||
.Padding(5)
|
||||
.ShowOnce()
|
||||
.Text(Placeholders.Label());
|
||||
|
||||
row.RelativeColumn(2)
|
||||
row.RelativeItem(2)
|
||||
.Border(1)
|
||||
.Padding(5)
|
||||
.Text(Placeholders.Paragraph());
|
||||
|
||||
@@ -21,17 +21,18 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
page.Margin(20);
|
||||
page.Size(PageSizes.A7.Landscape());
|
||||
page.Background(Colors.White);
|
||||
page.PageColor(Colors.White);
|
||||
|
||||
page.Header().Stack(stack =>
|
||||
page.Header().Column(column =>
|
||||
{
|
||||
stack.Item().ShowOnce().Text("This header is visible on the first page.");
|
||||
stack.Item().SkipOnce().Text("This header is visible on the second page and all following.");
|
||||
column.Item().ShowOnce().Text("This header is visible on the first page.");
|
||||
column.Item().SkipOnce().Text("This header is visible on the second page and all following.");
|
||||
});
|
||||
|
||||
page.Content()
|
||||
.PaddingVertical(10)
|
||||
.Text(Placeholders.Paragraphs(), TextStyle.Default.Color(Colors.Grey.Medium));
|
||||
.Text(Placeholders.Paragraphs())
|
||||
.FontColor(Colors.Grey.Medium);
|
||||
|
||||
page.Footer().Text(text =>
|
||||
{
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class StopPaging
|
||||
{
|
||||
[Test]
|
||||
public void Example()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 250)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.DefaultTextStyle(TextStyle.Default.Size(14))
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
decoration
|
||||
.Before()
|
||||
.Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(TextStyle.Default.SemiBold().Color(Colors.Blue.Medium));
|
||||
|
||||
text.Span("Page ");
|
||||
text.CurrentPageNumber();
|
||||
});
|
||||
|
||||
decoration
|
||||
.Content()
|
||||
.Column(column =>
|
||||
{
|
||||
column.Spacing(25);
|
||||
column.Item().StopPaging().Text(Placeholders.LoremIpsum());
|
||||
column.Item().ExtendHorizontal().Height(75).Background(Colors.Grey.Lighten2);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ namespace QuestPDF.Examples
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(x => ComposeBook(x, chapters));
|
||||
.RenderDocument(x => ComposeBook(x, chapters));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -42,8 +42,8 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProducePdf()
|
||||
.Render(x => ComposeBook(x, chapters));
|
||||
.ProducePdf()
|
||||
.RenderDocument(x => ComposeBook(x, chapters));
|
||||
}
|
||||
|
||||
IEnumerable<float> PerformTest(int attempts)
|
||||
@@ -102,112 +102,105 @@ namespace QuestPDF.Examples
|
||||
}
|
||||
}
|
||||
|
||||
private void ComposeBook(IContainer container, ICollection<BookChapter> chapters)
|
||||
private void ComposeBook(IDocumentContainer container, ICollection<BookChapter> chapters)
|
||||
{
|
||||
var subtitleStyle = TextStyle.Default.Size(24).SemiBold().Color(Colors.Blue.Medium);
|
||||
var normalStyle = TextStyle.Default.Size(14);
|
||||
|
||||
ComposePage(container);
|
||||
|
||||
void ComposePage(IContainer container)
|
||||
container.Page(page =>
|
||||
{
|
||||
container
|
||||
.Padding(50)
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
decoration
|
||||
.Content()
|
||||
.Stack(stack =>
|
||||
{
|
||||
stack.Item().Element(Title);
|
||||
stack.Item().PageBreak();
|
||||
stack.Item().Element(TableOfContents);
|
||||
stack.Item().PageBreak();
|
||||
page.Margin(50);
|
||||
|
||||
page.Content().Column(column =>
|
||||
{
|
||||
column.Item().Element(Title);
|
||||
column.Item().PageBreak();
|
||||
column.Item().Element(TableOfContents);
|
||||
column.Item().PageBreak();
|
||||
|
||||
Chapters(stack);
|
||||
Chapters(column);
|
||||
|
||||
stack.Item().Element(Acknowledgements);
|
||||
});
|
||||
column.Item().Element(Acknowledgements);
|
||||
});
|
||||
|
||||
page.Footer().Element(Footer);
|
||||
});
|
||||
|
||||
decoration.Footer().Element(Footer);
|
||||
});
|
||||
}
|
||||
|
||||
void Title(IContainer container)
|
||||
{
|
||||
container
|
||||
.Extend()
|
||||
.PaddingBottom(200)
|
||||
.AlignBottom()
|
||||
.Stack(stack =>
|
||||
.Column(column =>
|
||||
{
|
||||
stack.Item().Text("Quo Vadis", TextStyle.Default.Size(72).Bold().Color(Colors.Blue.Darken2));
|
||||
stack.Item().Text("Henryk Sienkiewicz", TextStyle.Default.Size(24).Color(Colors.Grey.Darken2));
|
||||
column.Item().Text("Quo Vadis").FontSize(72).Bold().FontColor(Colors.Blue.Darken2);
|
||||
column.Item().Text("Henryk Sienkiewicz").FontSize(24).FontColor(Colors.Grey.Darken2);
|
||||
});
|
||||
}
|
||||
|
||||
void TableOfContents(IContainer container)
|
||||
{
|
||||
container.Stack(stack =>
|
||||
container.Column(column =>
|
||||
{
|
||||
SectionTitle(stack, "Spis treści");
|
||||
SectionTitle(column, "Spis treści");
|
||||
|
||||
foreach (var chapter in chapters)
|
||||
{
|
||||
stack.Item().InternalLink(chapter.Title).Row(row =>
|
||||
column.Item().InternalLink(chapter.Title).Row(row =>
|
||||
{
|
||||
row.RelativeColumn().Text(chapter.Title, normalStyle);
|
||||
row.ConstantColumn(100).AlignRight().Text(text => text.PageNumberOfLocation(chapter.Title, normalStyle));
|
||||
row.RelativeItem().Text(chapter.Title).Style(normalStyle);
|
||||
row.ConstantItem(100).AlignRight().Text(text => text.BeginPageNumberOfSection(chapter.Title).Style(normalStyle));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Chapters(StackDescriptor stack)
|
||||
void Chapters(ColumnDescriptor column)
|
||||
{
|
||||
foreach (var chapter in chapters)
|
||||
{
|
||||
stack.Item().Element(container => Chapter(container, chapter.Title, chapter.Content));
|
||||
column.Item().Element(container => Chapter(container, chapter.Title, chapter.Content));
|
||||
}
|
||||
}
|
||||
|
||||
void Chapter(IContainer container, string title, string content)
|
||||
{
|
||||
container.Stack(stack =>
|
||||
container.Column(column =>
|
||||
{
|
||||
SectionTitle(stack, title);
|
||||
SectionTitle(column, title);
|
||||
|
||||
stack.Item().Text(text =>
|
||||
column.Item().Text(text =>
|
||||
{
|
||||
text.ParagraphSpacing(5);
|
||||
text.Span(content, normalStyle);
|
||||
text.Span(content).Style(normalStyle);
|
||||
});
|
||||
|
||||
stack.Item().PageBreak();
|
||||
column.Item().PageBreak();
|
||||
});
|
||||
}
|
||||
|
||||
void Acknowledgements(IContainer container)
|
||||
{
|
||||
container.Stack(stack =>
|
||||
container.Column(column =>
|
||||
{
|
||||
SectionTitle(stack, "Podziękowania");
|
||||
SectionTitle(column, "Podziękowania");
|
||||
|
||||
stack.Item().Text(text =>
|
||||
column.Item().Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(normalStyle);
|
||||
|
||||
text.Span("Ten dokument został wygenerowany na podstawie książki w formacie TXT opublikowanej w serwisie ");
|
||||
text.ExternalLocation("wolnelektury.pl", "https://wolnelektury.pl/", normalStyle.Color(Colors.Blue.Medium).Underline());
|
||||
text.Hyperlink("wolnelektury.pl", "https://wolnelektury.pl/").FontColor(Colors.Blue.Medium).Underline();
|
||||
text.Span(". Dziękuję za wspieranie polskiego czytelnictwa!");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void SectionTitle(StackDescriptor stack, string text)
|
||||
void SectionTitle(ColumnDescriptor column, string text)
|
||||
{
|
||||
stack.Item().Location(text).Text(text, subtitleStyle);
|
||||
stack.Item().PaddingTop(10).PaddingBottom(50).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).ExtendHorizontal();
|
||||
column.Item().Location(text).Text(text).Style(subtitleStyle);
|
||||
column.Item().PaddingTop(10).PaddingBottom(50).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).ExtendHorizontal();
|
||||
}
|
||||
|
||||
void Footer(IContainer container)
|
||||
|
||||
@@ -29,9 +29,10 @@ namespace QuestPDF.Examples
|
||||
.Padding(10)
|
||||
.Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(TextStyle.Default.Size(20));
|
||||
text.DefaultTextStyle(TextStyle.Default.FontSize(20));
|
||||
text.Span("This is a normal text, followed by an ");
|
||||
text.Span("underlined red text.", TextStyle.Default.Size(20).Color(Colors.Red.Medium).Underline());
|
||||
text.Span("underlined red text").FontColor(Colors.Red.Medium).Underline();
|
||||
text.Span(".");
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -57,7 +58,7 @@ namespace QuestPDF.Examples
|
||||
|
||||
foreach (var i in Enumerable.Range(1, 3))
|
||||
{
|
||||
text.Span($"Paragraph {i}: ", TextStyle.Default.SemiBold());
|
||||
text.Span($"Paragraph {i}: ").SemiBold();
|
||||
text.Line(Placeholders.Paragraph());
|
||||
}
|
||||
});
|
||||
@@ -81,7 +82,7 @@ namespace QuestPDF.Examples
|
||||
.Padding(10)
|
||||
.Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(TextStyle.Default.Size(20));
|
||||
text.DefaultTextStyle(TextStyle.Default.FontSize(20));
|
||||
text.Span("This is a random image aligned to the baseline: ");
|
||||
|
||||
text.Element()
|
||||
@@ -120,13 +121,13 @@ namespace QuestPDF.Examples
|
||||
|
||||
text.Line(Placeholders.LoremIpsum());
|
||||
|
||||
text.Span($"This is target text that should show up. {DateTime.UtcNow:T} > This is a short sentence that will be wrapped into second line hopefully, right? <", TextStyle.Default.Underline());
|
||||
text.Span($"This is target text that should show up. {DateTime.UtcNow:T} > This is a short sentence that will be wrapped into second line hopefully, right? <").Underline();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TextStack()
|
||||
public void Textcolumn()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
@@ -173,6 +174,8 @@ namespace QuestPDF.Examples
|
||||
.Padding(10)
|
||||
.Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(x => x.Bold());
|
||||
|
||||
text.DefaultTextStyle(TextStyle.Default);
|
||||
text.AlignLeft();
|
||||
text.ParagraphSpacing(10);
|
||||
@@ -182,14 +185,14 @@ namespace QuestPDF.Examples
|
||||
text.EmptyLine();
|
||||
|
||||
text.Span("This text is a normal text, ");
|
||||
text.Span("this is a bold text, ", TextStyle.Default.Bold());
|
||||
text.Span("this is a red and underlined text, ", TextStyle.Default.Color(Colors.Red.Medium).Underline());
|
||||
text.Span("and this is slightly bigger text.", TextStyle.Default.Size(16));
|
||||
text.Span("this is a bold text, ").Bold();
|
||||
text.Span("this is a red and underlined text, ").FontColor(Colors.Red.Medium).Underline();
|
||||
text.Span("and this is slightly bigger text.").FontSize(16);
|
||||
|
||||
text.EmptyLine();
|
||||
|
||||
text.Span("The new text element also supports injecting custom content between words: ");
|
||||
text.Element().PaddingBottom(-10).Height(16).Width(32).Image(Placeholders.Image);
|
||||
text.Element().PaddingBottom(-4).Height(16).Width(32).Image(Placeholders.Image);
|
||||
text.Span(".");
|
||||
|
||||
text.EmptyLine();
|
||||
@@ -201,7 +204,7 @@ namespace QuestPDF.Examples
|
||||
|
||||
text.EmptyLine();
|
||||
|
||||
text.ExternalLocation("Please visit QuestPDF website", "https://www.questpdf.com");
|
||||
text.Hyperlink("Please visit QuestPDF website", "https://www.questpdf.com");
|
||||
|
||||
text.EmptyLine();
|
||||
|
||||
@@ -210,7 +213,7 @@ namespace QuestPDF.Examples
|
||||
|
||||
text.EmptyLine();
|
||||
|
||||
text.Span(Placeholders.Paragraphs(), TextStyle.Default.Italic());
|
||||
text.Span(Placeholders.Paragraphs()).Italic();
|
||||
|
||||
text.Line("This is target text that does not show up. " + Placeholders.Paragraph());
|
||||
});
|
||||
@@ -236,17 +239,17 @@ namespace QuestPDF.Examples
|
||||
.Padding(10)
|
||||
.Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(TextStyle.Default);
|
||||
text.DefaultTextStyle(TextStyle.Default.FontSize(20));
|
||||
text.AlignLeft();
|
||||
text.ParagraphSpacing(10);
|
||||
|
||||
text.Span("This text is a normal text, ");
|
||||
text.Span("this is a bold text, ", TextStyle.Default.Bold());
|
||||
text.Span("this is a red and underlined text, ", TextStyle.Default.Color(Colors.Red.Medium).Underline());
|
||||
text.Span("and this is slightly bigger text.", TextStyle.Default.Size(16));
|
||||
text.Span("this is a bold text, ").Bold();
|
||||
text.Span("this is a red and underlined text, ").FontColor(Colors.Red.Medium).Underline();
|
||||
text.Span("and this is slightly bigger text.").FontSize(16);
|
||||
|
||||
text.Span("The new text element also supports injecting custom content between words: ");
|
||||
text.Element().PaddingBottom(-10).Height(16).Width(32).Image(Placeholders.Image);
|
||||
text.Element().PaddingBottom(-4).Height(16).Width(32).Image(Placeholders.Image);
|
||||
text.Span(".");
|
||||
|
||||
text.EmptyLine();
|
||||
@@ -255,7 +258,7 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
text.Line($"{i}: {Placeholders.Paragraph()}");
|
||||
|
||||
text.ExternalLocation("Please visit QuestPDF website", "https://www.questpdf.com");
|
||||
text.Hyperlink("Please visit QuestPDF website", "https://www.questpdf.com");
|
||||
|
||||
text.Span("This is page number ");
|
||||
text.CurrentPageNumber();
|
||||
@@ -267,5 +270,106 @@ namespace QuestPDF.Examples
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MeasureIssueWhenSpaceAtLineEnd()
|
||||
{
|
||||
// issue 135
|
||||
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.RenderDocument(container =>
|
||||
{
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Margin(50);
|
||||
page.PageColor(Colors.White);
|
||||
|
||||
page.Size(PageSizes.A4);
|
||||
|
||||
page.Content().Text("This is a specially crafted sentence with a specially chosen length for demonstration of the bug that occurs ;;;;;. ").FontSize(11).BackgroundColor(Colors.Red.Lighten3);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EmptyText()
|
||||
{
|
||||
// issue 135
|
||||
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.RenderDocument(container =>
|
||||
{
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Margin(50);
|
||||
page.PageColor(Colors.White);
|
||||
|
||||
page.Size(PageSizes.A4);
|
||||
|
||||
page.Content().Text(" ").FontSize(11).BackgroundColor(Colors.Red.Lighten3);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Whitespaces()
|
||||
{
|
||||
// issue 135
|
||||
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.RenderDocument(container =>
|
||||
{
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Margin(50);
|
||||
page.PageColor(Colors.White);
|
||||
|
||||
page.Size(PageSizes.A4);
|
||||
|
||||
page.Content().Text(" x ").FontSize(11).BackgroundColor(Colors.Red.Lighten3);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DrawingNullTextShouldNotThrowException()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.RenderDocument(container =>
|
||||
{
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Margin(50);
|
||||
page.PageColor(Colors.White);
|
||||
|
||||
page.Size(PageSizes.A4);
|
||||
|
||||
page.Content().Column(column =>
|
||||
{
|
||||
column.Item().Text(null);
|
||||
|
||||
column.Item().Text(text =>
|
||||
{
|
||||
text.Span(null);
|
||||
text.Line(null);
|
||||
text.Hyperlink(null, "http://www.questpdf.com");
|
||||
text.TotalPages().Format(x => null);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using System.Diagnostics;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.Previewer;
|
||||
|
||||
Document
|
||||
.Create(container =>
|
||||
{
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A4);
|
||||
page.Margin(2, Unit.Centimetre);
|
||||
page.PageColor(Colors.White);
|
||||
page.DefaultTextStyle(x => x.FontSize(14));
|
||||
|
||||
page.Header()
|
||||
.Text("Hello PDF!")
|
||||
.SemiBold().FontSize(36).FontColor(Colors.Blue.Medium);
|
||||
|
||||
page.Content()
|
||||
.PaddingVertical(1, Unit.Centimetre)
|
||||
.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.ConstantColumn(30);
|
||||
columns.RelativeColumn();
|
||||
columns.ConstantColumn(75);
|
||||
columns.ConstantColumn(75);
|
||||
columns.ConstantColumn(75);
|
||||
});
|
||||
|
||||
table.Header(header =>
|
||||
{
|
||||
var textStyle = TextStyle.Default.SemiBold();
|
||||
|
||||
header.Cell().Element(DefaultCellStyle).Text("#").Style(textStyle);
|
||||
header.Cell().Element(DefaultCellStyle).Text("Item name").Style(textStyle);
|
||||
header.Cell().Element(DefaultCellStyle).AlignRight().Text("Price").Style(textStyle);
|
||||
header.Cell().Element(DefaultCellStyle).AlignRight().Text("Count").Style(textStyle);
|
||||
header.Cell().Element(DefaultCellStyle).AlignRight().Text("Total").Style(textStyle);
|
||||
|
||||
static IContainer DefaultCellStyle(IContainer container)
|
||||
{
|
||||
return container.PaddingBottom(5).BorderBottom(1).PaddingBottom(5);
|
||||
}
|
||||
});
|
||||
|
||||
foreach(var i in Enumerable.Range(1, 100))
|
||||
{
|
||||
var price = Placeholders.Random.Next(100, 999) / 100f;
|
||||
var count = Placeholders.Random.Next(1, 10);
|
||||
var total = price * count;
|
||||
|
||||
table.Cell().Element(DefaultCellStyle).Text(i);
|
||||
table.Cell().Element(DefaultCellStyle).Text(Placeholders.Label());
|
||||
table.Cell().Element(DefaultCellStyle).AlignRight().Text($"{price:N2} $");
|
||||
table.Cell().Element(DefaultCellStyle).AlignRight().Text(count);
|
||||
table.Cell().Element(DefaultCellStyle).AlignRight().Text($"{total:N2} $");
|
||||
|
||||
static IContainer DefaultCellStyle(IContainer container)
|
||||
{
|
||||
return container.PaddingBottom(5).BorderBottom(1).BorderColor(Colors.Grey.Lighten1).PaddingBottom(5);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
page.Footer()
|
||||
.AlignCenter()
|
||||
.Text(x =>
|
||||
{
|
||||
x.Span("Page ");
|
||||
x.CurrentPageNumber();
|
||||
x.Span(" of ");
|
||||
x.TotalPages();
|
||||
});
|
||||
});
|
||||
})
|
||||
.ShowInPreviewer();
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\QuestPDF.Previewer\QuestPDF.Previewer.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="HotReloadManager.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,125 @@
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Previewer;
|
||||
|
||||
public static class DocumentPreviewerExtensions
|
||||
{
|
||||
public static void ShowInPreviewer(this IDocument document)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(document);
|
||||
|
||||
var builder = WebApplication.CreateBuilder();
|
||||
var app = builder.Build();
|
||||
|
||||
var pdfDocumentCache = GeneratePdf(document);
|
||||
var refreshFlag = false;
|
||||
|
||||
static byte[] GenerateDocumentAboutException(Exception exception)
|
||||
{
|
||||
return Document.Create(document =>
|
||||
{
|
||||
document.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A4);
|
||||
page.Margin(1, Unit.Inch);
|
||||
page.PageColor(Colors.Red.Lighten4);
|
||||
page.DefaultTextStyle(x => x.FontSize(16));
|
||||
|
||||
page.Header()
|
||||
.BorderBottom(2)
|
||||
.BorderColor(Colors.Red.Medium)
|
||||
.PaddingBottom(5)
|
||||
.Text("Ooops! Something went wrong...").FontSize(28).FontColor(Colors.Red.Medium).Bold();
|
||||
|
||||
page.Content().PaddingVertical(20).Column(column =>
|
||||
{
|
||||
var currentException = exception;
|
||||
|
||||
while (currentException != null)
|
||||
{
|
||||
column.Item().Text(exception.GetType().Name).FontSize(20).SemiBold();
|
||||
column.Item().Text(exception.Message).FontSize(14);
|
||||
column.Item().PaddingTop(10).Text(exception.StackTrace).FontSize(10).Light();
|
||||
|
||||
currentException = currentException.InnerException;
|
||||
|
||||
if (currentException != null)
|
||||
column.Item().PaddingVertical(15).LineHorizontal(2).LineColor(Colors.Red.Medium);
|
||||
}
|
||||
});
|
||||
});
|
||||
}).GeneratePdf();
|
||||
}
|
||||
|
||||
static byte[] GeneratePdf(IDocument document)
|
||||
{
|
||||
try
|
||||
{
|
||||
return document.GeneratePdf();
|
||||
}
|
||||
catch(Exception exception)
|
||||
{
|
||||
return GenerateDocumentAboutException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
HotReloadManager.OnApplicationChanged += () =>
|
||||
{
|
||||
pdfDocumentCache = GeneratePdf(document);
|
||||
refreshFlag = true;
|
||||
};
|
||||
|
||||
app.MapGet("/", () =>
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
var resourceName = "QuestPDF.Previewer.index.html";
|
||||
|
||||
using var stream = assembly.GetManifestResourceStream(resourceName);
|
||||
using var reader = new StreamReader(stream);
|
||||
var result = reader.ReadToEnd();
|
||||
return Results.Content(result, "text/html");
|
||||
});
|
||||
|
||||
app.MapGet("/render", () =>
|
||||
{
|
||||
refreshFlag = false;
|
||||
return Results.File(pdfDocumentCache, "application/pdf");
|
||||
});
|
||||
|
||||
app.MapGet("/listen", async () =>
|
||||
{
|
||||
foreach (var i in Enumerable.Range(0, 1000))
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(100));
|
||||
|
||||
if (!refreshFlag)
|
||||
continue;
|
||||
|
||||
return Results.Text("true");
|
||||
}
|
||||
|
||||
return Results.Text("false");
|
||||
});
|
||||
|
||||
app.Lifetime.ApplicationStarted.Register(() =>
|
||||
{
|
||||
var openBrowserProcess = new Process()
|
||||
{
|
||||
StartInfo = new()
|
||||
{
|
||||
UseShellExecute = true,
|
||||
FileName = app.Urls.First()
|
||||
}
|
||||
};
|
||||
|
||||
openBrowserProcess.Start();
|
||||
});
|
||||
|
||||
app.Run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.Reflection.Metadata;
|
||||
|
||||
[assembly: MetadataUpdateHandler(typeof(HotReloadManager))]
|
||||
|
||||
internal static class HotReloadManager
|
||||
{
|
||||
public static event Action? OnApplicationChanged;
|
||||
public static void UpdateApplication(Type[]? _) => OnApplicationChanged?.Invoke();
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\QuestPDF\QuestPDF.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="index.html" />
|
||||
<EmbeddedResource Include="index.html" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Remove="appsettings.Development.json" />
|
||||
<Content Remove="appsettings.json" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,117 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>QuestPDF Previewer</title>
|
||||
|
||||
<style>
|
||||
html {
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
embed {
|
||||
border: none;
|
||||
|
||||
position: absolute;
|
||||
|
||||
left: 0;
|
||||
top: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.alert {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
padding: 8px 16px;
|
||||
color: #FFFA;
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
box-shadow: 0 5px 5px -3px rgba(0,0,0,.2),0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12) !important;
|
||||
}
|
||||
|
||||
#recommended-alert {
|
||||
background-color: #616161;
|
||||
}
|
||||
|
||||
#recommended-alert .close {
|
||||
color: #FFF;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#connection-issue-alert {
|
||||
visibility: hidden;
|
||||
background-color: #B71C1C;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<embed id="previewer" src="/render#toolbar=0&scrollbar=0" />
|
||||
|
||||
<div id="recommended-alert" class="alert">
|
||||
The recommended browser for this previewer is Microsoft Edge. It does preserve document scroll position when refreshing document, giving better overall experience.
|
||||
<a class="close" onclick="closeRecommendation()">Click to close</a>
|
||||
</div>
|
||||
|
||||
<div id="connection-issue-alert" class="alert">
|
||||
Cannot connect to QuestPDF previewer host. Possibly your debugging session has ended. Please close this browser.
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const recommendationAlert = document.getElementById("recommended-alert");
|
||||
const connectionIssueAlert = document.getElementById("connection-issue-alert");
|
||||
|
||||
function closeRecommendation() {
|
||||
recommendationAlert.parentElement.removeChild(recommendationAlert);
|
||||
localStorage.setItem("show-recommendation-alert", 'false');
|
||||
}
|
||||
|
||||
function refreshRecommendation() {
|
||||
const showRecommendation = localStorage.getItem("show-recommendation-alert");
|
||||
|
||||
if (showRecommendation !== 'false')
|
||||
return;
|
||||
|
||||
recommendationAlert.parentElement.removeChild(recommendationAlert);
|
||||
}
|
||||
|
||||
function changeConnectionIssueAlertVisibility(show) {
|
||||
connectionIssueAlert.style.setProperty("visibility", show ? "visible" : "hidden");
|
||||
}
|
||||
|
||||
async function refreshDocumentLoop() {
|
||||
window.addEventListener("change", () => reloading = true);
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
const response = await fetch('/listen');
|
||||
const value = await response.text();
|
||||
|
||||
if (value === "true")
|
||||
window.location.reload();
|
||||
|
||||
changeConnectionIssueAlertVisibility(false);
|
||||
}
|
||||
catch {
|
||||
changeConnectionIssueAlertVisibility(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
refreshRecommendation();
|
||||
refreshDocumentLoop();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using SkiaSharp;
|
||||
|
||||
@@ -24,5 +25,32 @@ namespace QuestPDF.ReportSample
|
||||
Latitude = Helpers.Random.NextDouble() * 180f - 90f
|
||||
};
|
||||
}
|
||||
|
||||
private static readonly ConcurrentDictionary<int, string> RomanNumeralCache = new ConcurrentDictionary<int, string>();
|
||||
|
||||
public static string FormatAsRomanNumeral(this int number)
|
||||
{
|
||||
if (number < 0 || number > 3999)
|
||||
throw new ArgumentOutOfRangeException("Number should be in range from 1 to 3999");
|
||||
|
||||
return RomanNumeralCache.GetOrAdd(number, x =>
|
||||
{
|
||||
if (x >= 1000) return "M" + FormatAsRomanNumeral(x - 1000);
|
||||
if (x >= 900) return "CM" + FormatAsRomanNumeral(x - 900);
|
||||
if (x >= 500) return "D" + FormatAsRomanNumeral(x - 500);
|
||||
if (x >= 400) return "CD" + FormatAsRomanNumeral(x - 400);
|
||||
if (x >= 100) return "C" + FormatAsRomanNumeral(x - 100);
|
||||
if (x >= 90) return "XC" + FormatAsRomanNumeral(x - 90);
|
||||
if (x >= 50) return "L" + FormatAsRomanNumeral(x - 50);
|
||||
if (x >= 40) return "XL" + FormatAsRomanNumeral(x - 40);
|
||||
if (x >= 10) return "X" + FormatAsRomanNumeral(x - 10);
|
||||
if (x >= 9) return "IX" + FormatAsRomanNumeral(x - 9);
|
||||
if (x >= 5) return "V" + FormatAsRomanNumeral(x - 5);
|
||||
if (x >= 4) return "IV" + FormatAsRomanNumeral(x - 4);
|
||||
if (x >= 1) return "I" + FormatAsRomanNumeral(x - 1);
|
||||
|
||||
return string.Empty;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,58 +26,58 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
|
||||
private void ComposeHeader(IContainer container)
|
||||
{
|
||||
container.Background(Colors.Grey.Lighten3).Border(1).Stack(stack =>
|
||||
container.Background(Colors.Grey.Lighten3).Border(1).Column(column =>
|
||||
{
|
||||
stack.Item().ShowOnce().Padding(5).AlignMiddle().Row(row =>
|
||||
column.Item().ShowOnce().Padding(5).AlignMiddle().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(2).AlignMiddle().Text("PRIMARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
|
||||
row.RelativeColumn(1).AlignRight().MinimalBox().AlignMiddle().Background(Colors.Blue.Darken2).Padding(30);
|
||||
row.RelativeItem(2).AlignMiddle().Text("PRIMARY HEADER").FontColor(Colors.Grey.Darken3).FontSize(30).Bold();
|
||||
row.RelativeItem(1).AlignRight().MinimalBox().AlignMiddle().Background(Colors.Blue.Darken2).Padding(30);
|
||||
});
|
||||
stack.Item().SkipOnce().Padding(5).Row(row =>
|
||||
column.Item().SkipOnce().Padding(5).Row(row =>
|
||||
{
|
||||
row.RelativeColumn(2).Text("SECONDARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
|
||||
row.RelativeColumn(1).AlignRight().MinimalBox().Background(Colors.Blue.Lighten4).Padding(15);
|
||||
row.RelativeItem(2).Text("SECONDARY HEADER").FontColor(Colors.Grey.Darken3).FontSize(30).Bold();
|
||||
row.RelativeItem(1).AlignRight().MinimalBox().Background(Colors.Blue.Lighten4).Padding(15);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void ComposeContent(IContainer container)
|
||||
{
|
||||
container.Stack(stack =>
|
||||
container.Column(column =>
|
||||
{
|
||||
stack.Item().PaddingVertical(80).Text("First");
|
||||
stack.Item().PageBreak();
|
||||
stack.Item().PaddingVertical(80).Text("Second");
|
||||
stack.Item().PageBreak();
|
||||
stack.Item().PaddingVertical(80).Text("Third");
|
||||
stack.Item().PageBreak();
|
||||
column.Item().PaddingVertical(80).Text("First");
|
||||
column.Item().PageBreak();
|
||||
column.Item().PaddingVertical(80).Text("Second");
|
||||
column.Item().PageBreak();
|
||||
column.Item().PaddingVertical(80).Text("Third");
|
||||
column.Item().PageBreak();
|
||||
});
|
||||
}
|
||||
|
||||
private void ComposeFooter(IContainer container)
|
||||
{
|
||||
container.Background(Colors.Grey.Lighten3).Stack(stack =>
|
||||
container.Background(Colors.Grey.Lighten3).Column(column =>
|
||||
{
|
||||
stack.Item().ShowOnce().Background(Colors.Grey.Lighten3).Row(row =>
|
||||
column.Item().ShowOnce().Background(Colors.Grey.Lighten3).Row(row =>
|
||||
{
|
||||
row.RelativeColumn().Text(x =>
|
||||
row.RelativeItem().Text(x =>
|
||||
{
|
||||
x.CurrentPageNumber();
|
||||
x.Span(" / ");
|
||||
x.TotalPages();
|
||||
});
|
||||
row.RelativeColumn().AlignRight().Text("Footer for header");
|
||||
row.RelativeItem().AlignRight().Text("Footer for header");
|
||||
});
|
||||
|
||||
stack.Item().SkipOnce().Background(Colors.Grey.Lighten3).Row(row =>
|
||||
column.Item().SkipOnce().Background(Colors.Grey.Lighten3).Row(row =>
|
||||
{
|
||||
row.RelativeColumn().Text(x =>
|
||||
row.RelativeItem().Text(x =>
|
||||
{
|
||||
x.CurrentPageNumber();
|
||||
x.Span(" / ");
|
||||
x.TotalPages();
|
||||
});
|
||||
row.RelativeColumn().AlignRight().Text("Footer for every page except header");
|
||||
row.RelativeItem().AlignRight().Text("Footer for every page except header");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
{
|
||||
container
|
||||
.ShowEntire()
|
||||
.Stack(stack =>
|
||||
.Column(column =>
|
||||
{
|
||||
stack.Spacing(5);
|
||||
stack.Item().Element(PhotoWithMaps);
|
||||
stack.Item().Element(PhotoDetails);
|
||||
column.Spacing(5);
|
||||
column.Item().Element(PhotoWithMaps);
|
||||
column.Item().Element(PhotoDetails);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -30,14 +30,14 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
container
|
||||
.Row(row =>
|
||||
{
|
||||
row.RelativeColumn(2).AspectRatio(4 / 3f).Component<ImagePlaceholder>();
|
||||
row.RelativeItem(2).AspectRatio(4 / 3f).Component<ImagePlaceholder>();
|
||||
|
||||
row.RelativeColumn().PaddingLeft(5).Stack(stack =>
|
||||
row.RelativeItem().PaddingLeft(5).Column(column =>
|
||||
{
|
||||
stack.Spacing(7f);
|
||||
column.Spacing(7f);
|
||||
|
||||
stack.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>();
|
||||
stack.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>();
|
||||
column.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>();
|
||||
column.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -21,25 +21,26 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
decoration
|
||||
.Header()
|
||||
.Before()
|
||||
.PaddingBottom(5)
|
||||
.Text(Model.Title, Typography.Headline);
|
||||
.Text(Model.Title)
|
||||
.Style(Typography.Headline);
|
||||
|
||||
decoration.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Stack(stack =>
|
||||
decoration.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Column(column =>
|
||||
{
|
||||
foreach (var part in Model.Parts)
|
||||
{
|
||||
stack.Item().EnsureSpace(25).Row(row =>
|
||||
column.Item().EnsureSpace(25).Row(row =>
|
||||
{
|
||||
row.ConstantColumn(150).LabelCell().Text(part.Label);
|
||||
var frame = row.RelativeColumn().ValueCell();
|
||||
row.ConstantItem(150).LabelCell().Text(part.Label);
|
||||
var frame = row.RelativeItem().ValueCell();
|
||||
|
||||
if (part is ReportSectionText text)
|
||||
frame.ShowEntire().Text(text.Text);
|
||||
|
||||
|
||||
if (part is ReportSectionMap map)
|
||||
frame.Element(x => MapElement(x, map));
|
||||
|
||||
|
||||
if (part is ReportSectionPhotos photos)
|
||||
frame.Element(x => PhotosElement(x, photos));
|
||||
});
|
||||
@@ -56,12 +57,12 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
return;
|
||||
}
|
||||
|
||||
container.ShowEntire().Stack(stack =>
|
||||
container.ShowEntire().Column(column =>
|
||||
{
|
||||
stack.Spacing(5);
|
||||
column.Spacing(5);
|
||||
|
||||
stack.Item().MaxWidth(250).AspectRatio(4 / 3f).Component<ImagePlaceholder>();
|
||||
stack.Item().Text(model.Location.Format());
|
||||
column.Item().MaxWidth(250).AspectRatio(4 / 3f).Component<ImagePlaceholder>();
|
||||
column.Item().Text(model.Location.Format());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -69,7 +70,7 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
{
|
||||
if (model.PhotoCount == 0)
|
||||
{
|
||||
container.Text("No photos", Typography.Normal);
|
||||
container.Text("No photos").Style(Typography.Normal);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,28 +39,28 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
|
||||
page.Footer().AlignCenter().Text(text =>
|
||||
{
|
||||
text.CurrentPageNumber();
|
||||
text.CurrentPageNumber().Format(x => x?.FormatAsRomanNumeral() ?? "-----");
|
||||
text.Span(" / ");
|
||||
text.TotalPages();
|
||||
text.TotalPages().Format(x => x?.FormatAsRomanNumeral() ?? "-----");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void ComposeHeader(IContainer container)
|
||||
{
|
||||
container.Stack(stack =>
|
||||
container.Column(column =>
|
||||
{
|
||||
stack.Item().Row(row =>
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
row.Spacing(50);
|
||||
|
||||
row.RelativeColumn().PaddingTop(-10).Text(Model.Title, Typography.Title);
|
||||
row.ConstantColumn(90).ExternalLink("https://www.questpdf.com").MaxHeight(30).Component<ImagePlaceholder>();
|
||||
row.RelativeItem().PaddingTop(-10).Text(Model.Title).Style(Typography.Title);
|
||||
row.ConstantItem(90).ExternalLink("https://www.questpdf.com").MaxHeight(30).Component<ImagePlaceholder>();
|
||||
});
|
||||
|
||||
stack.Item().ShowOnce().PaddingVertical(15).Border(1f).BorderColor(Colors.Grey.Lighten1).ExtendHorizontal();
|
||||
column.Item().ShowOnce().PaddingVertical(15).Border(1f).BorderColor(Colors.Grey.Lighten1).ExtendHorizontal();
|
||||
|
||||
stack.Item().ShowOnce().Grid(grid =>
|
||||
column.Item().ShowOnce().Grid(grid =>
|
||||
{
|
||||
grid.Columns(2);
|
||||
grid.Spacing(5);
|
||||
@@ -69,7 +69,7 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
{
|
||||
grid.Item().Text(text =>
|
||||
{
|
||||
text.Span($"{field.Label}: ", TextStyle.Default.SemiBold());
|
||||
text.Span($"{field.Label}: ").SemiBold();
|
||||
text.Span(field.Value);
|
||||
});
|
||||
}
|
||||
@@ -79,22 +79,22 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
|
||||
void ComposeContent(IContainer container)
|
||||
{
|
||||
container.PaddingVertical(20).Stack(stack =>
|
||||
container.PaddingVertical(20).Column(column =>
|
||||
{
|
||||
stack.Spacing(20);
|
||||
|
||||
stack.Item().Component(new TableOfContentsTemplate(Model.Sections));
|
||||
column.Spacing(20);
|
||||
|
||||
stack.Item().PageBreak();
|
||||
column.Item().Component(new TableOfContentsTemplate(Model.Sections));
|
||||
|
||||
column.Item().PageBreak();
|
||||
|
||||
foreach (var section in Model.Sections)
|
||||
stack.Item().Location(section.Title).Component(new SectionTemplate(section));
|
||||
|
||||
stack.Item().PageBreak();
|
||||
stack.Item().Location("Photos");
|
||||
column.Item().Location(section.Title).Component(new SectionTemplate(section));
|
||||
|
||||
column.Item().PageBreak();
|
||||
column.Item().Location("Photos");
|
||||
|
||||
foreach (var photo in Model.Photos)
|
||||
stack.Item().Component(new PhotoTemplate(photo));
|
||||
column.Item().Component(new PhotoTemplate(photo));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.ReportSample.Layouts
|
||||
@@ -19,18 +21,19 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
decoration
|
||||
.Header()
|
||||
.Before()
|
||||
.PaddingBottom(5)
|
||||
.Text("Table of contents", Typography.Headline);
|
||||
.Text("Table of contents")
|
||||
.Style(Typography.Headline);
|
||||
|
||||
decoration.Content().Stack(stack =>
|
||||
decoration.Content().Column(column =>
|
||||
{
|
||||
stack.Spacing(5);
|
||||
column.Spacing(5);
|
||||
|
||||
for (var i = 0; i < Sections.Count; i++)
|
||||
stack.Item().Element(c => DrawLink(c, i+1, Sections[i].Title));
|
||||
column.Item().Element(c => DrawLink(c, i+1, Sections[i].Title));
|
||||
|
||||
stack.Item().Element(c => DrawLink(c, Sections.Count+1, "Photos"));
|
||||
column.Item().Element(c => DrawLink(c, Sections.Count+1, "Photos"));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -41,9 +44,20 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
.InternalLink(locationName)
|
||||
.Row(row =>
|
||||
{
|
||||
row.ConstantColumn(25).Text($"{number}.");
|
||||
row.RelativeColumn().Text(locationName);
|
||||
row.ConstantColumn(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName));
|
||||
row.ConstantItem(25).Text($"{number}.");
|
||||
row.RelativeItem().Text(locationName);
|
||||
row.ConstantItem(150).AlignRight().Text(text =>
|
||||
{
|
||||
text.BeginPageNumberOfSection(locationName);
|
||||
text.Span(" - ");
|
||||
text.EndPageNumberOfSection(locationName);
|
||||
|
||||
var lengthStyle = TextStyle.Default.Color(Colors.Grey.Medium);
|
||||
|
||||
text.Span(" (").Style(lengthStyle);
|
||||
text.TotalPagesWithinSection(locationName).Style(lengthStyle).Format(x => x == 1 ? "1 page long" : $"{x} pages long");
|
||||
text.Span(")").Style(lengthStyle);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace QuestPDF.ReportSample
|
||||
[Test]
|
||||
public void GenerateAndShowPdf()
|
||||
{
|
||||
ImagePlaceholder.Solid = true;
|
||||
//ImagePlaceholder.Solid = true;
|
||||
|
||||
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.pdf");
|
||||
Report.GeneratePdf(path);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using NUnit.Framework;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Fluent;
|
||||
@@ -8,19 +9,40 @@ using QuestPDF.UnitTests.TestEngine;
|
||||
namespace QuestPDF.UnitTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class StackTests
|
||||
public class ColumnTests
|
||||
{
|
||||
private Column CreateColumnWithTwoItems(TestPlan testPlan)
|
||||
{
|
||||
return new Column
|
||||
{
|
||||
Items =
|
||||
{
|
||||
new ColumnItem
|
||||
{
|
||||
Child = testPlan.CreateChild("first")
|
||||
},
|
||||
new ColumnItem
|
||||
{
|
||||
Child = testPlan.CreateChild("second")
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Column CreateColumnWithTwoItemsWhereFirstIsFullyRendered(TestPlan testPlan)
|
||||
{
|
||||
var column = CreateColumnWithTwoItems(testPlan);
|
||||
column.Items.First().IsRendered = true;
|
||||
return column;
|
||||
}
|
||||
|
||||
#region Measure
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsWrap_WhenFirstChildWraps()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new BinaryStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
})
|
||||
.For(CreateColumnWithTwoItems)
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.Wrap())
|
||||
.CheckMeasureResult(SpacePlan.Wrap());
|
||||
@@ -30,11 +52,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Measure_ReturnsPartialRender_WhenFirstChildReturnsPartialRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new BinaryStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
})
|
||||
.For(CreateColumnWithTwoItems)
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.PartialRender(300, 200))
|
||||
.CheckMeasureResult(SpacePlan.PartialRender(300, 200));
|
||||
@@ -44,11 +62,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Measure_ReturnsPartialRender_WhenSecondChildWraps()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new BinaryStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
})
|
||||
.For(CreateColumnWithTwoItems)
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
|
||||
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.Wrap())
|
||||
@@ -59,11 +73,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Measure_ReturnsPartialRender_WhenSecondChildReturnsPartialRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new BinaryStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
})
|
||||
.For(CreateColumnWithTwoItems)
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
|
||||
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.PartialRender(300, 150))
|
||||
@@ -74,33 +84,13 @@ namespace QuestPDF.UnitTests
|
||||
public void Measure_ReturnsFullRender_WhenSecondChildReturnsFullRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new BinaryStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
})
|
||||
.For(CreateColumnWithTwoItems)
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
|
||||
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.FullRender(100, 50))
|
||||
.CheckMeasureResult(SpacePlan.FullRender(200, 150));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_UsesEmpty_WhenFirstChildIsRendered()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new BinaryStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second"),
|
||||
|
||||
IsFirstRendered = true
|
||||
})
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("second", new Size(400, 300), SpacePlan.FullRender(200, 300))
|
||||
.CheckMeasureResult(SpacePlan.FullRender(200, 300));
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Draw
|
||||
@@ -109,11 +99,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Draw_WhenFirstChildWraps()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new BinaryStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
})
|
||||
.For(CreateColumnWithTwoItems)
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.Wrap())
|
||||
.CheckDrawResult();
|
||||
@@ -123,14 +109,12 @@ namespace QuestPDF.UnitTests
|
||||
public void Draw_WhenFirstChildPartiallyRenders()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new BinaryStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
})
|
||||
.For(CreateColumnWithTwoItems)
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.PartialRender(200, 100))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw("first", new Size(400, 100))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
@@ -138,15 +122,13 @@ namespace QuestPDF.UnitTests
|
||||
public void Draw_WhenFirstChildFullyRenders_AndSecondChildWraps()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new BinaryStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
})
|
||||
.For(CreateColumnWithTwoItems)
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
|
||||
.ExpectChildDraw("first", new Size(400, 100))
|
||||
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.Wrap())
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw("first", new Size(400, 100))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
@@ -154,15 +136,13 @@ namespace QuestPDF.UnitTests
|
||||
public void Draw_WhenFirstChildFullyRenders_AndSecondChildPartiallyRenders()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new BinaryStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
})
|
||||
.For(CreateColumnWithTwoItems)
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
|
||||
.ExpectChildDraw("first", new Size(400, 100))
|
||||
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.PartialRender(250, 150))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw("first", new Size(400, 100))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectCanvasTranslate(0, 100)
|
||||
.ExpectChildDraw("second", new Size(400, 150))
|
||||
.ExpectCanvasTranslate(0, -100)
|
||||
@@ -173,15 +153,13 @@ namespace QuestPDF.UnitTests
|
||||
public void Draw_WhenFirstChildFullyRenders_AndSecondChildFullyRenders()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new BinaryStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
})
|
||||
.For(CreateColumnWithTwoItems)
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
|
||||
.ExpectChildDraw("first", new Size(400, 100))
|
||||
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.FullRender(250, 150))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw("first", new Size(400, 100))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectCanvasTranslate(0, 100)
|
||||
.ExpectChildDraw("second", new Size(400, 150))
|
||||
.ExpectCanvasTranslate(0, -100)
|
||||
@@ -192,19 +170,13 @@ namespace QuestPDF.UnitTests
|
||||
public void Draw_UsesEmpty_WhenFirstChildIsRendered()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new BinaryStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second"),
|
||||
|
||||
IsFirstRendered = true
|
||||
})
|
||||
.For(CreateColumnWithTwoItemsWhereFirstIsFullyRendered)
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("second", new Size(400, 300), SpacePlan.PartialRender(200, 300))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw("second", new Size(400, 300))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.CheckState<BinaryStack>(x => x.IsFirstRendered)
|
||||
.CheckState<Column>(x => x.Items.First().IsRendered)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
@@ -212,97 +184,14 @@ namespace QuestPDF.UnitTests
|
||||
public void Draw_TogglesFirstRenderedFlag_WhenSecondFullyRenders()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new BinaryStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second"),
|
||||
|
||||
IsFirstRendered = true
|
||||
})
|
||||
.For(CreateColumnWithTwoItemsWhereFirstIsFullyRendered)
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("second", new Size(400, 300), SpacePlan.FullRender(200, 300))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw("second", new Size(400, 300))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.CheckDrawResult()
|
||||
.CheckState<BinaryStack>(x => !x.IsFirstRendered);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Structure
|
||||
|
||||
[Test]
|
||||
public void Structure_Simple()
|
||||
{
|
||||
// arrange
|
||||
var childA = TestPlan.CreateUniqueElement();
|
||||
var childB = TestPlan.CreateUniqueElement();
|
||||
var childC = TestPlan.CreateUniqueElement();
|
||||
var childD = TestPlan.CreateUniqueElement();
|
||||
var childE = TestPlan.CreateUniqueElement();
|
||||
|
||||
const int spacing = 20;
|
||||
|
||||
// act
|
||||
var structure = new Container();
|
||||
|
||||
structure.Stack(stack =>
|
||||
{
|
||||
stack.Spacing(spacing);
|
||||
|
||||
stack.Item().Element(childA);
|
||||
stack.Item().Element(childB);
|
||||
stack.Item().Element(childC);
|
||||
stack.Item().Element(childD);
|
||||
stack.Item().Element(childE);
|
||||
});
|
||||
|
||||
// assert
|
||||
var expected = new Padding
|
||||
{
|
||||
Bottom = -spacing,
|
||||
|
||||
Child = new BinaryStack
|
||||
{
|
||||
First = new BinaryStack
|
||||
{
|
||||
First = new Padding
|
||||
{
|
||||
Bottom = spacing,
|
||||
Child = childA
|
||||
},
|
||||
Second = new Padding
|
||||
{
|
||||
Bottom = spacing,
|
||||
Child = childB
|
||||
}
|
||||
},
|
||||
Second = new BinaryStack
|
||||
{
|
||||
First = new Padding
|
||||
{
|
||||
Bottom = spacing,
|
||||
Child = childC
|
||||
},
|
||||
Second = new BinaryStack
|
||||
{
|
||||
First = new Padding
|
||||
{
|
||||
Bottom = spacing,
|
||||
Child = childD
|
||||
},
|
||||
Second = new Padding
|
||||
{
|
||||
Bottom = spacing,
|
||||
Child = childE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TestPlan.CompareOperations(structure, expected);
|
||||
.CheckState<Column>(x => !x.Items.First().IsRendered);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -9,35 +9,27 @@ namespace QuestPDF.UnitTests
|
||||
[TestFixture]
|
||||
public class DecorationTests
|
||||
{
|
||||
private Decoration CreateDecoration(TestPlan testPlan)
|
||||
{
|
||||
return new Decoration
|
||||
{
|
||||
Before = testPlan.CreateChild("before"),
|
||||
Content = testPlan.CreateChild("content"),
|
||||
After = testPlan.CreateChild("after"),
|
||||
};
|
||||
}
|
||||
|
||||
#region Measure
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsWrap_WhenDecorationReturnsWrap()
|
||||
public void Measure_ReturnsWrap_WhenBeforeReturnsWrap()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new BinaryDecoration
|
||||
{
|
||||
Type = DecorationType.Append,
|
||||
DecorationElement = x.CreateChild("decoration"),
|
||||
ContentElement = x.CreateChild("content")
|
||||
})
|
||||
.For(CreateDecoration)
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.Wrap())
|
||||
.CheckMeasureResult(SpacePlan.Wrap());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsWrap_WhenDecorationReturnsPartialRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new BinaryDecoration
|
||||
{
|
||||
Type = DecorationType.Append,
|
||||
DecorationElement = x.CreateChild("decoration"),
|
||||
ContentElement = x.CreateChild("content")
|
||||
})
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.PartialRender(300, 200))
|
||||
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.Wrap())
|
||||
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50))
|
||||
.ExpectChildMeasure("content", new Size(400, 250), SpacePlan.FullRender(100, 100))
|
||||
.CheckMeasureResult(SpacePlan.Wrap());
|
||||
}
|
||||
|
||||
@@ -45,89 +37,100 @@ namespace QuestPDF.UnitTests
|
||||
public void Measure_ReturnsWrap_WhenContentReturnsWrap()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new BinaryDecoration
|
||||
{
|
||||
Type = DecorationType.Append,
|
||||
DecorationElement = x.CreateChild("decoration"),
|
||||
ContentElement = x.CreateChild("content")
|
||||
})
|
||||
.For(CreateDecoration)
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100))
|
||||
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(100, 50))
|
||||
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50))
|
||||
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.Wrap())
|
||||
.CheckMeasureResult(SpacePlan.Wrap());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsPartialRender_WhenContentReturnsPartialRender()
|
||||
public void Measure_ReturnsWrap_WhenAfterReturnsWrap()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new BinaryDecoration
|
||||
{
|
||||
Type = DecorationType.Append,
|
||||
DecorationElement = x.CreateChild("decoration"),
|
||||
ContentElement = x.CreateChild("content")
|
||||
})
|
||||
.For(CreateDecoration)
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100))
|
||||
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.PartialRender(200, 150))
|
||||
.CheckMeasureResult(SpacePlan.PartialRender(400, 250));
|
||||
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(100, 50))
|
||||
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.Wrap())
|
||||
.ExpectChildMeasure("content", new Size(400, 250), SpacePlan.FullRender(100, 100))
|
||||
.CheckMeasureResult(SpacePlan.Wrap());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsFullRender_WhenContentReturnsFullRender()
|
||||
public void Measure_ReturnsWrap_WhenBeforeReturnsPartialRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new BinaryDecoration
|
||||
{
|
||||
Type = DecorationType.Append,
|
||||
DecorationElement = x.CreateChild("decoration"),
|
||||
ContentElement = x.CreateChild("content")
|
||||
})
|
||||
.For(CreateDecoration)
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100))
|
||||
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.FullRender(200, 150))
|
||||
.CheckMeasureResult(SpacePlan.FullRender(400, 250));
|
||||
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.PartialRender(100, 50))
|
||||
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50))
|
||||
.ExpectChildMeasure("content", new Size(400, 250), SpacePlan.FullRender(100, 100))
|
||||
.CheckMeasureResult(SpacePlan.Wrap());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsWrap_WhenAfterReturnsPartialRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(CreateDecoration)
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(100, 50))
|
||||
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.PartialRender(100, 50))
|
||||
.ExpectChildMeasure("content", new Size(400, 250), SpacePlan.FullRender(100, 100))
|
||||
.CheckMeasureResult(SpacePlan.Wrap());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsWrap_WhenContentReturnsPartialRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(CreateDecoration)
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(100, 50))
|
||||
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50))
|
||||
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.PartialRender(150, 100))
|
||||
.CheckMeasureResult(SpacePlan.PartialRender(150, 200));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsWrap_WhenContentReturnsFullRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(CreateDecoration)
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(100, 50))
|
||||
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50))
|
||||
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.FullRender(150, 100))
|
||||
.CheckMeasureResult(SpacePlan.FullRender(150, 200));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Draw
|
||||
|
||||
[Test]
|
||||
public void Draw_Prepend()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new BinaryDecoration
|
||||
{
|
||||
Type = DecorationType.Prepend,
|
||||
DecorationElement = x.CreateChild("decoration"),
|
||||
ContentElement = x.CreateChild("content")
|
||||
})
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100))
|
||||
.ExpectChildDraw("decoration", new Size(400, 100))
|
||||
.ExpectCanvasTranslate(0, 100)
|
||||
.ExpectChildDraw("content", new Size(400, 200))
|
||||
.ExpectCanvasTranslate(0, -100)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Draw_Append()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new BinaryDecoration
|
||||
{
|
||||
Type = DecorationType.Append,
|
||||
DecorationElement = x.CreateChild("decoration"),
|
||||
ContentElement = x.CreateChild("content")
|
||||
})
|
||||
.For(CreateDecoration)
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100))
|
||||
.ExpectChildDraw("content", new Size(400, 200))
|
||||
.ExpectCanvasTranslate(0, 200)
|
||||
.ExpectChildDraw("decoration", new Size(400, 100))
|
||||
.ExpectCanvasTranslate(0, -200)
|
||||
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(200, 40))
|
||||
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(200, 60))
|
||||
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.FullRender(300, 100))
|
||||
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw("before", new Size(300, 40))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
|
||||
.ExpectCanvasTranslate(0, 40)
|
||||
.ExpectChildDraw("content", new Size(300, 100))
|
||||
.ExpectCanvasTranslate(0, -40)
|
||||
|
||||
.ExpectCanvasTranslate(0, 140)
|
||||
.ExpectChildDraw("after", new Size(300, 60))
|
||||
.ExpectCanvasTranslate(0, -140)
|
||||
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace QuestPDF.UnitTests
|
||||
public class ExternalLinkTests
|
||||
{
|
||||
[Test]
|
||||
public void Measure() => SimpleContainerTests.Measure<ExternalLink>();
|
||||
public void Measure() => SimpleContainerTests.Measure<Hyperlink>();
|
||||
|
||||
// TODO: consider tests for the Draw method
|
||||
}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing;
|
||||
using SkiaSharp;
|
||||
using static SkiaSharp.SKFontStyleSlant;
|
||||
|
||||
namespace QuestPDF.UnitTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class FontStyleSetTests
|
||||
{
|
||||
private void ExpectComparisonOrder(SKFontStyle target, SKFontStyle[] styles)
|
||||
{
|
||||
for (var i = 0; i < styles.Length - 1; i++)
|
||||
{
|
||||
var currentStyle = styles[i];
|
||||
var nextStyle = styles[i + 1];
|
||||
|
||||
FontStyleSet.IsBetterMatch(target, currentStyle, nextStyle).Should().BeTrue();
|
||||
FontStyleSet.IsBetterMatch(target, nextStyle, currentStyle).Should().BeFalse();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FontStyleSet_IsBetterMatch_CondensedWidth()
|
||||
{
|
||||
var styles = new[]
|
||||
{
|
||||
new SKFontStyle(500, 5, Upright),
|
||||
new SKFontStyle(500, 4, Upright),
|
||||
new SKFontStyle(500, 3, Upright),
|
||||
new SKFontStyle(500, 6, Upright)
|
||||
};
|
||||
|
||||
ExpectComparisonOrder(new SKFontStyle(500, 5, Upright), styles);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FontStyleSet_IsBetterMatch_ExpandedWidth()
|
||||
{
|
||||
var styles = new[]
|
||||
{
|
||||
new SKFontStyle(500, 6, Upright),
|
||||
new SKFontStyle(500, 7, Upright),
|
||||
new SKFontStyle(500, 8, Upright),
|
||||
new SKFontStyle(500, 5, Upright)
|
||||
};
|
||||
|
||||
ExpectComparisonOrder(new SKFontStyle(500, 6, Upright), styles);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FontStyleSet_IsBetterMatch_ItalicSlant()
|
||||
{
|
||||
var styles = new[]
|
||||
{
|
||||
new SKFontStyle(500, 5, Italic),
|
||||
new SKFontStyle(500, 5, Oblique),
|
||||
new SKFontStyle(500, 5, Upright)
|
||||
};
|
||||
|
||||
ExpectComparisonOrder(new SKFontStyle(500, 5, Italic), styles);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FontStyleSet_IsBetterMatch_ObliqueSlant()
|
||||
{
|
||||
var styles = new[]
|
||||
{
|
||||
new SKFontStyle(500, 5, Oblique),
|
||||
new SKFontStyle(500, 5, Italic),
|
||||
new SKFontStyle(500, 5, Upright)
|
||||
};
|
||||
|
||||
ExpectComparisonOrder(new SKFontStyle(500, 5, Oblique), styles);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FontStyleSet_IsBetterMatch_UprightSlant()
|
||||
{
|
||||
var styles = new[]
|
||||
{
|
||||
new SKFontStyle(500, 5, Upright),
|
||||
new SKFontStyle(500, 5, Oblique),
|
||||
new SKFontStyle(500, 5, Italic)
|
||||
};
|
||||
|
||||
ExpectComparisonOrder(new SKFontStyle(500, 5, Upright), styles);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FontStyleSet_IsBetterMatch_ThinWeight()
|
||||
{
|
||||
var styles = new[]
|
||||
{
|
||||
new SKFontStyle(300, 5, Upright),
|
||||
new SKFontStyle(200, 5, Upright),
|
||||
new SKFontStyle(100, 5, Upright),
|
||||
new SKFontStyle(400, 5, Upright)
|
||||
};
|
||||
|
||||
ExpectComparisonOrder(new SKFontStyle(300, 5, Upright), styles);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FontStyleSet_IsBetterMatch_RegularWeight()
|
||||
{
|
||||
var styles = new[]
|
||||
{
|
||||
new SKFontStyle(500, 5, Upright),
|
||||
new SKFontStyle(300, 5, Upright),
|
||||
new SKFontStyle(100, 5, Upright),
|
||||
new SKFontStyle(600, 5, Upright)
|
||||
};
|
||||
|
||||
ExpectComparisonOrder(new SKFontStyle(400, 5, Upright), styles);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FontStyleSet_IsBetterMatch_BoldWeight()
|
||||
{
|
||||
var styles = new[]
|
||||
{
|
||||
new SKFontStyle(600, 5, Upright),
|
||||
new SKFontStyle(700, 5, Upright),
|
||||
new SKFontStyle(800, 5, Upright),
|
||||
new SKFontStyle(500, 5, Upright)
|
||||
};
|
||||
|
||||
ExpectComparisonOrder(new SKFontStyle(600, 5, Upright), styles);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FontStyleSet_RespectsPriority()
|
||||
{
|
||||
var styles = new[]
|
||||
{
|
||||
new SKFontStyle(600, 5, Italic),
|
||||
new SKFontStyle(600, 6, Upright),
|
||||
new SKFontStyle(500, 6, Italic)
|
||||
};
|
||||
|
||||
ExpectComparisonOrder(new SKFontStyle(500, 5, Upright), styles);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,26 +39,26 @@ namespace QuestPDF.UnitTests
|
||||
// assert
|
||||
var expected = new Container();
|
||||
|
||||
expected.Stack(stack =>
|
||||
expected.Column(column =>
|
||||
{
|
||||
stack.Item().Row(row =>
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(6).Element(childA);
|
||||
row.RelativeColumn(4).Element(childB);
|
||||
row.RelativeColumn(2);
|
||||
row.RelativeItem(6).Element(childA);
|
||||
row.RelativeItem(4).Element(childB);
|
||||
row.RelativeItem(2);
|
||||
});
|
||||
|
||||
stack.Item().Row(row =>
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(4).Element(childC);
|
||||
row.RelativeColumn(2).Element(childD);
|
||||
row.RelativeColumn(6);
|
||||
row.RelativeItem(4).Element(childC);
|
||||
row.RelativeItem(2).Element(childD);
|
||||
row.RelativeItem(6);
|
||||
});
|
||||
|
||||
stack.Item().Row(row =>
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(8).Element(childE);
|
||||
row.RelativeColumn(4);
|
||||
row.RelativeItem(8).Element(childE);
|
||||
row.RelativeItem(4);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -93,29 +93,29 @@ namespace QuestPDF.UnitTests
|
||||
// assert
|
||||
var expected = new Container();
|
||||
|
||||
expected.Stack(stack =>
|
||||
expected.Column(column =>
|
||||
{
|
||||
stack.Item().Row(row =>
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(1);
|
||||
row.RelativeColumn(6).Element(childA);
|
||||
row.RelativeColumn(4).Element(childB);
|
||||
row.RelativeColumn(1);
|
||||
row.RelativeItem(1);
|
||||
row.RelativeItem(6).Element(childA);
|
||||
row.RelativeItem(4).Element(childB);
|
||||
row.RelativeItem(1);
|
||||
});
|
||||
|
||||
stack.Item().Row(row =>
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(3);
|
||||
row.RelativeColumn(4).Element(childC);
|
||||
row.RelativeColumn(2).Element(childD);
|
||||
row.RelativeColumn(3);
|
||||
row.RelativeItem(3);
|
||||
row.RelativeItem(4).Element(childC);
|
||||
row.RelativeItem(2).Element(childD);
|
||||
row.RelativeItem(3);
|
||||
});
|
||||
|
||||
stack.Item().Row(row =>
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(2);
|
||||
row.RelativeColumn(8).Element(childE);
|
||||
row.RelativeColumn(2);
|
||||
row.RelativeItem(2);
|
||||
row.RelativeItem(8).Element(childE);
|
||||
row.RelativeItem(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -150,26 +150,26 @@ namespace QuestPDF.UnitTests
|
||||
// assert
|
||||
var expected = new Container();
|
||||
|
||||
expected.Stack(stack =>
|
||||
expected.Column(column =>
|
||||
{
|
||||
stack.Item().Row(row =>
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(2);
|
||||
row.RelativeColumn(6).Element(childA);
|
||||
row.RelativeColumn(4).Element(childB);
|
||||
row.RelativeItem(2);
|
||||
row.RelativeItem(6).Element(childA);
|
||||
row.RelativeItem(4).Element(childB);
|
||||
});
|
||||
|
||||
stack.Item().Row(row =>
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(6);
|
||||
row.RelativeColumn(4).Element(childC);
|
||||
row.RelativeColumn(2).Element(childD);
|
||||
row.RelativeItem(6);
|
||||
row.RelativeItem(4).Element(childC);
|
||||
row.RelativeItem(2).Element(childD);
|
||||
});
|
||||
|
||||
stack.Item().Row(row =>
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(4);
|
||||
row.RelativeColumn(8).Element(childE);
|
||||
row.RelativeItem(4);
|
||||
row.RelativeItem(8).Element(childE);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -210,36 +210,36 @@ namespace QuestPDF.UnitTests
|
||||
// assert
|
||||
var expected = new Container();
|
||||
|
||||
expected.Stack(stack =>
|
||||
expected.Column(column =>
|
||||
{
|
||||
stack.Spacing(20);
|
||||
column.Spacing(20);
|
||||
|
||||
stack.Item().Row(row =>
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
row.Spacing(30);
|
||||
|
||||
row.RelativeColumn(3);
|
||||
row.RelativeColumn(5).Element(childA);
|
||||
row.RelativeColumn(5).Element(childB);
|
||||
row.RelativeColumn(3);
|
||||
row.RelativeItem(3);
|
||||
row.RelativeItem(5).Element(childA);
|
||||
row.RelativeItem(5).Element(childB);
|
||||
row.RelativeItem(3);
|
||||
});
|
||||
|
||||
stack.Item().Row(row =>
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
row.Spacing(30);
|
||||
|
||||
row.RelativeColumn(3);
|
||||
row.RelativeColumn(10).Element(childC);
|
||||
row.RelativeColumn(3);
|
||||
row.RelativeItem(3);
|
||||
row.RelativeItem(10).Element(childC);
|
||||
row.RelativeItem(3);
|
||||
});
|
||||
|
||||
stack.Item().Row(row =>
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
row.Spacing(30);
|
||||
|
||||
row.RelativeColumn(2);
|
||||
row.RelativeColumn(12).Element(childD);
|
||||
row.RelativeColumn(2);
|
||||
row.RelativeItem(2);
|
||||
row.RelativeItem(12).Element(childD);
|
||||
row.RelativeItem(2);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace QuestPDF.UnitTests
|
||||
public class InternalLinkTests
|
||||
{
|
||||
[Test]
|
||||
public void Measure() => SimpleContainerTests.Measure<InternalLink>();
|
||||
public void Measure() => SimpleContainerTests.Measure<SectionLink>();
|
||||
|
||||
// TODO: consider tests for the Draw method
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace QuestPDF.UnitTests
|
||||
public class InternalLocationTests
|
||||
{
|
||||
[Test]
|
||||
public void Measure() => SimpleContainerTests.Measure<InternalLink>();
|
||||
public void Measure() => SimpleContainerTests.Measure<SectionLink>();
|
||||
|
||||
// TODO: consider tests for the Draw method
|
||||
}
|
||||
|
||||
@@ -1,268 +0,0 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.UnitTests.TestEngine;
|
||||
|
||||
namespace QuestPDF.UnitTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class RowTests
|
||||
{
|
||||
#region Measure
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsWrap_WhenLeftChildReturnsWrap()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new BinaryRow
|
||||
{
|
||||
Left = x.CreateChild("left"),
|
||||
Right = x.CreateChild("right")
|
||||
})
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.Wrap())
|
||||
.CheckMeasureResult(SpacePlan.Wrap());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsWrap_WhenRightChildReturnsWrap()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new BinaryRow
|
||||
{
|
||||
Left = x.CreateChild("left"),
|
||||
Right = x.CreateChild("right")
|
||||
})
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.FullRender(250, 150))
|
||||
.ExpectChildMeasure("right", new Size(150, 300), SpacePlan.Wrap())
|
||||
.CheckMeasureResult(SpacePlan.Wrap());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsPartialRender_WhenLeftChildReturnsPartialRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new BinaryRow
|
||||
{
|
||||
Left = x.CreateChild("left"),
|
||||
Right = x.CreateChild("right")
|
||||
})
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.PartialRender(250, 150))
|
||||
.ExpectChildMeasure("right", new Size(150, 300), SpacePlan.FullRender(100, 100))
|
||||
.CheckMeasureResult(SpacePlan.PartialRender(350, 150));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsPartialRender_WhenRightChildReturnsPartialRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new BinaryRow
|
||||
{
|
||||
Left = x.CreateChild("left"),
|
||||
Right = x.CreateChild("right")
|
||||
})
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.FullRender(250, 150))
|
||||
.ExpectChildMeasure("right", new Size(150, 300), SpacePlan.PartialRender(100, 100))
|
||||
.CheckMeasureResult(SpacePlan.PartialRender(350, 150));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsFullRender_WhenBothChildrenReturnFullRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new BinaryRow
|
||||
{
|
||||
Left = x.CreateChild("left"),
|
||||
Right = x.CreateChild("right")
|
||||
})
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.FullRender(200, 150))
|
||||
.ExpectChildMeasure("right", new Size(200, 300), SpacePlan.FullRender(100, 100))
|
||||
.CheckMeasureResult(SpacePlan.FullRender(300, 150));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Draw
|
||||
|
||||
[Test]
|
||||
public void Draw()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new BinaryRow
|
||||
{
|
||||
Left = x.CreateChild("left"),
|
||||
Right = x.CreateChild("right")
|
||||
})
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.FullRender(250, 150))
|
||||
.ExpectChildDraw("left", new Size(250, 300))
|
||||
.ExpectChildMeasure("right", new Size(150, 300), SpacePlan.FullRender(150, 200))
|
||||
.ExpectCanvasTranslate(250, 0)
|
||||
.ExpectChildDraw("right", new Size(150, 300))
|
||||
.ExpectCanvasTranslate(-250, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Structure
|
||||
|
||||
[Test]
|
||||
public void Structure_RelativeColumnsHandling()
|
||||
{
|
||||
// arrange
|
||||
var childA = TestPlan.CreateUniqueElement();
|
||||
var childB = TestPlan.CreateUniqueElement();
|
||||
var childC = TestPlan.CreateUniqueElement();
|
||||
var childD = TestPlan.CreateUniqueElement();
|
||||
var childE = TestPlan.CreateUniqueElement();
|
||||
|
||||
const int spacing = 25;
|
||||
var availableSpace = new Size(1100, 400);
|
||||
|
||||
// act
|
||||
var value = new Container();
|
||||
|
||||
value.Row(row =>
|
||||
{
|
||||
row.Spacing(spacing);
|
||||
|
||||
row.ConstantColumn(150).Element(childA);
|
||||
row.ConstantColumn(250).Element(childB);
|
||||
row.RelativeColumn(1).Element(childC);
|
||||
row.RelativeColumn(2).Element(childD);
|
||||
row.RelativeColumn(3).Element(childE);
|
||||
});
|
||||
|
||||
// assert
|
||||
var expected = new Container();
|
||||
|
||||
expected.Row(row =>
|
||||
{
|
||||
row.Spacing(spacing);
|
||||
|
||||
row.ConstantColumn(150).Element(childA);
|
||||
row.ConstantColumn(250).Element(childB);
|
||||
row.ConstantColumn(100).Element(childC);
|
||||
row.ConstantColumn(200).Element(childD);
|
||||
row.ConstantColumn(300).Element(childE);
|
||||
});
|
||||
|
||||
TestPlan.CompareOperations(value, expected, availableSpace);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Structure_Tree()
|
||||
{
|
||||
// arrange
|
||||
var childA = TestPlan.CreateUniqueElement();
|
||||
var childB = TestPlan.CreateUniqueElement();
|
||||
var childC = TestPlan.CreateUniqueElement();
|
||||
var childD = TestPlan.CreateUniqueElement();
|
||||
var childE = TestPlan.CreateUniqueElement();
|
||||
|
||||
const int spacing = 25;
|
||||
var availableSpace = new Size(1200, 400);
|
||||
|
||||
// act
|
||||
var value = new Container();
|
||||
|
||||
value.Row(row =>
|
||||
{
|
||||
row.Spacing(spacing);
|
||||
|
||||
row.ConstantColumn(150).Element(childA);
|
||||
row.ConstantColumn(200).Element(childB);
|
||||
row.ConstantColumn(250).Element(childC);
|
||||
row.RelativeColumn(2).Element(childD);
|
||||
row.RelativeColumn(3).Element(childE);
|
||||
});
|
||||
|
||||
// assert
|
||||
var expected = new BinaryRow
|
||||
{
|
||||
Left = new BinaryRow
|
||||
{
|
||||
Left = new BinaryRow
|
||||
{
|
||||
Left = new Constrained
|
||||
{
|
||||
MinWidth = 150,
|
||||
MaxWidth = 150,
|
||||
Child = childA
|
||||
},
|
||||
Right = new Constrained
|
||||
{
|
||||
MinWidth = 25,
|
||||
MaxWidth = 25
|
||||
}
|
||||
},
|
||||
Right = new BinaryRow
|
||||
{
|
||||
Left = new Constrained
|
||||
{
|
||||
MinWidth = 200,
|
||||
MaxWidth = 200,
|
||||
Child = childB
|
||||
},
|
||||
Right = new Constrained
|
||||
{
|
||||
MinWidth = 25,
|
||||
MaxWidth = 25
|
||||
}
|
||||
}
|
||||
},
|
||||
Right = new BinaryRow
|
||||
{
|
||||
Left = new BinaryRow
|
||||
{
|
||||
Left = new Constrained
|
||||
{
|
||||
MinWidth = 250,
|
||||
MaxWidth = 250,
|
||||
Child = childC
|
||||
},
|
||||
Right = new Constrained
|
||||
{
|
||||
MinWidth = 25,
|
||||
MaxWidth = 25
|
||||
}
|
||||
},
|
||||
Right = new BinaryRow
|
||||
{
|
||||
Left = new Constrained
|
||||
{
|
||||
MinWidth = 200,
|
||||
MaxWidth = 200,
|
||||
Child = childD
|
||||
},
|
||||
Right = new BinaryRow
|
||||
{
|
||||
Left = new Constrained
|
||||
{
|
||||
MinWidth = 25,
|
||||
MaxWidth = 25
|
||||
},
|
||||
Right = new Constrained
|
||||
{
|
||||
MinWidth = 300,
|
||||
MaxWidth = 300,
|
||||
Child = childE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TestPlan.CompareOperations(value, expected, availableSpace);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,9 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
public void DrawRectangle(Position vector, Size size, string color) => DrawRectFunc(vector, size, color);
|
||||
public void DrawText(string text, Position position, TextStyle style) => DrawTextFunc(text, position, style);
|
||||
public void DrawImage(SKImage image, Position position, Size size) => DrawImageFunc(image, position, size);
|
||||
|
||||
public void DrawExternalLink(string url, Size size) => throw new NotImplementedException();
|
||||
public void DrawLocationLink(string locationName, Size size) => throw new NotImplementedException();
|
||||
public void DrawLocation(string locationName) => throw new NotImplementedException();
|
||||
|
||||
public void DrawHyperlink(string url, Size size) => throw new NotImplementedException();
|
||||
public void DrawSectionLink(string sectionName, Size size) => throw new NotImplementedException();
|
||||
public void DrawSection(string sectionName) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,8 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
public void DrawText(string text, Position position, TextStyle style) => Operations.Add(new CanvasDrawTextOperation(text, position, style));
|
||||
public void DrawImage(SKImage image, Position position, Size size) => Operations.Add(new CanvasDrawImageOperation(position, size));
|
||||
|
||||
public void DrawExternalLink(string url, Size size) => throw new NotImplementedException();
|
||||
public void DrawLocationLink(string locationName, Size size) => throw new NotImplementedException();
|
||||
public void DrawLocation(string locationName) => throw new NotImplementedException();
|
||||
public void DrawHyperlink(string url, Size size) => throw new NotImplementedException();
|
||||
public void DrawSectionLink(string sectionName, Size size) => throw new NotImplementedException();
|
||||
public void DrawSection(string sectionName) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,7 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
Assert.AreEqual(expected.Position.Y, position.Y, "Draw text: Y");
|
||||
|
||||
Assert.AreEqual(expected.Style.Color, style.Color, "Draw text: color");
|
||||
Assert.AreEqual(expected.Style.FontType, style.FontType, "Draw text: font");
|
||||
Assert.AreEqual(expected.Style.FontFamily, style.FontFamily, "Draw text: font");
|
||||
Assert.AreEqual(expected.Style.Size, style.Size, "Draw text: size");
|
||||
},
|
||||
DrawImageFunc = (image, position, size) =>
|
||||
|
||||
@@ -8,6 +8,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPDF.UnitTests", "Quest
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPDF.Examples", "QuestPDF.Examples\QuestPDF.Examples.csproj", "{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPDF.Previewer", "QuestPDF.Previewer\QuestPDF.Previewer.csproj", "{B4D3CBBC-2F38-434F-9347-6E5297737047}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPDF.Previewer.Example", "QuestPDF.Previewer.Example\QuestPDF.Previewer.Example.csproj", "{012F05A0-C30B-4112-8EB0-E70CE1D31E57}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -30,5 +34,13 @@ Global
|
||||
{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B4D3CBBC-2F38-434F-9347-6E5297737047}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B4D3CBBC-2F38-434F-9347-6E5297737047}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B4D3CBBC-2F38-434F-9347-6E5297737047}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B4D3CBBC-2F38-434F-9347-6E5297737047}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{012F05A0-C30B-4112-8EB0-E70CE1D31E57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{012F05A0-C30B-4112-8EB0-E70CE1D31E57}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{012F05A0-C30B-4112-8EB0-E70CE1D31E57}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{012F05A0-C30B-4112-8EB0-E70CE1D31E57}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -16,13 +16,13 @@ namespace QuestPDF.Drawing
|
||||
var container = new Container();
|
||||
|
||||
container
|
||||
.Stack(stack =>
|
||||
.Column(column =>
|
||||
{
|
||||
Pages
|
||||
.SelectMany(x => new List<Action>()
|
||||
{
|
||||
() => stack.Item().PageBreak(),
|
||||
() => stack.Item().Component(x)
|
||||
() => column.Item().PageBreak(),
|
||||
() => column.Item().Component(x)
|
||||
})
|
||||
.Skip(1)
|
||||
.ToList()
|
||||
|
||||
@@ -17,6 +17,8 @@ namespace QuestPDF.Drawing
|
||||
{
|
||||
internal static void GeneratePdf(Stream stream, IDocument document)
|
||||
{
|
||||
CheckIfStreamIsCompatible(stream);
|
||||
|
||||
var metadata = document.GetMetadata();
|
||||
var canvas = new PdfCanvas(stream, metadata);
|
||||
RenderDocument(canvas, document);
|
||||
@@ -24,10 +26,21 @@ namespace QuestPDF.Drawing
|
||||
|
||||
internal static void GenerateXps(Stream stream, IDocument document)
|
||||
{
|
||||
CheckIfStreamIsCompatible(stream);
|
||||
|
||||
var metadata = document.GetMetadata();
|
||||
var canvas = new XpsCanvas(stream, metadata);
|
||||
RenderDocument(canvas, document);
|
||||
}
|
||||
|
||||
private static void CheckIfStreamIsCompatible(Stream stream)
|
||||
{
|
||||
if (!stream.CanWrite)
|
||||
throw new ArgumentException("The library requires a Stream object with the 'write' capability available (the CanWrite flag). Please consider using the MemoryStream class.");
|
||||
|
||||
if (!stream.CanSeek)
|
||||
throw new ArgumentException("The library requires a Stream object with the 'seek' capability available (the CanSeek flag). Please consider using the MemoryStream class.");
|
||||
}
|
||||
|
||||
internal static ICollection<byte[]> GenerateImages(IDocument document)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
@@ -8,16 +9,41 @@ namespace QuestPDF.Drawing
|
||||
{
|
||||
public static class FontManager
|
||||
{
|
||||
private static ConcurrentDictionary<string, SKTypeface> Typefaces = new ConcurrentDictionary<string, SKTypeface>();
|
||||
private static ConcurrentDictionary<string, SKFontMetrics> FontMetrics = new ConcurrentDictionary<string, SKFontMetrics>();
|
||||
private static ConcurrentDictionary<string, SKPaint> Paints = new ConcurrentDictionary<string, SKPaint>();
|
||||
private static ConcurrentDictionary<string, SKPaint> ColorPaint = new ConcurrentDictionary<string, SKPaint>();
|
||||
private static ConcurrentDictionary<string, FontStyleSet> StyleSets = new();
|
||||
private static ConcurrentDictionary<object, SKFontMetrics> FontMetrics = new();
|
||||
private static ConcurrentDictionary<object, SKPaint> Paints = new();
|
||||
private static ConcurrentDictionary<string, SKPaint> ColorPaint = new();
|
||||
|
||||
private static void RegisterFontType(SKData fontData, string? customName = null)
|
||||
{
|
||||
foreach (var index in Enumerable.Range(0, 256))
|
||||
{
|
||||
var typeface = SKTypeface.FromData(fontData, index);
|
||||
|
||||
if (typeface == null)
|
||||
break;
|
||||
|
||||
var typefaceName = customName ?? typeface.FamilyName;
|
||||
|
||||
var fontStyleSet = StyleSets.GetOrAdd(typefaceName, _ => new FontStyleSet());
|
||||
fontStyleSet.Add(typeface);
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Since version 2022.3, the FontManager class offers better font type matching support. Please use the RegisterFont(Stream stream) method.")]
|
||||
public static void RegisterFontType(string fontName, Stream stream)
|
||||
{
|
||||
Typefaces.TryAdd(fontName, SKTypeface.FromStream(stream));
|
||||
using var fontData = SKData.Create(stream);
|
||||
RegisterFontType(fontData);
|
||||
RegisterFontType(fontData, customName: fontName);
|
||||
}
|
||||
|
||||
|
||||
public static void RegisterFont(Stream stream)
|
||||
{
|
||||
using var fontData = SKData.Create(stream);
|
||||
RegisterFontType(fontData);
|
||||
}
|
||||
|
||||
internal static SKPaint ColorToPaint(this string color)
|
||||
{
|
||||
return ColorPaint.GetOrAdd(color, Convert);
|
||||
@@ -30,37 +56,47 @@ namespace QuestPDF.Drawing
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal static SKPaint ToPaint(this TextStyle style)
|
||||
{
|
||||
return Paints.GetOrAdd(style.Key, key => Convert(style));
|
||||
|
||||
return Paints.GetOrAdd(style.PaintKey, key => Convert(style));
|
||||
|
||||
static SKPaint Convert(TextStyle style)
|
||||
{
|
||||
return new SKPaint
|
||||
{
|
||||
Color = SKColor.Parse(style.Color),
|
||||
Typeface = GetTypeface(style),
|
||||
TextSize = (style.Size ?? 12),
|
||||
TextEncoding = SKTextEncoding.Utf32
|
||||
TextSize = style.Size ?? 12
|
||||
};
|
||||
}
|
||||
|
||||
static SKTypeface GetTypeface(TextStyle style)
|
||||
{
|
||||
if (Typefaces.TryGetValue(style.FontType, out var result))
|
||||
return result;
|
||||
|
||||
var weight = (SKFontStyleWeight)(style.FontWeight ?? FontWeight.Normal);
|
||||
var slant = (style.IsItalic ?? false) ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
|
||||
|
||||
var fontStyle = new SKFontStyle(weight, SKFontStyleWidth.Normal, slant);
|
||||
|
||||
if (StyleSets.TryGetValue(style.FontFamily, out var fontStyleSet))
|
||||
return fontStyleSet.Match(fontStyle);
|
||||
|
||||
var fontFromDefaultSource = SKFontManager.Default.MatchFamily(style.FontFamily, fontStyle);
|
||||
|
||||
return SKTypeface.FromFamilyName(style.FontType, (int)(style.FontWeight ?? FontWeight.Normal), (int)SKFontStyleWidth.Normal, slant)
|
||||
?? throw new ArgumentException($"The typeface {style.FontType} could not be found.");
|
||||
if (fontFromDefaultSource != null)
|
||||
return fontFromDefaultSource;
|
||||
|
||||
throw new ArgumentException(
|
||||
$"The typeface '{style.FontFamily}' could not be found. " +
|
||||
$"Please consider the following options: " +
|
||||
$"1) install the font on your operating system or execution environment. " +
|
||||
$"2) load a font file specifically for QuestPDF usage via the QuestPDF.Drawing.FontManager.RegisterFontType(Stream fileContentStream) static method.");
|
||||
}
|
||||
}
|
||||
|
||||
internal static SKFontMetrics ToFontMetrics(this TextStyle style)
|
||||
{
|
||||
return FontMetrics.GetOrAdd(style.Key, key => style.ToPaint().FontMetrics);
|
||||
return FontMetrics.GetOrAdd(style.FontMetricsKey, key => style.ToPaint().FontMetrics);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
internal class FontStyleSet
|
||||
{
|
||||
private ConcurrentDictionary<SKFontStyle, SKTypeface> Styles { get; } = new();
|
||||
|
||||
public void Add(SKTypeface typeface)
|
||||
{
|
||||
var style = typeface.FontStyle;
|
||||
Styles.AddOrUpdate(style, _ => typeface, (_, _) => typeface);
|
||||
}
|
||||
|
||||
public SKTypeface? Match(SKFontStyle target)
|
||||
{
|
||||
SKFontStyle? bestStyle = null;
|
||||
SKTypeface? bestTypeface = null;
|
||||
|
||||
foreach (var entry in Styles)
|
||||
{
|
||||
if (IsBetterMatch(target, entry.Key, bestStyle))
|
||||
{
|
||||
bestStyle = entry.Key;
|
||||
bestTypeface = entry.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return bestTypeface;
|
||||
}
|
||||
|
||||
private static Dictionary<SKFontStyleSlant, List<SKFontStyleSlant>> SlantFallbacks = new()
|
||||
{
|
||||
{ SKFontStyleSlant.Italic, new() { SKFontStyleSlant.Italic, SKFontStyleSlant.Oblique, SKFontStyleSlant.Upright } },
|
||||
{ SKFontStyleSlant.Oblique, new() { SKFontStyleSlant.Oblique, SKFontStyleSlant.Italic, SKFontStyleSlant.Upright } },
|
||||
{ SKFontStyleSlant.Upright, new() { SKFontStyleSlant.Upright, SKFontStyleSlant.Oblique, SKFontStyleSlant.Italic } },
|
||||
};
|
||||
|
||||
// Checks whether style a is a better match for the target then style b. Uses the CSS font style matching algorithm
|
||||
internal static bool IsBetterMatch(SKFontStyle? target, SKFontStyle? a, SKFontStyle? b)
|
||||
{
|
||||
// A font is better than no font
|
||||
if (b == null)
|
||||
return true;
|
||||
|
||||
if (a == null)
|
||||
return false;
|
||||
|
||||
// First check font width
|
||||
// For normal and condensed widths prefer smaller widths
|
||||
// For expanded widths prefer larger widths
|
||||
if (target.Width <= (int)SKFontStyleWidth.Normal)
|
||||
{
|
||||
if (a.Width <= target.Width && b.Width > target.Width)
|
||||
return true;
|
||||
|
||||
if (a.Width > target.Width && b.Width <= target.Width)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (a.Width >= target.Width && b.Width < target.Width)
|
||||
return true;
|
||||
|
||||
if (a.Width < target.Width && b.Width >= target.Width)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prefer closest match
|
||||
var widthDifferenceA = Math.Abs(a.Width - target.Width);
|
||||
var widthDifferenceB = Math.Abs(b.Width - target.Width);
|
||||
|
||||
if (widthDifferenceA < widthDifferenceB)
|
||||
return true;
|
||||
|
||||
if (widthDifferenceB < widthDifferenceA)
|
||||
return false;
|
||||
|
||||
// Prefer closest slant based on provided fallback list
|
||||
var slantFallback = SlantFallbacks[target.Slant];
|
||||
var slantIndexA = slantFallback.IndexOf(a.Slant);
|
||||
var slantIndexB = slantFallback.IndexOf(b.Slant);
|
||||
|
||||
if (slantIndexA < slantIndexB)
|
||||
return true;
|
||||
|
||||
if (slantIndexB < slantIndexA)
|
||||
return false;
|
||||
|
||||
// Check weight last
|
||||
// For thin (<400) weights, prefer thinner weights
|
||||
// For regular (400-500) weights, prefer other regular weights, then use rule for thin or bold
|
||||
// For bold (>500) weights, prefer thicker weights
|
||||
// Behavior for values other than multiples of 100 is not given in the specification
|
||||
|
||||
if (target.Weight >= 400 && target.Weight <= 500)
|
||||
{
|
||||
if ((a.Weight >= 400 && a.Weight <= 500) && !(b.Weight >= 400 && b.Weight <= 500))
|
||||
return true;
|
||||
|
||||
if (!(a.Weight >= 400 && a.Weight <= 500) && (b.Weight >= 400 && b.Weight <= 500))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target.Weight < 450)
|
||||
{
|
||||
if (a.Weight <= target.Weight && b.Weight > target.Weight)
|
||||
return true;
|
||||
|
||||
if (a.Weight > target.Weight && b.Weight <= target.Weight)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (a.Weight >= target.Weight && b.Weight < target.Weight)
|
||||
return true;
|
||||
|
||||
if (a.Weight < target.Weight && b.Weight >= target.Weight)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prefer closest weight
|
||||
var weightDifferenceA = Math.Abs(a.Weight - target.Weight);
|
||||
var weightDifferenceB = Math.Abs(b.Weight - target.Weight);
|
||||
|
||||
return weightDifferenceA < weightDifferenceB;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,17 +51,17 @@ namespace QuestPDF.Drawing
|
||||
|
||||
}
|
||||
|
||||
public void DrawExternalLink(string url, Size size)
|
||||
public void DrawHyperlink(string url, Size size)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void DrawLocationLink(string locationName, Size size)
|
||||
public void DrawSectionLink(string sectionName, Size size)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void DrawLocation(string locationName)
|
||||
public void DrawSection(string sectionName)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.IO;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.Helpers;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
|
||||
@@ -37,19 +37,19 @@ namespace QuestPDF.Drawing
|
||||
Canvas.DrawImage(image, new SKRect(vector.X, vector.Y, size.Width, size.Height));
|
||||
}
|
||||
|
||||
public void DrawExternalLink(string url, Size size)
|
||||
public void DrawHyperlink(string url, Size size)
|
||||
{
|
||||
Canvas.DrawUrlAnnotation(new SKRect(0, 0, size.Width, size.Height), url);
|
||||
}
|
||||
|
||||
public void DrawLocationLink(string locationName, Size size)
|
||||
public void DrawSectionLink(string sectionName, Size size)
|
||||
{
|
||||
Canvas.DrawLinkDestinationAnnotation(new SKRect(0, 0, size.Width, size.Height), locationName);
|
||||
Canvas.DrawLinkDestinationAnnotation(new SKRect(0, 0, size.Width, size.Height), sectionName);
|
||||
}
|
||||
|
||||
public void DrawLocation(string locationName)
|
||||
public void DrawSection(string sectionName)
|
||||
{
|
||||
Canvas.DrawNamedDestinationAnnotation(new SKPoint(0, 0), locationName);
|
||||
Canvas.DrawNamedDestinationAnnotation(new SKPoint(0, 0), sectionName);
|
||||
}
|
||||
|
||||
public void Rotate(float angle)
|
||||
|
||||
@@ -6,16 +6,16 @@ namespace QuestPDF.Drawing
|
||||
{
|
||||
internal class SkiaDocumentCanvasBase : SkiaCanvasBase
|
||||
{
|
||||
private SKDocument Document { get; }
|
||||
private SKDocument? Document { get; }
|
||||
|
||||
public SkiaDocumentCanvasBase(SKDocument document)
|
||||
protected SkiaDocumentCanvasBase(SKDocument document)
|
||||
{
|
||||
Document = document;
|
||||
}
|
||||
|
||||
~SkiaDocumentCanvasBase()
|
||||
{
|
||||
Document.Dispose();
|
||||
Document?.Dispose();
|
||||
}
|
||||
|
||||
public override void BeginDocument()
|
||||
@@ -25,7 +25,7 @@ namespace QuestPDF.Drawing
|
||||
|
||||
public override void EndDocument()
|
||||
{
|
||||
Canvas.Dispose();
|
||||
Canvas?.Dispose();
|
||||
|
||||
Document.Close();
|
||||
Document.Dispose();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.IO;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.Helpers;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class ColumnItem : Container
|
||||
{
|
||||
public bool IsRendered { get; set; }
|
||||
}
|
||||
|
||||
internal class ColumnItemRenderingCommand
|
||||
{
|
||||
public ColumnItem ColumnItem { get; set; }
|
||||
public SpacePlan Measurement { get; set; }
|
||||
public Size Size { get; set; }
|
||||
public Position Offset { get; set; }
|
||||
}
|
||||
|
||||
internal class Column : Element, ICacheable, IStateResettable
|
||||
{
|
||||
internal List<ColumnItem> Items { get; } = new();
|
||||
internal float Spacing { get; set; }
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
Items.ForEach(x => x.IsRendered = false);
|
||||
}
|
||||
|
||||
internal override IEnumerable<Element?> GetChildren()
|
||||
{
|
||||
return Items;
|
||||
}
|
||||
|
||||
internal override void CreateProxy(Func<Element?, Element?> create)
|
||||
{
|
||||
Items.ForEach(x => x.Child = create(x.Child));
|
||||
}
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
if (!Items.Any())
|
||||
return SpacePlan.FullRender(Size.Zero);
|
||||
|
||||
var renderingCommands = PlanLayout(availableSpace);
|
||||
|
||||
if (!renderingCommands.Any())
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
var width = renderingCommands.Max(x => x.Size.Width);
|
||||
var height = renderingCommands.Last().Offset.Y + renderingCommands.Last().Size.Height;
|
||||
var size = new Size(width, height);
|
||||
|
||||
if (width > availableSpace.Width + Size.Epsilon || height > availableSpace.Height + Size.Epsilon)
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
var totalRenderedItems = Items.Count(x => x.IsRendered) + renderingCommands.Count(x => x.Measurement.Type == SpacePlanType.FullRender);
|
||||
var willBeFullyRendered = totalRenderedItems == Items.Count;
|
||||
|
||||
return willBeFullyRendered
|
||||
? SpacePlan.FullRender(size)
|
||||
: SpacePlan.PartialRender(size);
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var renderingCommands = PlanLayout(availableSpace);
|
||||
|
||||
foreach (var command in renderingCommands)
|
||||
{
|
||||
if (command.Measurement.Type == SpacePlanType.FullRender)
|
||||
command.ColumnItem.IsRendered = true;
|
||||
|
||||
var targetSize = new Size(availableSpace.Width, command.Size.Height);
|
||||
|
||||
Canvas.Translate(command.Offset);
|
||||
command.ColumnItem.Draw(targetSize);
|
||||
Canvas.Translate(command.Offset.Reverse());
|
||||
}
|
||||
|
||||
if (Items.All(x => x.IsRendered))
|
||||
ResetState();
|
||||
}
|
||||
|
||||
private ICollection<ColumnItemRenderingCommand> PlanLayout(Size availableSpace)
|
||||
{
|
||||
var topOffset = 0f;
|
||||
var commands = new List<ColumnItemRenderingCommand>();
|
||||
|
||||
foreach (var item in Items)
|
||||
{
|
||||
if (item.IsRendered)
|
||||
continue;
|
||||
|
||||
var itemSpace = new Size(availableSpace.Width, availableSpace.Height - topOffset);
|
||||
var measurement = item.Measure(itemSpace);
|
||||
|
||||
if (measurement.Type == SpacePlanType.Wrap)
|
||||
break;
|
||||
|
||||
commands.Add(new ColumnItemRenderingCommand
|
||||
{
|
||||
ColumnItem = item,
|
||||
Size = measurement,
|
||||
Measurement = measurement,
|
||||
Offset = new Position(0, topOffset)
|
||||
});
|
||||
|
||||
if (measurement.Type == SpacePlanType.PartialRender)
|
||||
break;
|
||||
|
||||
topOffset += measurement.Height + Spacing;
|
||||
}
|
||||
|
||||
var targetWidth = commands.Select(x => x.Size.Width).DefaultIfEmpty(0).Max();
|
||||
commands.ForEach(x => x.Size = new Size(targetWidth, x.Size.Height));
|
||||
|
||||
return commands;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,10 @@ namespace QuestPDF.Elements
|
||||
.MinimalBox()
|
||||
.Background(Colors.White)
|
||||
.Padding(2)
|
||||
.Text(Text, TextStyle.Default.Color(Color).FontType(Fonts.Consolas).Size(8));
|
||||
.Text(Text)
|
||||
.FontColor(Color)
|
||||
.FontFamily(Fonts.Consolas)
|
||||
.FontSize(8);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,98 +1,112 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal enum DecorationType
|
||||
internal class DecorationItemRenderingCommand
|
||||
{
|
||||
Prepend,
|
||||
Append
|
||||
public Element Element { get; set; }
|
||||
public SpacePlan Measurement { get; set; }
|
||||
public Position Offset { get; set; }
|
||||
}
|
||||
|
||||
internal class BinaryDecoration : Element, ICacheable
|
||||
internal class Decoration : Element, ICacheable
|
||||
{
|
||||
public Element DecorationElement { get; set; } = Empty.Instance;
|
||||
public Element ContentElement { get; set; } = Empty.Instance;
|
||||
public DecorationType Type { get; set; }
|
||||
internal Element Before { get; set; } = new Empty();
|
||||
internal Element Content { get; set; } = new Empty();
|
||||
internal Element After { get; set; } = new Empty();
|
||||
|
||||
internal override IEnumerable<Element?> GetChildren()
|
||||
{
|
||||
yield return DecorationElement;
|
||||
yield return ContentElement;
|
||||
yield return Before;
|
||||
yield return Content;
|
||||
yield return After;
|
||||
}
|
||||
|
||||
internal override void CreateProxy(Func<Element, Element> create)
|
||||
|
||||
internal override void CreateProxy(Func<Element?, Element?> create)
|
||||
{
|
||||
DecorationElement = create(DecorationElement);
|
||||
ContentElement = create(ContentElement);
|
||||
Before = create(Before);
|
||||
Content = create(Content);
|
||||
After = create(After);
|
||||
}
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
var decorationMeasure = DecorationElement.Measure(availableSpace);
|
||||
|
||||
if (decorationMeasure.Type == SpacePlanType.Wrap || decorationMeasure.Type == SpacePlanType.PartialRender)
|
||||
var renderingCommands = PlanLayout(availableSpace).ToList();
|
||||
|
||||
if (renderingCommands.Any(x => x.Measurement.Type == SpacePlanType.Wrap))
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
var decorationSize = decorationMeasure;
|
||||
var contentMeasure = ContentElement.Measure(new Size(availableSpace.Width, availableSpace.Height - decorationSize.Height));
|
||||
var width = renderingCommands.Max(x => x.Measurement.Width);
|
||||
var height = renderingCommands.Sum(x => x.Measurement.Height);
|
||||
var size = new Size(width, height);
|
||||
|
||||
if (contentMeasure.Type == SpacePlanType.Wrap)
|
||||
if (width > availableSpace.Width + Size.Epsilon || height > availableSpace.Height + Size.Epsilon)
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
var willBeFullyRendered = renderingCommands.All(x => x.Measurement.Type == SpacePlanType.FullRender);
|
||||
|
||||
var contentSize = contentMeasure;
|
||||
var resultSize = new Size(availableSpace.Width, decorationSize.Height + contentSize.Height);
|
||||
|
||||
if (contentSize.Type == SpacePlanType.PartialRender)
|
||||
return SpacePlan.PartialRender(resultSize);
|
||||
|
||||
if (contentSize.Type == SpacePlanType.FullRender)
|
||||
return SpacePlan.FullRender(resultSize);
|
||||
|
||||
throw new NotSupportedException();
|
||||
return willBeFullyRendered
|
||||
? SpacePlan.FullRender(size)
|
||||
: SpacePlan.PartialRender(size);
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var decorationSize = DecorationElement.Measure(availableSpace);
|
||||
var contentSize = new Size(availableSpace.Width, availableSpace.Height - decorationSize.Height);
|
||||
|
||||
var translateHeight = Type == DecorationType.Prepend ? decorationSize.Height : contentSize.Height;
|
||||
Action drawDecoration = () => DecorationElement?.Draw(new Size(availableSpace.Width, decorationSize.Height));
|
||||
Action drawContent = () => ContentElement?.Draw(new Size (availableSpace.Width, contentSize.Height));
|
||||
|
||||
var first = Type == DecorationType.Prepend ? drawDecoration : drawContent;
|
||||
var second = Type == DecorationType.Prepend ? drawContent : drawDecoration;
|
||||
|
||||
first();
|
||||
Canvas.Translate(new Position(0, translateHeight));
|
||||
second();
|
||||
Canvas.Translate(new Position(0, -translateHeight));
|
||||
}
|
||||
}
|
||||
|
||||
internal class Decoration : IComponent
|
||||
{
|
||||
public Element Header { get; set; } = Empty.Instance;
|
||||
public Element Content { get; set; } = Empty.Instance;
|
||||
public Element Footer { get; set; } = Empty.Instance;
|
||||
|
||||
public void Compose(IContainer container)
|
||||
{
|
||||
container.Element(new BinaryDecoration
|
||||
var renderingCommands = PlanLayout(availableSpace).ToList();
|
||||
var width = renderingCommands.Max(x => x.Measurement.Width);
|
||||
|
||||
foreach (var command in renderingCommands)
|
||||
{
|
||||
Type = DecorationType.Prepend,
|
||||
DecorationElement = Header,
|
||||
ContentElement = new BinaryDecoration
|
||||
{
|
||||
Type = DecorationType.Append,
|
||||
ContentElement = Content,
|
||||
DecorationElement = Footer
|
||||
}
|
||||
});
|
||||
var elementSize = new Size(width, command.Measurement.Height);
|
||||
|
||||
Canvas.Translate(command.Offset);
|
||||
command.Element.Draw(elementSize);
|
||||
Canvas.Translate(command.Offset.Reverse());
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<DecorationItemRenderingCommand> PlanLayout(Size availableSpace)
|
||||
{
|
||||
SpacePlan GetDecorationMeasurement(Element element)
|
||||
{
|
||||
var measurement = element.Measure(availableSpace);
|
||||
|
||||
return measurement.Type == SpacePlanType.FullRender
|
||||
? measurement
|
||||
: SpacePlan.Wrap();
|
||||
}
|
||||
|
||||
var beforeMeasurement = GetDecorationMeasurement(Before);
|
||||
var afterMeasurement = GetDecorationMeasurement(After);
|
||||
|
||||
var contentSpace = new Size(availableSpace.Width, availableSpace.Height - beforeMeasurement.Height - afterMeasurement.Height);
|
||||
var contentMeasurement = Content.Measure(contentSpace);
|
||||
|
||||
yield return new DecorationItemRenderingCommand
|
||||
{
|
||||
Element = Before,
|
||||
Measurement = beforeMeasurement,
|
||||
Offset = Position.Zero
|
||||
};
|
||||
|
||||
yield return new DecorationItemRenderingCommand
|
||||
{
|
||||
Element = Content,
|
||||
Measurement = contentMeasurement,
|
||||
Offset = new Position(0, beforeMeasurement.Height)
|
||||
};
|
||||
|
||||
yield return new DecorationItemRenderingCommand
|
||||
{
|
||||
Element = After,
|
||||
Measurement = afterMeasurement,
|
||||
Offset = new Position(0, beforeMeasurement.Height + contentMeasurement.Height)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,12 +27,12 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
ChildrenQueue = new Queue<GridElement>(Children);
|
||||
|
||||
container.Stack(stack =>
|
||||
container.Column(column =>
|
||||
{
|
||||
stack.Spacing(VerticalSpacing);
|
||||
column.Spacing(VerticalSpacing);
|
||||
|
||||
while (ChildrenQueue.Any())
|
||||
stack.Item().Row(BuildRow);
|
||||
column.Item().Row(BuildRow);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -65,12 +65,12 @@ namespace QuestPDF.Elements
|
||||
emptySpace /= 2;
|
||||
|
||||
if (hasEmptySpace && Alignment != HorizontalAlignment.Left)
|
||||
row.RelativeColumn(emptySpace);
|
||||
row.RelativeItem(emptySpace);
|
||||
|
||||
elements.ForEach(x => row.RelativeColumn(x.Columns).Element(x.Child));
|
||||
elements.ForEach(x => row.RelativeItem(x.Columns).Element(x.Child));
|
||||
|
||||
if (hasEmptySpace && Alignment != HorizontalAlignment.Right)
|
||||
row.RelativeColumn(emptySpace);
|
||||
row.RelativeItem(emptySpace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class ExternalLink : ContainerElement
|
||||
internal class Hyperlink : ContainerElement
|
||||
{
|
||||
public string Url { get; set; } = "https://www.questpdf.com";
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace QuestPDF.Elements
|
||||
if (targetSize.Type == SpacePlanType.Wrap)
|
||||
return;
|
||||
|
||||
Canvas.DrawExternalLink(Url, targetSize);
|
||||
Canvas.DrawHyperlink(Url, targetSize);
|
||||
base.Draw(availableSpace);
|
||||
}
|
||||
}
|
||||
@@ -8,15 +8,7 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
internal class InlinedElement : Container
|
||||
{
|
||||
public SpacePlan? MeasureCache { get; set; }
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
// TODO: once element caching proxy is introduces, this can be removed
|
||||
|
||||
MeasureCache ??= Child.Measure(Size.Max);
|
||||
return MeasureCache.Value;
|
||||
}
|
||||
}
|
||||
|
||||
internal enum InlinedAlignment
|
||||
@@ -112,8 +104,11 @@ namespace QuestPDF.Elements
|
||||
|
||||
foreach (var element in elements)
|
||||
{
|
||||
var size = element.Measure(Size.Max);
|
||||
var size = (Size)element.Measure(Size.Max);
|
||||
var baselineOffset = BaselineOffset(size, lineSize.Height);
|
||||
|
||||
if (size.Height == 0)
|
||||
size = new Size(size.Width, lineSize.Height);
|
||||
|
||||
Canvas.Translate(new Position(0, baselineOffset));
|
||||
element.Draw(size);
|
||||
@@ -132,48 +127,39 @@ namespace QuestPDF.Elements
|
||||
if (elements.Count == 1)
|
||||
return 0;
|
||||
|
||||
if (ElementsAlignment == InlinedAlignment.Justify)
|
||||
return difference / (elements.Count - 1);
|
||||
|
||||
if (ElementsAlignment == InlinedAlignment.SpaceAround)
|
||||
return difference / (elements.Count + 1);
|
||||
|
||||
return HorizontalSpacing;
|
||||
return ElementsAlignment switch
|
||||
{
|
||||
InlinedAlignment.Justify => difference / (elements.Count - 1),
|
||||
InlinedAlignment.SpaceAround => difference / (elements.Count + 1),
|
||||
_ => HorizontalSpacing
|
||||
};
|
||||
}
|
||||
|
||||
float AlignOffset()
|
||||
{
|
||||
if (ElementsAlignment == InlinedAlignment.Left)
|
||||
return 0;
|
||||
|
||||
if (ElementsAlignment == InlinedAlignment.Justify)
|
||||
return 0;
|
||||
|
||||
if (ElementsAlignment == InlinedAlignment.SpaceAround)
|
||||
return elementOffset;
|
||||
|
||||
var difference = availableSpace.Width - lineSize.Width - (elements.Count - 1) * HorizontalSpacing;
|
||||
|
||||
if (ElementsAlignment == InlinedAlignment.Center)
|
||||
return difference / 2;
|
||||
|
||||
if (ElementsAlignment == InlinedAlignment.Right)
|
||||
return difference;
|
||||
|
||||
return 0;
|
||||
return ElementsAlignment switch
|
||||
{
|
||||
InlinedAlignment.Left => 0,
|
||||
InlinedAlignment.Justify => 0,
|
||||
InlinedAlignment.SpaceAround => elementOffset,
|
||||
InlinedAlignment.Center => difference / 2,
|
||||
InlinedAlignment.Right => difference,
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
|
||||
float BaselineOffset(Size elementSize, float lineHeight)
|
||||
{
|
||||
if (BaselineAlignment == VerticalAlignment.Top)
|
||||
return 0;
|
||||
|
||||
var difference = lineHeight - elementSize.Height;
|
||||
|
||||
if (BaselineAlignment == VerticalAlignment.Middle)
|
||||
return difference / 2;
|
||||
|
||||
return difference;
|
||||
return BaselineAlignment switch
|
||||
{
|
||||
VerticalAlignment.Top => 0,
|
||||
VerticalAlignment.Middle => difference / 2,
|
||||
_ => difference
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -250,11 +236,12 @@ namespace QuestPDF.Elements
|
||||
float GetInitialAlignmentOffset()
|
||||
{
|
||||
// this method makes sure that the spacing between elements is no lesser than configured
|
||||
|
||||
if (ElementsAlignment == InlinedAlignment.SpaceAround)
|
||||
return HorizontalSpacing * 2;
|
||||
|
||||
return 0;
|
||||
return ElementsAlignment switch
|
||||
{
|
||||
InlinedAlignment.SpaceAround => HorizontalSpacing * 2,
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
public interface ILine
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
internal enum LineType
|
||||
{
|
||||
Vertical,
|
||||
Horizontal
|
||||
}
|
||||
|
||||
internal class Line : Element, ILine, ICacheable
|
||||
{
|
||||
public LineType Type { get; set; } = LineType.Vertical;
|
||||
public string Color { get; set; } = Colors.Black;
|
||||
public float Size { get; set; } = 1;
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
return Type switch
|
||||
{
|
||||
LineType.Vertical when availableSpace.Width + Infrastructure.Size.Epsilon >= Size => SpacePlan.FullRender(Size, 0),
|
||||
LineType.Horizontal when availableSpace.Height + Infrastructure.Size.Epsilon >= Size => SpacePlan.FullRender(0, Size),
|
||||
_ => SpacePlan.Wrap()
|
||||
};
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
if (Type == LineType.Vertical)
|
||||
{
|
||||
Canvas.DrawRectangle(new Position(-Size/2, 0), new Size(Size, availableSpace.Height), Color);
|
||||
}
|
||||
else if (Type == LineType.Horizontal)
|
||||
{
|
||||
Canvas.DrawRectangle(new Position(0, -Size/2), new Size(availableSpace.Width, Size), Color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
-29
@@ -20,6 +20,9 @@ namespace QuestPDF.Elements
|
||||
|
||||
public string BackgroundColor { get; set; } = Colors.Transparent;
|
||||
|
||||
public Element Background { get; set; } = Empty.Instance;
|
||||
public Element Foreground { get; set; } = Empty.Instance;
|
||||
|
||||
public Element Header { get; set; } = Empty.Instance;
|
||||
public Element Content { get; set; } = Empty.Instance;
|
||||
public Element Footer { get; set; } = Empty.Instance;
|
||||
@@ -27,39 +30,53 @@ namespace QuestPDF.Elements
|
||||
public void Compose(IContainer container)
|
||||
{
|
||||
container
|
||||
.MinWidth(MinSize.Width)
|
||||
.MinHeight(MinSize.Height)
|
||||
|
||||
.MaxWidth(MaxSize.Width)
|
||||
.MaxHeight(MaxSize.Height)
|
||||
|
||||
.Background(BackgroundColor)
|
||||
|
||||
.PaddingLeft(MarginLeft)
|
||||
.PaddingRight(MarginRight)
|
||||
.PaddingTop(MarginTop)
|
||||
.PaddingBottom(MarginBottom)
|
||||
|
||||
.DefaultTextStyle(DefaultTextStyle)
|
||||
|
||||
.Decoration(decoration =>
|
||||
.Layers(layers =>
|
||||
{
|
||||
decoration
|
||||
.Header()
|
||||
.DebugPointer("Page header")
|
||||
.Element(Header);
|
||||
layers
|
||||
.Layer()
|
||||
.DebugPointer("Page background layer")
|
||||
.Element(Background);
|
||||
|
||||
decoration
|
||||
.Content()
|
||||
.Element(x => IsClose(MinSize.Width, MaxSize.Width) ? x.ExtendHorizontal() : x)
|
||||
.Element(x => IsClose(MinSize.Height, MaxSize.Height) ? x.ExtendVertical() : x)
|
||||
.DebugPointer("Page content")
|
||||
.Element(Content);
|
||||
layers
|
||||
.PrimaryLayer()
|
||||
.MinWidth(MinSize.Width)
|
||||
.MinHeight(MinSize.Height)
|
||||
|
||||
.MaxWidth(MaxSize.Width)
|
||||
.MaxHeight(MaxSize.Height)
|
||||
|
||||
.PaddingLeft(MarginLeft)
|
||||
.PaddingRight(MarginRight)
|
||||
.PaddingTop(MarginTop)
|
||||
.PaddingBottom(MarginBottom)
|
||||
|
||||
.DefaultTextStyle(DefaultTextStyle)
|
||||
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
decoration
|
||||
.Before()
|
||||
.DebugPointer("Page header")
|
||||
.Element(Header);
|
||||
|
||||
decoration
|
||||
.Content()
|
||||
.Element(x => IsClose(MinSize.Width, MaxSize.Width) ? x.ExtendHorizontal() : x)
|
||||
.Element(x => IsClose(MinSize.Height, MaxSize.Height) ? x.ExtendVertical() : x)
|
||||
.DebugPointer("Page content")
|
||||
.Element(Content);
|
||||
|
||||
decoration
|
||||
.After()
|
||||
.DebugPointer("Page footer")
|
||||
.Element(Footer);
|
||||
});
|
||||
|
||||
decoration
|
||||
.Footer()
|
||||
.DebugPointer("Page footer")
|
||||
.Element(Footer);
|
||||
layers
|
||||
.Layer()
|
||||
.DebugPointer("Page foreground layer")
|
||||
.Element(Foreground);
|
||||
});
|
||||
|
||||
bool IsClose(float x, float y)
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace QuestPDF.Elements
|
||||
if (string.IsNullOrWhiteSpace(Text))
|
||||
x.MaxHeight(32).Image(ImageData, ImageScaling.FitArea);
|
||||
else
|
||||
x.Text(Text, TextStyle.Default.Size(14));
|
||||
x.Text(Text).FontSize(14);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+120
-131
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing;
|
||||
@@ -6,174 +7,162 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class RowElement : Constrained
|
||||
internal enum RowItemType
|
||||
{
|
||||
public float ConstantSize { get; }
|
||||
public float RelativeSize { get; }
|
||||
|
||||
public RowElement(float constantSize, float relativeSize)
|
||||
{
|
||||
ConstantSize = constantSize;
|
||||
RelativeSize = relativeSize;
|
||||
}
|
||||
|
||||
public void SetWidth(float width)
|
||||
{
|
||||
MinWidth = width;
|
||||
MaxWidth = width;
|
||||
}
|
||||
Auto,
|
||||
Constant,
|
||||
Relative
|
||||
}
|
||||
|
||||
internal class BinaryRow : Element, ICacheable, IStateResettable
|
||||
internal class RowItem : Container
|
||||
{
|
||||
internal Element Left { get; set; }
|
||||
internal Element Right { get; set; }
|
||||
|
||||
private bool IsLeftRendered { get; set; }
|
||||
private bool IsRightRendered { get; set; }
|
||||
public bool IsRendered { get; set; }
|
||||
public float Width { get; set; }
|
||||
|
||||
public RowItemType Type { get; set; }
|
||||
public float Size { get; set; }
|
||||
}
|
||||
|
||||
internal class RowItemRenderingCommand
|
||||
{
|
||||
public RowItem RowItem { get; set; }
|
||||
public SpacePlan Measurement { get; set; }
|
||||
public Size Size { get; set; }
|
||||
public Position Offset { get; set; }
|
||||
}
|
||||
|
||||
internal class Row : Element, ICacheable, IStateResettable
|
||||
{
|
||||
internal List<RowItem> Items { get; } = new();
|
||||
internal float Spacing { get; set; }
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
IsLeftRendered = false;
|
||||
IsRightRendered = false;
|
||||
Items.ForEach(x => x.IsRendered = false);
|
||||
}
|
||||
|
||||
internal override IEnumerable<Element?> GetChildren()
|
||||
{
|
||||
yield return Left;
|
||||
yield return Right;
|
||||
return Items;
|
||||
}
|
||||
|
||||
internal override void CreateProxy(Func<Element?, Element?> create)
|
||||
{
|
||||
Left = create(Left);
|
||||
Right = create(Right);
|
||||
Items.ForEach(x => x.Child = create(x.Child));
|
||||
}
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height));
|
||||
if (!Items.Any())
|
||||
return SpacePlan.FullRender(Size.Zero);
|
||||
|
||||
if (leftMeasurement.Type == SpacePlanType.Wrap)
|
||||
UpdateItemsWidth(availableSpace.Width);
|
||||
var renderingCommands = PlanLayout(availableSpace);
|
||||
|
||||
if (renderingCommands.Any(x => !x.RowItem.IsRendered && x.Measurement.Type == SpacePlanType.Wrap))
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
var rightMeasurement = Right.Measure(new Size(availableSpace.Width - leftMeasurement.Width, availableSpace.Height));
|
||||
var width = renderingCommands.Last().Offset.X + renderingCommands.Last().Size.Width;
|
||||
var height = renderingCommands.Max(x => x.Size.Height);
|
||||
var size = new Size(width, height);
|
||||
|
||||
if (rightMeasurement.Type == SpacePlanType.Wrap)
|
||||
if (width > availableSpace.Width + Size.Epsilon || height > availableSpace.Height + Size.Epsilon)
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
if (renderingCommands.Any(x => !x.RowItem.IsRendered && x.Measurement.Type == SpacePlanType.PartialRender))
|
||||
return SpacePlan.PartialRender(size);
|
||||
|
||||
var totalWidth = leftMeasurement.Width + rightMeasurement.Width;
|
||||
var totalHeight = Math.Max(leftMeasurement.Height, rightMeasurement.Height);
|
||||
|
||||
var targetSize = new Size(totalWidth, totalHeight);
|
||||
|
||||
if ((!IsLeftRendered && leftMeasurement.Type == SpacePlanType.PartialRender) ||
|
||||
(!IsRightRendered && rightMeasurement.Type == SpacePlanType.PartialRender))
|
||||
return SpacePlan.PartialRender(targetSize);
|
||||
|
||||
return SpacePlan.FullRender(targetSize);
|
||||
return SpacePlan.FullRender(size);
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var leftSpace = new Size(availableSpace.Width, availableSpace.Height);
|
||||
var leftMeasurement = Left.Measure(leftSpace);
|
||||
if (!Items.Any())
|
||||
return;
|
||||
|
||||
if (leftMeasurement.Type == SpacePlanType.FullRender)
|
||||
IsLeftRendered = true;
|
||||
|
||||
Left.Draw(new Size(leftMeasurement.Width, availableSpace.Height));
|
||||
UpdateItemsWidth(availableSpace.Width);
|
||||
var renderingCommands = PlanLayout(availableSpace);
|
||||
|
||||
var rightSpace = new Size(availableSpace.Width - leftMeasurement.Width, availableSpace.Height);
|
||||
var rightMeasurement = Right.Measure(rightSpace);
|
||||
|
||||
if (rightMeasurement.Type == SpacePlanType.FullRender)
|
||||
IsRightRendered = true;
|
||||
|
||||
Canvas.Translate(new Position(leftMeasurement.Width, 0));
|
||||
Right.Draw(rightSpace);
|
||||
Canvas.Translate(new Position(-leftMeasurement.Width, 0));
|
||||
}
|
||||
}
|
||||
|
||||
internal class Row : Element
|
||||
{
|
||||
public float Spacing { get; set; } = 0;
|
||||
|
||||
public ICollection<RowElement> Items { get; internal set; } = new List<RowElement>();
|
||||
private Element? RootElement { get; set; }
|
||||
|
||||
internal override IEnumerable<Element?> GetChildren()
|
||||
{
|
||||
if (RootElement == null)
|
||||
ComposeTree();
|
||||
|
||||
yield return RootElement;
|
||||
}
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
UpdateElementsWidth(availableSpace.Width);
|
||||
return RootElement.Measure(availableSpace);
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
UpdateElementsWidth(availableSpace.Width);
|
||||
RootElement.Draw(availableSpace);
|
||||
}
|
||||
|
||||
#region structure
|
||||
|
||||
private void ComposeTree()
|
||||
{
|
||||
Items = AddSpacing(Items, Spacing);
|
||||
|
||||
var elements = Items.Cast<Element>().ToArray();
|
||||
RootElement = BuildTree(elements);
|
||||
}
|
||||
|
||||
private void UpdateElementsWidth(float availableWidth)
|
||||
{
|
||||
var constantWidth = Items.Sum(x => x.ConstantSize);
|
||||
var relativeWidth = Items.Sum(x => x.RelativeSize);
|
||||
|
||||
var widthPerRelativeUnit = (relativeWidth > 0) ? (availableWidth - constantWidth) / relativeWidth : 0;
|
||||
|
||||
foreach (var row in Items)
|
||||
foreach (var command in renderingCommands)
|
||||
{
|
||||
row.SetWidth(row.ConstantSize + row.RelativeSize * widthPerRelativeUnit);
|
||||
if (command.Measurement.Type == SpacePlanType.FullRender)
|
||||
command.RowItem.IsRendered = true;
|
||||
|
||||
if (command.Measurement.Type == SpacePlanType.Wrap)
|
||||
continue;
|
||||
|
||||
Canvas.Translate(command.Offset);
|
||||
command.RowItem.Draw(command.Size);
|
||||
Canvas.Translate(command.Offset.Reverse());
|
||||
}
|
||||
|
||||
if (Items.All(x => x.IsRendered))
|
||||
ResetState();
|
||||
}
|
||||
|
||||
private void UpdateItemsWidth(float availableWidth)
|
||||
{
|
||||
HandleItemsWithAutoWidth();
|
||||
|
||||
var constantWidth = Items.Where(x => x.Type == RowItemType.Constant).Sum(x => x.Size);
|
||||
var relativeWidth = Items.Where(x => x.Type == RowItemType.Relative).Sum(x => x.Size);
|
||||
var spacingWidth = (Items.Count - 1) * Spacing;
|
||||
|
||||
foreach (var item in Items.Where(x => x.Type == RowItemType.Constant))
|
||||
item.Width = item.Size;
|
||||
|
||||
if (relativeWidth <= 0)
|
||||
return;
|
||||
|
||||
var widthPerRelativeUnit = (availableWidth - constantWidth - spacingWidth) / relativeWidth;
|
||||
|
||||
foreach (var item in Items.Where(x => x.Type == RowItemType.Relative))
|
||||
item.Width = item.Size * widthPerRelativeUnit;
|
||||
}
|
||||
|
||||
private void HandleItemsWithAutoWidth()
|
||||
{
|
||||
foreach (var rowItem in Items.Where(x => x.Type == RowItemType.Auto))
|
||||
{
|
||||
rowItem.Size = rowItem.Measure(Size.Max).Width;
|
||||
rowItem.Type = RowItemType.Constant;
|
||||
}
|
||||
}
|
||||
|
||||
private static ICollection<RowElement> AddSpacing(ICollection<RowElement> elements, float spacing)
|
||||
|
||||
private ICollection<RowItemRenderingCommand> PlanLayout(Size availableSpace)
|
||||
{
|
||||
if (spacing < Size.Epsilon)
|
||||
return elements;
|
||||
|
||||
return elements
|
||||
.SelectMany(x => new[] { new RowElement(spacing, 0), x })
|
||||
.Skip(1)
|
||||
.ToList();
|
||||
}
|
||||
var leftOffset = 0f;
|
||||
var renderingCommands = new List<RowItemRenderingCommand>();
|
||||
|
||||
private static Element BuildTree(Span<Element> elements)
|
||||
{
|
||||
if (elements.IsEmpty)
|
||||
return Empty.Instance;
|
||||
|
||||
if (elements.Length == 1)
|
||||
return elements[0];
|
||||
|
||||
var half = elements.Length / 2;
|
||||
|
||||
return new BinaryRow
|
||||
foreach (var item in Items)
|
||||
{
|
||||
Left = BuildTree(elements.Slice(0, half)),
|
||||
Right = BuildTree(elements.Slice(half))
|
||||
};
|
||||
var itemSpace = new Size(item.Width, availableSpace.Height);
|
||||
|
||||
var command = new RowItemRenderingCommand
|
||||
{
|
||||
RowItem = item,
|
||||
Size = itemSpace,
|
||||
Measurement = item.Measure(itemSpace),
|
||||
Offset = new Position(leftOffset, 0)
|
||||
};
|
||||
|
||||
renderingCommands.Add(command);
|
||||
leftOffset += item.Width + Spacing;
|
||||
}
|
||||
|
||||
var rowHeight = renderingCommands
|
||||
.Where(x => !x.RowItem.IsRendered)
|
||||
.Select(x => x.Measurement.Height)
|
||||
.DefaultIfEmpty(0)
|
||||
.Max();
|
||||
|
||||
foreach (var command in renderingCommands)
|
||||
{
|
||||
command.Size = new Size(command.Size.Width, rowHeight);
|
||||
command.Measurement = command.RowItem.Measure(command.Size);
|
||||
}
|
||||
|
||||
return renderingCommands;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class ScaleToFit : ContainerElement
|
||||
{
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
if (Child == null)
|
||||
return SpacePlan.FullRender(Size.Zero);
|
||||
|
||||
var perfectScale = FindPerfectScale(Child, availableSpace);
|
||||
|
||||
if (perfectScale == null)
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
var scaledSpace = ScaleSize(availableSpace, 1 / perfectScale.Value);
|
||||
var childSizeInScale = Child.Measure(scaledSpace);
|
||||
var childSizeInOriginalScale = ScaleSize(childSizeInScale, perfectScale.Value);
|
||||
return SpacePlan.FullRender(childSizeInOriginalScale);
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var perfectScale = FindPerfectScale(Child, availableSpace);
|
||||
|
||||
if (!perfectScale.HasValue)
|
||||
return;
|
||||
|
||||
var targetScale = perfectScale.Value;
|
||||
var targetSpace = ScaleSize(availableSpace, 1 / targetScale);
|
||||
|
||||
Canvas.Scale(targetScale, targetScale);
|
||||
Child?.Draw(targetSpace);
|
||||
Canvas.Scale(1 / targetScale, 1 / targetScale);
|
||||
}
|
||||
|
||||
private static Size ScaleSize(Size size, float factor)
|
||||
{
|
||||
return new Size(size.Width * factor, size.Height * factor);
|
||||
}
|
||||
|
||||
private static float? FindPerfectScale(Element child, Size availableSpace)
|
||||
{
|
||||
if (ChildFits(1))
|
||||
return 1;
|
||||
|
||||
var maxScale = 1f;
|
||||
var minScale = Size.Epsilon;
|
||||
|
||||
var lastWorkingScale = (float?)null;
|
||||
|
||||
foreach (var _ in Enumerable.Range(0, 8))
|
||||
{
|
||||
var halfScale = (maxScale + minScale) / 2;
|
||||
|
||||
if (ChildFits(halfScale))
|
||||
{
|
||||
minScale = halfScale;
|
||||
lastWorkingScale = halfScale;
|
||||
}
|
||||
else
|
||||
{
|
||||
maxScale = halfScale;
|
||||
}
|
||||
}
|
||||
|
||||
return lastWorkingScale;
|
||||
|
||||
bool ChildFits(float scale)
|
||||
{
|
||||
var scaledSpace = ScaleSize(availableSpace, 1 / scale);
|
||||
return child.Measure(scaledSpace).Type == SpacePlanType.FullRender;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class InternalLocation : ContainerElement, IStateResettable
|
||||
internal class Section : ContainerElement, IStateResettable
|
||||
{
|
||||
public string LocationName { get; set; }
|
||||
private bool IsRendered { get; set; }
|
||||
@@ -16,12 +16,11 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
if (!IsRendered)
|
||||
{
|
||||
PageContext.SetLocationPage(LocationName);
|
||||
|
||||
Canvas.DrawLocation(LocationName);
|
||||
Canvas.DrawSection(LocationName);
|
||||
IsRendered = true;
|
||||
}
|
||||
|
||||
PageContext.SetSectionPage(LocationName);
|
||||
base.Draw(availableSpace);
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,9 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class InternalLink : ContainerElement
|
||||
internal class SectionLink : ContainerElement
|
||||
{
|
||||
public string LocationName { get; set; }
|
||||
public string SectionName { get; set; }
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
@@ -14,7 +14,7 @@ namespace QuestPDF.Elements
|
||||
if (targetSize.Type == SpacePlanType.Wrap)
|
||||
return;
|
||||
|
||||
Canvas.DrawLocationLink(LocationName, targetSize);
|
||||
Canvas.DrawSectionLink(SectionName, targetSize);
|
||||
base.Draw(availableSpace);
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
using IComponent = QuestPDF.Infrastructure.IComponent;
|
||||
using IContainer = QuestPDF.Infrastructure.IContainer;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class BinaryStack : Element, IStateResettable, ICacheable
|
||||
{
|
||||
internal Element First { get; set; } = Empty.Instance;
|
||||
internal Element Second { get; set; } = Empty.Instance;
|
||||
|
||||
internal bool IsFirstRendered { get; set; } = false;
|
||||
|
||||
internal override IEnumerable<Element?> GetChildren()
|
||||
{
|
||||
yield return First;
|
||||
yield return Second;
|
||||
}
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
IsFirstRendered = false;
|
||||
}
|
||||
|
||||
internal override void CreateProxy(Func<Element?, Element?> create)
|
||||
{
|
||||
First = create(First);
|
||||
Second = create(Second);
|
||||
}
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
var firstElement = IsFirstRendered ? Empty.Instance : First;
|
||||
var firstSize = firstElement.Measure(availableSpace);
|
||||
|
||||
if (firstSize.Type == SpacePlanType.Wrap)
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
if (firstSize.Type == SpacePlanType.PartialRender)
|
||||
return firstSize;
|
||||
|
||||
var spaceForSecond = new Size(availableSpace.Width, availableSpace.Height - firstSize.Height);
|
||||
var secondSize = Second.Measure(spaceForSecond);
|
||||
|
||||
if (secondSize.Type == SpacePlanType.Wrap)
|
||||
return SpacePlan.PartialRender(firstSize);
|
||||
|
||||
var totalWidth = Math.Max(firstSize.Width, secondSize.Width);
|
||||
var totalHeight = firstSize.Height + secondSize.Height;
|
||||
var targetSize = new Size(totalWidth, totalHeight);
|
||||
|
||||
if (secondSize.Type == SpacePlanType.PartialRender)
|
||||
return SpacePlan.PartialRender(targetSize);
|
||||
|
||||
return SpacePlan.FullRender(targetSize);
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var firstElement = IsFirstRendered ? Empty.Instance : First;
|
||||
|
||||
var firstMeasurement = firstElement.Measure(availableSpace);
|
||||
|
||||
if (firstMeasurement.Type == SpacePlanType.FullRender)
|
||||
IsFirstRendered = true;
|
||||
|
||||
var firstSize = firstMeasurement;
|
||||
|
||||
if (firstSize.Type != SpacePlanType.Wrap)
|
||||
firstElement.Draw(new Size(availableSpace.Width, firstSize.Height));
|
||||
|
||||
if (firstMeasurement.Type == SpacePlanType.Wrap || firstMeasurement.Type == SpacePlanType.PartialRender)
|
||||
return;
|
||||
|
||||
var firstHeight = firstSize.Height;
|
||||
var spaceForSecond = new Size(availableSpace.Width, availableSpace.Height - firstHeight);
|
||||
var secondMeasurement = Second.Measure(spaceForSecond);
|
||||
|
||||
if (secondMeasurement.Type == SpacePlanType.Wrap)
|
||||
return;
|
||||
|
||||
Canvas.Translate(new Position(0, firstHeight));
|
||||
Second.Draw(new Size(availableSpace.Width, secondMeasurement.Height));
|
||||
Canvas.Translate(new Position(0, -firstHeight));
|
||||
|
||||
if (secondMeasurement.Type == SpacePlanType.FullRender)
|
||||
IsFirstRendered = false;
|
||||
}
|
||||
}
|
||||
|
||||
internal class Stack : IComponent
|
||||
{
|
||||
public ICollection<Element> Items { get; } = new List<Element>();
|
||||
public float Spacing { get; set; } = 0;
|
||||
|
||||
public void Compose(IContainer container)
|
||||
{
|
||||
var elements = AddSpacing(Spacing, Items);
|
||||
|
||||
container
|
||||
.PaddingBottom(-Spacing)
|
||||
.Element(BuildTree(elements.ToArray()));
|
||||
}
|
||||
|
||||
static ICollection<Element> AddSpacing(float spacing, ICollection<Element> elements)
|
||||
{
|
||||
if (spacing < Size.Epsilon)
|
||||
return elements;
|
||||
|
||||
return elements
|
||||
.Where(x => !(x is Empty))
|
||||
.Select(x => new Padding
|
||||
{
|
||||
Bottom = spacing,
|
||||
Child = x
|
||||
})
|
||||
.Cast<Element>()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
static Element BuildTree(Span<Element> elements)
|
||||
{
|
||||
if (elements.IsEmpty)
|
||||
return Empty.Instance;
|
||||
|
||||
if (elements.Length == 1)
|
||||
return elements[0];
|
||||
|
||||
var half = elements.Length / 2;
|
||||
|
||||
return new BinaryStack
|
||||
{
|
||||
First = BuildTree(elements.Slice(0, half)),
|
||||
Second = BuildTree(elements.Slice(half))
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class StopPaging : ContainerElement
|
||||
{
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
if (Child == null)
|
||||
return SpacePlan.FullRender(Size.Zero);
|
||||
|
||||
var measurement = Child.Measure(availableSpace);
|
||||
|
||||
return measurement.Type switch
|
||||
{
|
||||
SpacePlanType.Wrap => SpacePlan.FullRender(Size.Zero),
|
||||
SpacePlanType.PartialRender => SpacePlan.FullRender(measurement),
|
||||
SpacePlanType.FullRender => measurement,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,6 @@ namespace QuestPDF.Elements.Text.Calculation
|
||||
|
||||
public int StartIndex { get; set; }
|
||||
public float AvailableWidth { get; set; }
|
||||
public bool IsFirstLineElement { get; set; }
|
||||
public bool IsFirstElementInLine { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,6 @@ namespace QuestPDF.Elements.Text.Calculation
|
||||
public int NextIndex { get; set; }
|
||||
public int TotalIndex { get; set; }
|
||||
|
||||
public bool IsLast => EndIndex == TotalIndex;
|
||||
public bool IsLast => NextIndex == EndIndex;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -3,7 +3,7 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
internal class TextBlockExternalLink : TextBlockSpan
|
||||
internal class TextBlockHyperlink : TextBlockSpan
|
||||
{
|
||||
public string Url { get; set; }
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace QuestPDF.Elements.Text.Items
|
||||
public override void Draw(TextDrawingRequest request)
|
||||
{
|
||||
request.Canvas.Translate(new Position(0, request.TotalAscent));
|
||||
request.Canvas.DrawExternalLink(Url, new Size(request.TextSize.Width, request.TextSize.Height));
|
||||
request.Canvas.DrawHyperlink(Url, new Size(request.TextSize.Width, request.TextSize.Height));
|
||||
request.Canvas.Translate(new Position(0, -request.TotalAscent));
|
||||
|
||||
base.Draw(request);
|
||||
@@ -1,33 +1,29 @@
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
using System;
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
internal class TextBlockPageNumber : TextBlockSpan
|
||||
{
|
||||
public string SlotName { get; set; }
|
||||
public const string PageNumberPlaceholder = "123";
|
||||
public Func<IPageContext, string> Source { get; set; } = _ => PageNumberPlaceholder;
|
||||
|
||||
public override TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
{
|
||||
SetPageNumber(request.PageContext);
|
||||
UpdatePageNumberText(request.PageContext);
|
||||
return MeasureWithoutCache(request);
|
||||
}
|
||||
|
||||
public override void Draw(TextDrawingRequest request)
|
||||
{
|
||||
SetPageNumber(request.PageContext);
|
||||
UpdatePageNumberText(request.PageContext);
|
||||
base.Draw(request);
|
||||
}
|
||||
|
||||
private void SetPageNumber(IPageContext context)
|
||||
private void UpdatePageNumberText(IPageContext context)
|
||||
{
|
||||
var pageNumberPlaceholder = 123;
|
||||
|
||||
var pageNumber = context.GetRegisteredLocations().Contains(SlotName)
|
||||
? context.GetLocationPage(SlotName)
|
||||
: pageNumberPlaceholder;
|
||||
|
||||
Text = pageNumber.ToString();
|
||||
Text = Source(context) ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -3,9 +3,9 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
internal class TextBlockInternalLink : TextBlockSpan
|
||||
internal class TextBlockSectionlLink : TextBlockSpan
|
||||
{
|
||||
public string LocationName { get; set; }
|
||||
public string SectionName { get; set; }
|
||||
|
||||
public override TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
{
|
||||
@@ -15,7 +15,7 @@ namespace QuestPDF.Elements.Text.Items
|
||||
public override void Draw(TextDrawingRequest request)
|
||||
{
|
||||
request.Canvas.Translate(new Position(0, request.TotalAscent));
|
||||
request.Canvas.DrawLocationLink(LocationName, new Size(request.TextSize.Width, request.TextSize.Height));
|
||||
request.Canvas.DrawSectionLink(SectionName, new Size(request.TextSize.Width, request.TextSize.Height));
|
||||
request.Canvas.Translate(new Position(0, -request.TotalAscent));
|
||||
|
||||
base.Draw(request);
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
using QuestPDF.Infrastructure;
|
||||
@@ -11,13 +12,12 @@ namespace QuestPDF.Elements.Text.Items
|
||||
public string Text { get; set; }
|
||||
public TextStyle Style { get; set; } = new TextStyle();
|
||||
|
||||
private Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?> MeasureCache =
|
||||
new Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?>();
|
||||
private Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?> MeasureCache = new ();
|
||||
|
||||
public virtual TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
{
|
||||
var cacheKey = (request.StartIndex, request.AvailableWidth);
|
||||
|
||||
|
||||
if (!MeasureCache.ContainsKey(cacheKey))
|
||||
MeasureCache[cacheKey] = MeasureWithoutCache(request);
|
||||
|
||||
@@ -33,13 +33,15 @@ namespace QuestPDF.Elements.Text.Items
|
||||
|
||||
var startIndex = request.StartIndex;
|
||||
|
||||
if (request.IsFirstLineElement)
|
||||
// if the element is the first one within the line,
|
||||
// ignore leading spaces
|
||||
if (request.IsFirstElementInLine)
|
||||
{
|
||||
while (startIndex + 1 < Text.Length && Text[startIndex] == space)
|
||||
while (startIndex < Text.Length && Text[startIndex] == space)
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
if (Text.Length == 0)
|
||||
|
||||
if (Text.Length == 0 || startIndex == Text.Length)
|
||||
{
|
||||
return new TextMeasurementResult
|
||||
{
|
||||
@@ -52,38 +54,28 @@ namespace QuestPDF.Elements.Text.Items
|
||||
}
|
||||
|
||||
// start breaking text from requested position
|
||||
var text = Text.Substring(startIndex);
|
||||
var text = Text.AsSpan().Slice(startIndex);
|
||||
|
||||
var textLength = (int)paint.BreakText(text, request.AvailableWidth);
|
||||
var textLength = (int)paint.BreakText(text, request.AvailableWidth + Size.Epsilon);
|
||||
|
||||
if (textLength <= 0)
|
||||
return null;
|
||||
|
||||
if (textLength < text.Length && text[textLength] == space)
|
||||
textLength++;
|
||||
|
||||
|
||||
// break text only on spaces
|
||||
if (textLength < text.Length)
|
||||
{
|
||||
var lastSpaceIndex = text.Substring(0, textLength).LastIndexOf(space) - 1;
|
||||
var wrappedTextLength = WrapText(text, textLength, request.IsFirstElementInLine);
|
||||
|
||||
if (lastSpaceIndex <= 0)
|
||||
{
|
||||
if (!request.IsFirstLineElement)
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
textLength = lastSpaceIndex + 1;
|
||||
}
|
||||
}
|
||||
if (wrappedTextLength == null)
|
||||
return null;
|
||||
|
||||
text = text.Substring(0, textLength);
|
||||
textLength = wrappedTextLength.Value;
|
||||
|
||||
text = text.Slice(0, textLength);
|
||||
|
||||
var endIndex = startIndex + textLength;
|
||||
var nextIndex = endIndex;
|
||||
|
||||
while (nextIndex + 1 < Text.Length && Text[nextIndex] == space)
|
||||
// when breaking text, omit spaces at the end of the line
|
||||
while (nextIndex < Text.Length && Text[nextIndex] == space)
|
||||
nextIndex++;
|
||||
|
||||
// measure final text
|
||||
@@ -103,6 +95,31 @@ namespace QuestPDF.Elements.Text.Items
|
||||
NextIndex = nextIndex,
|
||||
TotalIndex = Text.Length
|
||||
};
|
||||
|
||||
static int? WrapText(ReadOnlySpan<char> text, int textLength, bool isFirstElementInLine)
|
||||
{
|
||||
// textLength - length of the part of the text that fits in available width (creating a line)
|
||||
|
||||
// entire text fits, no need to wrap
|
||||
if (textLength == text.Length)
|
||||
return textLength;
|
||||
|
||||
// current line ends at word, next character is space, perfect place to wrap
|
||||
if (text[textLength - 1] != space && text[textLength] == space)
|
||||
return textLength;
|
||||
|
||||
// find last space within the available text to wrap
|
||||
var lastSpaceIndex = text.Slice(0, textLength).LastIndexOf(space);
|
||||
|
||||
// text contains space that can be used to wrap
|
||||
if (lastSpaceIndex > 0)
|
||||
return lastSpaceIndex;
|
||||
|
||||
// there is no available space to wrap text
|
||||
// if the item is first within the line, perform safe mode and chop the word
|
||||
// otherwise, move the item into the next line
|
||||
return isFirstElementInLine ? textLength : null;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Draw(TextDrawingRequest request)
|
||||
|
||||
@@ -170,7 +170,7 @@ namespace QuestPDF.Elements.Text
|
||||
|
||||
StartIndex = currentItemIndex,
|
||||
AvailableWidth = availableWidth - currentWidth,
|
||||
IsFirstLineElement = !currentLineElements.Any()
|
||||
IsFirstElementInLine = !currentLineElements.Any()
|
||||
};
|
||||
|
||||
var measurementResponse = currentElement.Measure(measurementRequest);
|
||||
|
||||
@@ -14,39 +14,45 @@ namespace QuestPDF.Fluent
|
||||
return element.Element(border);
|
||||
}
|
||||
|
||||
public static IContainer Border(this IContainer element, float value)
|
||||
public static IContainer Border(this IContainer element, float value, Unit unit = Unit.Point)
|
||||
{
|
||||
return element.BorderHorizontal(value).BorderVertical(value);
|
||||
return element
|
||||
.BorderHorizontal(value, unit)
|
||||
.BorderVertical(value, unit);
|
||||
}
|
||||
|
||||
public static IContainer BorderVertical(this IContainer element, float value)
|
||||
public static IContainer BorderVertical(this IContainer element, float value, Unit unit = Unit.Point)
|
||||
{
|
||||
return element.BorderLeft(value).BorderRight(value);
|
||||
return element
|
||||
.BorderLeft(value, unit)
|
||||
.BorderRight(value, unit);
|
||||
}
|
||||
|
||||
public static IContainer BorderHorizontal(this IContainer element, float value)
|
||||
public static IContainer BorderHorizontal(this IContainer element, float value, Unit unit = Unit.Point)
|
||||
{
|
||||
return element.BorderTop(value).BorderBottom(value);
|
||||
return element
|
||||
.BorderTop(value, unit)
|
||||
.BorderBottom(value, unit);
|
||||
}
|
||||
|
||||
public static IContainer BorderLeft(this IContainer element, float value)
|
||||
public static IContainer BorderLeft(this IContainer element, float value, Unit unit = Unit.Point)
|
||||
{
|
||||
return element.Border(x => x.Left = value);
|
||||
return element.Border(x => x.Left = value.ToPoints(unit));
|
||||
}
|
||||
|
||||
public static IContainer BorderRight(this IContainer element, float value)
|
||||
public static IContainer BorderRight(this IContainer element, float value, Unit unit = Unit.Point)
|
||||
{
|
||||
return element.Border(x => x.Right = value);
|
||||
return element.Border(x => x.Right = value.ToPoints(unit));
|
||||
}
|
||||
|
||||
public static IContainer BorderTop(this IContainer element, float value)
|
||||
public static IContainer BorderTop(this IContainer element, float value, Unit unit = Unit.Point)
|
||||
{
|
||||
return element.Border(x => x.Top = value);
|
||||
return element.Border(x => x.Top = value.ToPoints(unit));
|
||||
}
|
||||
|
||||
public static IContainer BorderBottom(this IContainer element, float value)
|
||||
public static IContainer BorderBottom(this IContainer element, float value, Unit unit = Unit.Point)
|
||||
{
|
||||
return element.Border(x => x.Bottom = value);
|
||||
return element.Border(x => x.Bottom = value.ToPoints(unit));
|
||||
}
|
||||
|
||||
public static IContainer BorderColor(this IContainer element, string color)
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public class ColumnDescriptor
|
||||
{
|
||||
internal Column Column { get; } = new();
|
||||
|
||||
public void Spacing(float value, Unit unit = Unit.Point)
|
||||
{
|
||||
Column.Spacing = value.ToPoints(unit);
|
||||
}
|
||||
|
||||
public IContainer Item()
|
||||
{
|
||||
var container = new Container();
|
||||
|
||||
Column.Items.Add(new ColumnItem
|
||||
{
|
||||
Child = container
|
||||
});
|
||||
|
||||
return container;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ColumnExtensions
|
||||
{
|
||||
[Obsolete("This element has been renamed since version 2022.2. Please use the 'Column' method.")]
|
||||
public static void Stack(this IContainer element, Action<ColumnDescriptor> handler)
|
||||
{
|
||||
element.Column(handler);
|
||||
}
|
||||
|
||||
public static void Column(this IContainer element, Action<ColumnDescriptor> handler)
|
||||
{
|
||||
var descriptor = new ColumnDescriptor();
|
||||
handler(descriptor);
|
||||
element.Element(descriptor.Column);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,34 +14,38 @@ namespace QuestPDF.Fluent
|
||||
return element.Element(constrained);
|
||||
}
|
||||
|
||||
public static IContainer Width(this IContainer element, float value)
|
||||
public static IContainer Width(this IContainer element, float value, Unit unit = Unit.Point)
|
||||
{
|
||||
return element.MinWidth(value).MaxWidth(value);
|
||||
return element
|
||||
.MinWidth(value, unit)
|
||||
.MaxWidth(value, unit);
|
||||
}
|
||||
|
||||
public static IContainer MinWidth(this IContainer element, float value)
|
||||
public static IContainer MinWidth(this IContainer element, float value, Unit unit = Unit.Point)
|
||||
{
|
||||
return element.Constrained(x => x.MinWidth = value);
|
||||
return element.Constrained(x => x.MinWidth = value.ToPoints(unit));
|
||||
}
|
||||
|
||||
public static IContainer MaxWidth(this IContainer element, float value)
|
||||
public static IContainer MaxWidth(this IContainer element, float value, Unit unit = Unit.Point)
|
||||
{
|
||||
return element.Constrained(x => x.MaxWidth = value);
|
||||
return element.Constrained(x => x.MaxWidth = value.ToPoints(unit));
|
||||
}
|
||||
|
||||
public static IContainer Height(this IContainer element, float value)
|
||||
public static IContainer Height(this IContainer element, float value, Unit unit = Unit.Point)
|
||||
{
|
||||
return element.MinHeight(value).MaxHeight(value);
|
||||
return element
|
||||
.MinHeight(value, unit)
|
||||
.MaxHeight(value, unit);
|
||||
}
|
||||
|
||||
public static IContainer MinHeight(this IContainer element, float value)
|
||||
public static IContainer MinHeight(this IContainer element, float value, Unit unit = Unit.Point)
|
||||
{
|
||||
return element.Constrained(x => x.MinHeight = value);
|
||||
return element.Constrained(x => x.MinHeight = value.ToPoints(unit));
|
||||
}
|
||||
|
||||
public static IContainer MaxHeight(this IContainer element, float value)
|
||||
public static IContainer MaxHeight(this IContainer element, float value, Unit unit = Unit.Point)
|
||||
{
|
||||
return element.Constrained(x => x.MaxHeight = value);
|
||||
return element.Constrained(x => x.MaxHeight = value.ToPoints(unit));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,16 +8,16 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
internal Decoration Decoration { get; } = new Decoration();
|
||||
|
||||
public IContainer Header()
|
||||
public IContainer Before()
|
||||
{
|
||||
var container = new Container();
|
||||
Decoration.Header = container;
|
||||
Decoration.Before = container;
|
||||
return container;
|
||||
}
|
||||
|
||||
public void Header(Action<IContainer> handler)
|
||||
public void Before(Action<IContainer> handler)
|
||||
{
|
||||
handler?.Invoke(Header());
|
||||
handler?.Invoke(Before());
|
||||
}
|
||||
|
||||
public IContainer Content()
|
||||
@@ -32,17 +32,49 @@ namespace QuestPDF.Fluent
|
||||
handler?.Invoke(Content());
|
||||
}
|
||||
|
||||
public IContainer Footer()
|
||||
public IContainer After()
|
||||
{
|
||||
var container = new Container();
|
||||
Decoration.Footer = container;
|
||||
Decoration.After = container;
|
||||
return container;
|
||||
}
|
||||
|
||||
public void After(Action<IContainer> handler)
|
||||
{
|
||||
handler?.Invoke(After());
|
||||
}
|
||||
|
||||
#region Obsolete
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.2. Please use the 'Before' method.")]
|
||||
public IContainer Header()
|
||||
{
|
||||
var container = new Container();
|
||||
Decoration.Before = container;
|
||||
return container;
|
||||
}
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.2. Please use the 'Before' method.")]
|
||||
public void Header(Action<IContainer> handler)
|
||||
{
|
||||
handler?.Invoke(Header());
|
||||
}
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.2. Please use the 'After' method.")]
|
||||
public IContainer Footer()
|
||||
{
|
||||
var container = new Container();
|
||||
Decoration.After = container;
|
||||
return container;
|
||||
}
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.2. Please use the 'After' method.")]
|
||||
public void Footer(Action<IContainer> handler)
|
||||
{
|
||||
handler?.Invoke(Footer());
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public static class DecorationExtensions
|
||||
@@ -52,7 +84,7 @@ namespace QuestPDF.Fluent
|
||||
var descriptor = new DecorationDescriptor();
|
||||
handler(descriptor);
|
||||
|
||||
element.Component(descriptor.Decoration);
|
||||
element.Element(descriptor.Decoration);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@ namespace QuestPDF.Fluent
|
||||
|
||||
public static void Placeholder(this IContainer element, string? text = null)
|
||||
{
|
||||
element.Component(new Elements.Placeholder
|
||||
element.Component(new Placeholder
|
||||
{
|
||||
Text = text ?? string.Empty
|
||||
});
|
||||
@@ -99,27 +99,45 @@ namespace QuestPDF.Fluent
|
||||
return element.Element(new Container());
|
||||
}
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.3. Please use the Hyperlink method.")]
|
||||
public static IContainer ExternalLink(this IContainer element, string url)
|
||||
{
|
||||
return element.Element(new ExternalLink
|
||||
return element.Hyperlink(url);
|
||||
}
|
||||
|
||||
public static IContainer Hyperlink(this IContainer element, string url)
|
||||
{
|
||||
return element.Element(new Hyperlink
|
||||
{
|
||||
Url = url
|
||||
});
|
||||
}
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.3. Please use the Section method.")]
|
||||
public static IContainer Location(this IContainer element, string locationName)
|
||||
{
|
||||
return element.Element(new InternalLocation
|
||||
return element.Section(locationName);
|
||||
}
|
||||
|
||||
public static IContainer Section(this IContainer element, string sectionName)
|
||||
{
|
||||
return element.Element(new Section
|
||||
{
|
||||
LocationName = locationName
|
||||
LocationName = sectionName
|
||||
});
|
||||
}
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.3. Please use the SectionLink method.")]
|
||||
public static IContainer InternalLink(this IContainer element, string locationName)
|
||||
{
|
||||
return element.Element(new InternalLink
|
||||
return element.SectionLink(locationName);
|
||||
}
|
||||
|
||||
public static IContainer SectionLink(this IContainer element, string sectionName)
|
||||
{
|
||||
return element.Element(new SectionLink
|
||||
{
|
||||
LocationName = locationName
|
||||
SectionName = sectionName
|
||||
});
|
||||
}
|
||||
|
||||
@@ -136,8 +154,7 @@ namespace QuestPDF.Fluent
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: deprecated Box method in QuestPDF 2022.1
|
||||
[Obsolete("This element has been renamed. Please use the MinimalBox method.")]
|
||||
[Obsolete("This element has been renamed since version 2022.1. Please use the MinimalBox method.")]
|
||||
public static IContainer Box(this IContainer element)
|
||||
{
|
||||
return element.Element(new MinimalBox());
|
||||
@@ -160,5 +177,23 @@ namespace QuestPDF.Fluent
|
||||
TextStyle = textStyle
|
||||
});
|
||||
}
|
||||
|
||||
public static IContainer DefaultTextStyle(this IContainer element, Func<TextStyle, TextStyle> handler)
|
||||
{
|
||||
return element.Element(new DefaultTextStyle
|
||||
{
|
||||
TextStyle = handler(TextStyle.Default)
|
||||
});
|
||||
}
|
||||
|
||||
public static IContainer StopPaging(this IContainer element)
|
||||
{
|
||||
return element.Element(new StopPaging());
|
||||
}
|
||||
|
||||
public static IContainer ScaleToFit(this IContainer element)
|
||||
{
|
||||
return element.Element(new ScaleToFit());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ namespace QuestPDF.Fluent
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region XPS
|
||||
|
||||
public static byte[] GenerateXps(this IDocument document)
|
||||
{
|
||||
@@ -48,8 +50,6 @@ namespace QuestPDF.Fluent
|
||||
DocumentGenerator.GenerateXps(stream, document);
|
||||
}
|
||||
|
||||
#region XPS
|
||||
|
||||
#endregion
|
||||
|
||||
#region Images
|
||||
|
||||
@@ -8,20 +8,20 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
internal Grid Grid { get; } = new Grid();
|
||||
|
||||
public void Spacing(float value)
|
||||
public void Spacing(float value, Unit unit = Unit.Point)
|
||||
{
|
||||
VerticalSpacing(value);
|
||||
HorizontalSpacing(value);
|
||||
VerticalSpacing(value, unit);
|
||||
HorizontalSpacing(value, unit);
|
||||
}
|
||||
|
||||
public void VerticalSpacing(float value)
|
||||
public void VerticalSpacing(float value, Unit unit = Unit.Point)
|
||||
{
|
||||
Grid.VerticalSpacing = value;
|
||||
Grid.VerticalSpacing = value.ToPoints(unit);
|
||||
}
|
||||
|
||||
public void HorizontalSpacing(float value)
|
||||
public void HorizontalSpacing(float value, Unit unit = Unit.Point)
|
||||
{
|
||||
Grid.HorizontalSpacing = value;
|
||||
Grid.HorizontalSpacing = value.ToPoints(unit);
|
||||
}
|
||||
|
||||
public void Columns(int value = Grid.DefaultColumnsCount)
|
||||
|
||||
@@ -10,14 +10,21 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
internal Inlined Inlined { get; } = new Inlined();
|
||||
|
||||
public void Spacing(float value)
|
||||
public void Spacing(float value, Unit unit = Unit.Point)
|
||||
{
|
||||
VerticalSpacing(value);
|
||||
HorizontalSpacing(value);
|
||||
VerticalSpacing(value, unit);
|
||||
HorizontalSpacing(value, unit);
|
||||
}
|
||||
|
||||
public void VerticalSpacing(float value) => Inlined.VerticalSpacing = value;
|
||||
public void HorizontalSpacing(float value) => Inlined.HorizontalSpacing = value;
|
||||
public void VerticalSpacing(float value, Unit unit = Unit.Point)
|
||||
{
|
||||
Inlined.VerticalSpacing = value.ToPoints(unit);
|
||||
}
|
||||
|
||||
public void HorizontalSpacing(float value, Unit unit = Unit.Point)
|
||||
{
|
||||
Inlined.HorizontalSpacing = value.ToPoints(unit);
|
||||
}
|
||||
|
||||
public void BaselineTop() => Inlined.BaselineAlignment = VerticalAlignment.Top;
|
||||
public void BaselineMiddle() => Inlined.BaselineAlignment = VerticalAlignment.Middle;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public static class LineExtensions
|
||||
{
|
||||
private static ILine Line(this IContainer element, LineType type, float size)
|
||||
{
|
||||
var line = new Line
|
||||
{
|
||||
Size = size,
|
||||
Type = type
|
||||
};
|
||||
|
||||
element.Element(line);
|
||||
return line;
|
||||
}
|
||||
|
||||
public static ILine LineVertical(this IContainer element, float size, Unit unit = Unit.Point)
|
||||
{
|
||||
return element.Line(LineType.Vertical, size.ToPoints(unit));
|
||||
}
|
||||
|
||||
public static ILine LineHorizontal(this IContainer element, float size, Unit unit = Unit.Point)
|
||||
{
|
||||
return element.Line(LineType.Horizontal, size.ToPoints(unit));
|
||||
}
|
||||
|
||||
public static void LineColor(this ILine descriptor, string value)
|
||||
{
|
||||
(descriptor as Line).Color = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public class Document : IDocument
|
||||
{
|
||||
private Action<IDocumentContainer> ContentSource { get; }
|
||||
private DocumentMetadata Metadata { get; set; } = DocumentMetadata.Default;
|
||||
|
||||
private Document(Action<IDocumentContainer> contentSource)
|
||||
{
|
||||
ContentSource = contentSource;
|
||||
}
|
||||
|
||||
public static Document Create(Action<IDocumentContainer> handler)
|
||||
{
|
||||
return new Document(handler);
|
||||
}
|
||||
|
||||
public Document WithMetadata(DocumentMetadata metadata)
|
||||
{
|
||||
Metadata = metadata ?? Metadata;
|
||||
return this;
|
||||
}
|
||||
|
||||
#region IDocument
|
||||
|
||||
public DocumentMetadata GetMetadata() => Metadata;
|
||||
public void Compose(IDocumentContainer container) => ContentSource(container);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -14,39 +14,45 @@ namespace QuestPDF.Fluent
|
||||
return element.Element(padding);
|
||||
}
|
||||
|
||||
public static IContainer Padding(this IContainer element, float value)
|
||||
public static IContainer Padding(this IContainer element, float value, Unit unit = Unit.Point)
|
||||
{
|
||||
return element.PaddingVertical(value).PaddingHorizontal(value);
|
||||
return element
|
||||
.PaddingVertical(value, unit)
|
||||
.PaddingHorizontal(value, unit);
|
||||
}
|
||||
|
||||
public static IContainer PaddingHorizontal(this IContainer element, float value)
|
||||
public static IContainer PaddingHorizontal(this IContainer element, float value, Unit unit = Unit.Point)
|
||||
{
|
||||
return element.PaddingLeft(value).PaddingRight(value);
|
||||
return element
|
||||
.PaddingLeft(value, unit)
|
||||
.PaddingRight(value, unit);
|
||||
}
|
||||
|
||||
public static IContainer PaddingVertical(this IContainer element, float value)
|
||||
public static IContainer PaddingVertical(this IContainer element, float value, Unit unit = Unit.Point)
|
||||
{
|
||||
return element.PaddingTop(value).PaddingBottom(value);
|
||||
return element
|
||||
.PaddingTop(value, unit)
|
||||
.PaddingBottom(value, unit);
|
||||
}
|
||||
|
||||
public static IContainer PaddingTop(this IContainer element, float value)
|
||||
public static IContainer PaddingTop(this IContainer element, float value, Unit unit = Unit.Point)
|
||||
{
|
||||
return element.Padding(x => x.Top = value);
|
||||
return element.Padding(x => x.Top += value.ToPoints(unit));
|
||||
}
|
||||
|
||||
public static IContainer PaddingBottom(this IContainer element, float value)
|
||||
public static IContainer PaddingBottom(this IContainer element, float value, Unit unit = Unit.Point)
|
||||
{
|
||||
return element.Padding(x => x.Bottom = value);
|
||||
return element.Padding(x => x.Bottom += value.ToPoints(unit));
|
||||
}
|
||||
|
||||
public static IContainer PaddingLeft(this IContainer element, float value)
|
||||
public static IContainer PaddingLeft(this IContainer element, float value, Unit unit = Unit.Point)
|
||||
{
|
||||
return element.Padding(x => x.Left = value);
|
||||
return element.Padding(x => x.Left += value.ToPoints(unit));
|
||||
}
|
||||
|
||||
public static IContainer PaddingRight(this IContainer element, float value)
|
||||
public static IContainer PaddingRight(this IContainer element, float value, Unit unit = Unit.Point)
|
||||
{
|
||||
return element.Padding(x => x.Right = value);
|
||||
return element.Padding(x => x.Right += value.ToPoints(unit));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,16 +10,24 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
internal Page Page { get; } = new Page();
|
||||
|
||||
public void Size(float width, float height, Unit unit = Unit.Inch)
|
||||
{
|
||||
var pageSize = new PageSize(width, height, unit);
|
||||
|
||||
MinSize(pageSize);
|
||||
MaxSize(pageSize);
|
||||
}
|
||||
|
||||
public void Size(PageSize pageSize)
|
||||
{
|
||||
MinSize(pageSize);
|
||||
MaxSize(pageSize);
|
||||
}
|
||||
|
||||
public void ContinuousSize(float width)
|
||||
public void ContinuousSize(float width, Unit unit = Unit.Point)
|
||||
{
|
||||
MinSize(new PageSize(width, 0));
|
||||
MaxSize(new PageSize(width, Infrastructure.Size.Max.Height));
|
||||
MinSize(new PageSize(width.ToPoints(unit), 0));
|
||||
MaxSize(new PageSize(width.ToPoints(unit), Infrastructure.Size.Max.Height));
|
||||
}
|
||||
|
||||
public void MinSize(PageSize pageSize)
|
||||
@@ -32,42 +40,42 @@ namespace QuestPDF.Fluent
|
||||
Page.MaxSize = pageSize;
|
||||
}
|
||||
|
||||
public void MarginLeft(float value)
|
||||
public void MarginLeft(float value, Unit unit = Unit.Point)
|
||||
{
|
||||
Page.MarginLeft = value;
|
||||
Page.MarginLeft = value.ToPoints(unit);
|
||||
}
|
||||
|
||||
public void MarginRight(float value)
|
||||
public void MarginRight(float value, Unit unit = Unit.Point)
|
||||
{
|
||||
Page.MarginRight = value;
|
||||
Page.MarginRight = value.ToPoints(unit);
|
||||
}
|
||||
|
||||
public void MarginTop(float value)
|
||||
public void MarginTop(float value, Unit unit = Unit.Point)
|
||||
{
|
||||
Page.MarginTop = value;
|
||||
Page.MarginTop = value.ToPoints(unit);
|
||||
}
|
||||
|
||||
public void MarginBottom(float value)
|
||||
public void MarginBottom(float value, Unit unit = Unit.Point)
|
||||
{
|
||||
Page.MarginBottom = value;
|
||||
Page.MarginBottom = value.ToPoints(unit);
|
||||
}
|
||||
|
||||
public void MarginVertical(float value)
|
||||
public void MarginVertical(float value, Unit unit = Unit.Point)
|
||||
{
|
||||
MarginTop(value);
|
||||
MarginBottom(value);
|
||||
MarginTop(value, unit);
|
||||
MarginBottom(value, unit);
|
||||
}
|
||||
|
||||
public void MarginHorizontal(float value)
|
||||
public void MarginHorizontal(float value, Unit unit = Unit.Point)
|
||||
{
|
||||
MarginLeft(value);
|
||||
MarginRight(value);
|
||||
MarginLeft(value, unit);
|
||||
MarginRight(value, unit);
|
||||
}
|
||||
|
||||
public void Margin(float value)
|
||||
public void Margin(float value, Unit unit = Unit.Point)
|
||||
{
|
||||
MarginVertical(value);
|
||||
MarginHorizontal(value);
|
||||
MarginVertical(value, unit);
|
||||
MarginHorizontal(value, unit);
|
||||
}
|
||||
|
||||
public void DefaultTextStyle(TextStyle textStyle)
|
||||
@@ -75,11 +83,36 @@ namespace QuestPDF.Fluent
|
||||
Page.DefaultTextStyle = textStyle;
|
||||
}
|
||||
|
||||
public void Background(string color)
|
||||
public void DefaultTextStyle(Func<TextStyle, TextStyle> handler)
|
||||
{
|
||||
DefaultTextStyle(handler(TextStyle.Default));
|
||||
}
|
||||
|
||||
public void PageColor(string color)
|
||||
{
|
||||
Page.BackgroundColor = color;
|
||||
}
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.3. Please use the PageColor method.")]
|
||||
public void Background(string color)
|
||||
{
|
||||
PageColor(color);
|
||||
}
|
||||
|
||||
public IContainer Background()
|
||||
{
|
||||
var container = new Container();
|
||||
Page.Background = container;
|
||||
return container;
|
||||
}
|
||||
|
||||
public IContainer Foreground()
|
||||
{
|
||||
var container = new Container();
|
||||
Page.Foreground = container;
|
||||
return container;
|
||||
}
|
||||
|
||||
public IContainer Header()
|
||||
{
|
||||
var container = new Container();
|
||||
|
||||
@@ -6,30 +6,51 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
public class RowDescriptor
|
||||
{
|
||||
internal Row Row { get; } = new Row();
|
||||
internal Row Row { get; } = new();
|
||||
|
||||
public void Spacing(float value)
|
||||
{
|
||||
Row.Spacing = value;
|
||||
}
|
||||
|
||||
public IContainer ConstantColumn(float width)
|
||||
|
||||
private IContainer Item(RowItemType type, float size = 0)
|
||||
{
|
||||
return Column(constantWidth: width);
|
||||
}
|
||||
|
||||
public IContainer RelativeColumn(float width = 1)
|
||||
{
|
||||
return Column(relativeWidth: width);
|
||||
}
|
||||
|
||||
private IContainer Column(float constantWidth = 0, float relativeWidth = 0)
|
||||
{
|
||||
var element = new RowElement(constantWidth, relativeWidth);
|
||||
var element = new RowItem
|
||||
{
|
||||
Type = type,
|
||||
Size = size
|
||||
};
|
||||
|
||||
Row.Items.Add(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.2. Please use the RelativeItem method.")]
|
||||
public IContainer RelativeColumn(float size = 1)
|
||||
{
|
||||
return Item(RowItemType.Relative, size);
|
||||
}
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.2. Please use the ConstantItem method.")]
|
||||
public IContainer ConstantColumn(float size)
|
||||
{
|
||||
return Item(RowItemType.Constant, size);
|
||||
}
|
||||
|
||||
public IContainer RelativeItem(float size = 1)
|
||||
{
|
||||
return Item(RowItemType.Relative, size);
|
||||
}
|
||||
|
||||
public IContainer ConstantItem(float size, Unit unit = Unit.Point)
|
||||
{
|
||||
return Item(RowItemType.Constant, size.ToPoints(unit));
|
||||
}
|
||||
|
||||
public IContainer AutoItem()
|
||||
{
|
||||
return Item(RowItemType.Auto);
|
||||
}
|
||||
}
|
||||
|
||||
public static class RowExtensions
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace QuestPDF.Fluent
|
||||
|
||||
public static IContainer ScaleHorizontal(this IContainer element, float value)
|
||||
{
|
||||
return element.Scale(x => x.ScaleX = value);
|
||||
return element.Scale(x => x.ScaleX *= value);
|
||||
}
|
||||
|
||||
public static IContainer FlipHorizontal(this IContainer element)
|
||||
@@ -31,7 +31,7 @@ namespace QuestPDF.Fluent
|
||||
|
||||
public static IContainer ScaleVertical(this IContainer element, float value)
|
||||
{
|
||||
return element.Scale(x => x.ScaleY = value);
|
||||
return element.Scale(x => x.ScaleY *= value);
|
||||
}
|
||||
|
||||
public static IContainer FlipVertical(this IContainer element)
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
using System;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public class StackDescriptor
|
||||
{
|
||||
internal Stack Stack { get; } = new Stack();
|
||||
|
||||
public void Spacing(float value)
|
||||
{
|
||||
Stack.Spacing = value;
|
||||
}
|
||||
|
||||
public IContainer Item()
|
||||
{
|
||||
var container = new Container();
|
||||
Stack.Items.Add(container);
|
||||
return container;
|
||||
}
|
||||
}
|
||||
|
||||
public static class StackExtensions
|
||||
{
|
||||
public static void Stack(this IContainer element, Action<StackDescriptor> handler)
|
||||
{
|
||||
var descriptor = new StackDescriptor();
|
||||
handler(descriptor);
|
||||
element.Component(descriptor.Stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user