Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2772b5b4a4 | |||
| 3dfe7a59e0 | |||
| df5c5524ee | |||
| 5975b19445 | |||
| fb7743e828 | |||
| 3bb46ecc42 | |||
| 3112ee10cf | |||
| 899c138842 | |||
| 5177dcfd2b | |||
| 4e6415aee2 | |||
| d9b3f016e8 | |||
| b9cfaf3846 | |||
| e3e7a22af8 | |||
| c938ea32b0 | |||
| 69419c8440 | |||
| 23e8aa3491 | |||
| fd5d51e764 | |||
| dbc479db07 | |||
| edc595d199 | |||
| 7f94ae589a | |||
| adfce6204e | |||
| 6d3c8d0708 | |||
| 3b8c300126 | |||
| 239a042615 | |||
| 49fb6dadb3 | |||
| c79a4a0c08 | |||
| 31191efd88 | |||
| 20fe49dd01 | |||
| 7cdc1fcee2 | |||
| 14f11232be | |||
| c05c5e1597 | |||
| 07f1eb99b9 | |||
| 44d7538578 | |||
| b894262f7e | |||
| 4423774648 | |||
| 5d4f1f530a | |||
| 3ba8185871 | |||
| 78e5cbd098 | |||
| f792f6c749 | |||
| 0d86039c0c |
@@ -0,0 +1,32 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
[TestFixture]
|
||||
public class BarcodeExamples
|
||||
{
|
||||
[Test]
|
||||
public void Barcode()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 300)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(25)
|
||||
.Stack(stack =>
|
||||
{
|
||||
stack.Item().Border(1).Background(Colors.Grey.Lighten3).Padding(5).Text("Barcode Example");
|
||||
stack.Item().Border(1).Padding(5).AlignCenter().Text("*123456789*", TextStyle.Default.FontType("CarolinaBar-Demo-25E2").Size(20));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,226 +1,781 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class ElementExamples : ExampleTestBase
|
||||
[TestFixture]
|
||||
public class ElementExamples
|
||||
{
|
||||
//[ShowResult]
|
||||
[ImageSize(200, 150)]
|
||||
public void Placeholder(IContainer container)
|
||||
[Test]
|
||||
public void Placeholder()
|
||||
{
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(25)
|
||||
.Placeholder();
|
||||
}
|
||||
|
||||
//[ShowResult]
|
||||
[ImageSize(300, 300)]
|
||||
public void Section(IContainer container)
|
||||
{
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(25)
|
||||
.Decoration(section =>
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(200, 150)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
section
|
||||
.Header()
|
||||
.Background("#888")
|
||||
.Padding(10)
|
||||
.Text("Notes", TextStyle.Default.Size(16).Color("#FFF"));
|
||||
|
||||
section
|
||||
.Content()
|
||||
.Background("#DDD")
|
||||
.Padding(10)
|
||||
.ExtendVertical()
|
||||
.Text(Helpers.Placeholders.LoremIpsum());
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(25)
|
||||
.Placeholder();
|
||||
});
|
||||
}
|
||||
|
||||
//[ShowResult]
|
||||
[ImageSize(298, 421)]
|
||||
public void Page(IContainer container)
|
||||
[Test]
|
||||
public void Decoration()
|
||||
{
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(15)
|
||||
.Page(page =>
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 300)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
page.Header()
|
||||
.Height(60)
|
||||
.Background("#BBB")
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.Text("Header");
|
||||
|
||||
page.Content()
|
||||
.Background("#DDD")
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.Text("Content");
|
||||
|
||||
page.Footer()
|
||||
.Height(30)
|
||||
.Background("#BBB")
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.Text("Footer");
|
||||
});
|
||||
}
|
||||
|
||||
//[ShowResult]
|
||||
[ImageSize(740, 200)]
|
||||
public void Row(IContainer container)
|
||||
{
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(20)
|
||||
.Stack(stack =>
|
||||
{
|
||||
stack.Item()
|
||||
.PaddingBottom(10)
|
||||
.AlignCenter()
|
||||
.Text("This Row element is 700pt wide");
|
||||
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.ConstantColumn(100)
|
||||
.Background("#DDD")
|
||||
.Padding(10)
|
||||
.ExtendVertical()
|
||||
.Text("This column is 100 pt wide");
|
||||
|
||||
row.RelativeColumn()
|
||||
.Background("#BBB")
|
||||
.Padding(10)
|
||||
.Text("This column takes 1/3 of the available space (200pt)");
|
||||
|
||||
row.RelativeColumn(2)
|
||||
.Background("#DDD")
|
||||
.Padding(10)
|
||||
.Text("This column takes 2/3 of the available space (400pt)");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//[ShowResult]
|
||||
[ImageSize(500, 350)]
|
||||
public void Column(IContainer container)
|
||||
{
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(15)
|
||||
.Stack(column =>
|
||||
{
|
||||
column.Spacing(10);
|
||||
|
||||
column
|
||||
.Item()
|
||||
.Background("#999")
|
||||
.Height(50);
|
||||
|
||||
column
|
||||
.Item()
|
||||
.Background("#BBB")
|
||||
.Height(100);
|
||||
|
||||
column
|
||||
.Item()
|
||||
.Background("#DDD")
|
||||
.Height(150);
|
||||
});
|
||||
}
|
||||
|
||||
//[ShowResult]
|
||||
[ImageSize(300, 200)]
|
||||
public void Debug(IContainer container)
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.Debug()
|
||||
.Padding(-5)
|
||||
.Row(row =>
|
||||
{
|
||||
row.RelativeColumn().Padding(5).Extend().Placeholder();
|
||||
row.RelativeColumn().Padding(5).Extend().Placeholder();
|
||||
});
|
||||
}
|
||||
|
||||
//[ShowResult]
|
||||
[ImageSize(300, 200)]
|
||||
public void ElementEnd(IContainer container)
|
||||
{
|
||||
var text = "";
|
||||
|
||||
container
|
||||
.Padding(10)
|
||||
.Element(x =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
x.Height(10).Width(50).Background("#DDD");
|
||||
else
|
||||
x.Text(text);
|
||||
});
|
||||
}
|
||||
|
||||
//[ShowResult]
|
||||
[ImageSize(300, 200)]
|
||||
public void GridExample(IContainer container)
|
||||
{
|
||||
var textStyle = TextStyle.Default.Size(14);
|
||||
|
||||
container
|
||||
.Padding(20)
|
||||
.AlignRight()
|
||||
.Grid(grid =>
|
||||
{
|
||||
grid.Spacing(5);
|
||||
grid.Columns(12);
|
||||
|
||||
grid.Element(8).Background("#DDD").Height(50).Padding(5).Text("This is a short text", textStyle);
|
||||
grid.Element(4).Background("#BBB").Padding(5).Text("Showing how to...", textStyle);
|
||||
grid.Element(2).Background("#999").Height(50);
|
||||
grid.Element(4).Background("#AAA").Border(2).BorderColor("#666").Padding(5).Text("...generate", textStyle);
|
||||
grid.Element(6).Background("#CCC").Padding(5).Text("simple grids", textStyle.Size(18).Bold());
|
||||
grid.Element(8).Background("#DDD").Height(50);
|
||||
});
|
||||
}
|
||||
|
||||
[ShowResult]
|
||||
[ImageSize(300, 300)]
|
||||
public void Layers(IContainer container)
|
||||
{
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(25)
|
||||
.Layers(layers =>
|
||||
{
|
||||
layers.Layer().Text("Something else");
|
||||
|
||||
layers.PrimaryLayer().Stack(stack =>
|
||||
{
|
||||
stack.Item().PaddingTop(20).Text("Text 1");
|
||||
stack.Item().PaddingTop(40).Text("Text 2");
|
||||
});
|
||||
|
||||
layers.Layer().Canvas((canvas, size) =>
|
||||
{
|
||||
using var paint = new SKPaint
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(25)
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
Color = SKColors.Red,
|
||||
StrokeWidth = 5
|
||||
};
|
||||
|
||||
canvas.Translate(size.Width / 2, size.Height / 2);
|
||||
canvas.DrawCircle(0, 0, 50, paint);
|
||||
});
|
||||
decoration
|
||||
.Header()
|
||||
.Background(Colors.Grey.Medium)
|
||||
.Padding(10)
|
||||
.Text("Notes", TextStyle.Default.Size(16).Color("#FFF"));
|
||||
|
||||
layers.Layer().Background("#8F00").Extend();
|
||||
layers.Layer().PaddingTop(40).Text("It works!", TextStyle.Default.Size(24));
|
||||
decoration
|
||||
.Content()
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.Padding(10)
|
||||
.ExtendVertical()
|
||||
.Text(Helpers.Placeholders.LoremIpsum());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Row()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(740, 200)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(20)
|
||||
.Stack(stack =>
|
||||
{
|
||||
stack.Item()
|
||||
.PaddingBottom(10)
|
||||
.AlignCenter()
|
||||
.Text("This Row element is 700pt wide");
|
||||
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.ConstantColumn(100)
|
||||
.Background(Colors.Grey.Lighten1)
|
||||
.Padding(10)
|
||||
.ExtendVertical()
|
||||
.Text("This column is 100 pt wide");
|
||||
|
||||
row.RelativeColumn()
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Padding(10)
|
||||
.Text("This column takes 1/3 of the available space (200pt)");
|
||||
|
||||
row.RelativeColumn(2)
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.Padding(10)
|
||||
.Text("This column takes 2/3 of the available space (400pt)");
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RowSpacing()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(740, 200)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(20)
|
||||
.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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Stack()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 360)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(15)
|
||||
.Stack(stack =>
|
||||
{
|
||||
stack.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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Debug()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(210, 210)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.Debug("Grid example", Colors.Blue.Medium)
|
||||
.Grid(grid =>
|
||||
{
|
||||
grid.Columns(3);
|
||||
grid.Spacing(5);
|
||||
|
||||
foreach (var _ in Enumerable.Range(0, 8))
|
||||
grid.Item().Height(50).Placeholder();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ElementEnd()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 200)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
var text = "";
|
||||
|
||||
container
|
||||
.Padding(10)
|
||||
.Element(x =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
x.Height(10).Width(50).Background("#DDD");
|
||||
else
|
||||
x.Text(text);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GridExample()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(400, 230)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
var textStyle = TextStyle.Default.Size(14);
|
||||
|
||||
container
|
||||
.Padding(15)
|
||||
.AlignRight()
|
||||
.Grid(grid =>
|
||||
{
|
||||
grid.VerticalSpacing(10);
|
||||
grid.HorizontalSpacing(10);
|
||||
grid.AlignCenter();
|
||||
grid.Columns(10); // 12 by default
|
||||
|
||||
grid.Item(6).Background(Colors.Blue.Lighten1).Height(50);
|
||||
grid.Item(4).Background(Colors.Blue.Lighten3).Height(50);
|
||||
|
||||
grid.Item(2).Background(Colors.Teal.Lighten1).Height(70);
|
||||
grid.Item(3).Background(Colors.Teal.Lighten2).Height(70);
|
||||
grid.Item(5).Background(Colors.Teal.Lighten3).Height(70);
|
||||
|
||||
grid.Item(2).Background(Colors.Green.Lighten1).Height(50);
|
||||
grid.Item(2).Background(Colors.Green.Lighten2).Height(50);
|
||||
grid.Item(2).Background(Colors.Green.Lighten3).Height(50);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Canvas()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 200)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(25)
|
||||
.Canvas((canvas, size) =>
|
||||
{
|
||||
using var paint = new SKPaint
|
||||
{
|
||||
Color = SKColors.Red,
|
||||
StrokeWidth = 10,
|
||||
IsStroke = true
|
||||
};
|
||||
|
||||
// move origin to the center of the available space
|
||||
canvas.Translate(size.Width / 2, size.Height / 2);
|
||||
|
||||
// draw a circle
|
||||
canvas.DrawCircle(0, 0, 50, paint);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LayersExample()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(400, 250)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.Layers(layers =>
|
||||
{
|
||||
// layer below main content
|
||||
layers
|
||||
.Layer()
|
||||
.Height(100)
|
||||
.Width(100)
|
||||
.Background(Colors.Grey.Lighten3);
|
||||
|
||||
layers
|
||||
.PrimaryLayer()
|
||||
.Padding(25)
|
||||
.Stack(stack =>
|
||||
{
|
||||
stack.Spacing(5);
|
||||
|
||||
foreach (var _ in Enumerable.Range(0, 7))
|
||||
stack.Item().Text(Placeholders.Sentence());
|
||||
});
|
||||
|
||||
// layer above the main content
|
||||
layers
|
||||
.Layer()
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.Text("Watermark", TextStyle.Default.Size(48).Bold().Color(Colors.Green.Lighten3));
|
||||
|
||||
layers
|
||||
.Layer()
|
||||
.AlignBottom()
|
||||
.Text(text => text.CurrentPageNumber(TextStyle.Default.Size(16).Color(Colors.Green.Medium)));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// [Test]
|
||||
// public void EnsureSpace()
|
||||
// {
|
||||
// RenderingTest
|
||||
// .Create()
|
||||
// .PageSize(300, 400)
|
||||
// .Render(container =>
|
||||
// {
|
||||
// container
|
||||
// .Padding(50)
|
||||
// .Page(page =>
|
||||
// {
|
||||
// page.Header().PageNumber("Page {pdf:currentPage}");
|
||||
//
|
||||
// page.Content().Height(300).Stack(content =>
|
||||
// {
|
||||
// content.Item().Height(200).Background(Colors.Grey.Lighten2);
|
||||
//
|
||||
// content.Item().EnsureSpace(100).Stack(stack =>
|
||||
// {
|
||||
// stack.Spacing(10);
|
||||
//
|
||||
// foreach (var _ in Enumerable.Range(0, 4))
|
||||
// stack.Item().Height(50).Background(Colors.Green.Lighten1);
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
[Test]
|
||||
public void RandomColorMatrix()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 300)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.Grid(grid =>
|
||||
{
|
||||
grid.Columns(5);
|
||||
|
||||
Enumerable
|
||||
.Range(0, 25)
|
||||
.Select(x => Placeholders.BackgroundColor())
|
||||
.ToList()
|
||||
.ForEach(x => grid.Item().Height(50).Background(x));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DefinedColors()
|
||||
{
|
||||
var colors = new[]
|
||||
{
|
||||
Colors.Green.Darken4,
|
||||
Colors.Green.Darken3,
|
||||
Colors.Green.Darken2,
|
||||
Colors.Green.Darken1,
|
||||
|
||||
Colors.Green.Medium,
|
||||
|
||||
Colors.Green.Lighten1,
|
||||
Colors.Green.Lighten2,
|
||||
Colors.Green.Lighten3,
|
||||
Colors.Green.Lighten4,
|
||||
Colors.Green.Lighten5,
|
||||
|
||||
Colors.Green.Accent1,
|
||||
Colors.Green.Accent2,
|
||||
Colors.Green.Accent3,
|
||||
Colors.Green.Accent4,
|
||||
};
|
||||
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(450, 150)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.Height(100)
|
||||
.Row(row =>
|
||||
{
|
||||
foreach (var color in colors)
|
||||
row.RelativeColumn().Background(color);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DefinedFonts()
|
||||
{
|
||||
var fonts = new[]
|
||||
{
|
||||
Fonts.Calibri,
|
||||
Fonts.Candara,
|
||||
Fonts.Arial,
|
||||
Fonts.TimesNewRoman,
|
||||
Fonts.Consolas,
|
||||
Fonts.Tahoma,
|
||||
Fonts.Impact,
|
||||
Fonts.Trebuchet,
|
||||
Fonts.ComicSans
|
||||
};
|
||||
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 175)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.Grid(grid =>
|
||||
{
|
||||
grid.Columns(3);
|
||||
|
||||
foreach (var font in fonts)
|
||||
{
|
||||
grid.Item()
|
||||
.Border(1)
|
||||
.BorderColor(Colors.Grey.Medium)
|
||||
.Padding(10)
|
||||
.Text(font, TextStyle.Default.FontType(font).Size(16));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Layers()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 300)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(25)
|
||||
.Layers(layers =>
|
||||
{
|
||||
layers.Layer().Text("Something else");
|
||||
|
||||
layers.PrimaryLayer().Stack(stack =>
|
||||
{
|
||||
stack.Item().PaddingTop(20).Text("Text 1");
|
||||
stack.Item().PaddingTop(40).Text("Text 2");
|
||||
});
|
||||
|
||||
layers.Layer().Canvas((canvas, size) =>
|
||||
{
|
||||
using var paint = new SKPaint
|
||||
{
|
||||
Color = SKColors.Red,
|
||||
StrokeWidth = 5
|
||||
};
|
||||
|
||||
canvas.Translate(size.Width / 2, size.Height / 2);
|
||||
canvas.DrawCircle(0, 0, 50, paint);
|
||||
});
|
||||
|
||||
layers.Layer().Background("#8F00").Extend();
|
||||
layers.Layer().PaddingTop(40).Text("It works!", TextStyle.Default.Size(24));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Box()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 150)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(15)
|
||||
.Border(4)
|
||||
.BorderColor(Colors.Blue.Medium)
|
||||
//.Box()
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Padding(15)
|
||||
.Text("Test of the \n box element", TextStyle.Default.Size(20));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Scale()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 175)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background(Colors.White)
|
||||
.Padding(10)
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
var headerFontStyle = TextStyle
|
||||
.Default
|
||||
.Size(20)
|
||||
.Color(Colors.Blue.Darken2)
|
||||
.SemiBold();
|
||||
|
||||
decoration
|
||||
.Header()
|
||||
.PaddingBottom(10)
|
||||
.Text("Example: scale component", headerFontStyle);
|
||||
|
||||
decoration
|
||||
.Content()
|
||||
.Stack(stack =>
|
||||
{
|
||||
var scales = new[] { 0.8f, 0.9f, 1.1f, 1.2f };
|
||||
|
||||
foreach (var scale in scales)
|
||||
{
|
||||
var fontColor = scale <= 1f
|
||||
? Colors.Red.Lighten4
|
||||
: Colors.Green.Lighten4;
|
||||
|
||||
var fontStyle = TextStyle.Default.Size(16);
|
||||
|
||||
stack
|
||||
.Item()
|
||||
.Border(1)
|
||||
.Background(fontColor)
|
||||
.Scale(scale)
|
||||
.Padding(5)
|
||||
.Text($"Content with {scale} scale.", fontStyle);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Translate()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 200)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Box()
|
||||
|
||||
.Padding(25)
|
||||
|
||||
.Background(Colors.Green.Lighten3)
|
||||
|
||||
.TranslateX(15)
|
||||
.TranslateY(15)
|
||||
|
||||
.Border(2)
|
||||
.BorderColor(Colors.Green.Darken1)
|
||||
|
||||
.Padding(50)
|
||||
.Text("Moved text", TextStyle.Default.Size(25));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ConstrainedRotate()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(350, 350)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(20)
|
||||
.Grid(grid =>
|
||||
{
|
||||
grid.Columns(2);
|
||||
grid.Spacing(10);
|
||||
|
||||
foreach (var turns in Enumerable.Range(0, 4))
|
||||
{
|
||||
grid.Item()
|
||||
.Width(150)
|
||||
.Height(150)
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Padding(10)
|
||||
.Element(element =>
|
||||
{
|
||||
foreach (var x in Enumerable.Range(0, turns))
|
||||
element = element.RotateRight();
|
||||
|
||||
return element;
|
||||
})
|
||||
.Box()
|
||||
.Background(Colors.White)
|
||||
.Padding(10)
|
||||
.Text($"Rotated {turns * 90}°", TextStyle.Default.Size(16));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FreeRotate()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 300)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
|
||||
|
||||
.Background(Colors.White)
|
||||
|
||||
.Rotate(30)
|
||||
|
||||
.Width(100)
|
||||
.Height(100)
|
||||
.Background(Colors.Blue.Medium);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FreeRotateCenter()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 300)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
|
||||
.Background(Colors.White)
|
||||
|
||||
.TranslateX(50)
|
||||
.TranslateY(50)
|
||||
|
||||
.Rotate(30)
|
||||
|
||||
.TranslateX(-50)
|
||||
.TranslateY(-50)
|
||||
|
||||
.Width(100)
|
||||
.Height(100)
|
||||
.Background(Colors.Blue.Medium);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Flip()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(350, 350)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(20)
|
||||
.Grid(grid =>
|
||||
{
|
||||
grid.Columns(2);
|
||||
grid.Spacing(10);
|
||||
|
||||
foreach (var turns in Enumerable.Range(0, 4))
|
||||
{
|
||||
grid.Item()
|
||||
.Width(150)
|
||||
.Height(150)
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.Padding(10)
|
||||
.Element(element =>
|
||||
{
|
||||
if (turns == 1 || turns == 2)
|
||||
element = element.FlipHorizontal();
|
||||
|
||||
if (turns == 2 || turns == 3)
|
||||
element = element.FlipVertical();
|
||||
|
||||
return element;
|
||||
})
|
||||
.Box()
|
||||
.Background(Colors.White)
|
||||
.Padding(10)
|
||||
.Text($"Flipped {turns}", TextStyle.Default.Size(16));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RotateInTable()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(200, 200)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.Border(2)
|
||||
.Row(row =>
|
||||
{
|
||||
row.ConstantColumn(25)
|
||||
.Border(1)
|
||||
.RotateLeft()
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.Text("Sample text");
|
||||
|
||||
row.RelativeColumn().Border(1).Padding(5).Text(Placeholders.Paragraph());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Unconstrained()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(400, 350)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.PaddingLeft(75)
|
||||
.Stack(stack =>
|
||||
{
|
||||
stack.Item().Width(300).Height(150).Background(Colors.Blue.Lighten4);
|
||||
|
||||
stack
|
||||
.Item()
|
||||
|
||||
// creates an infinite space for its child
|
||||
.Unconstrained()
|
||||
|
||||
// moves the child up and left
|
||||
.TranslateX(-50)
|
||||
.TranslateY(-50)
|
||||
|
||||
// limits the space for the child
|
||||
.Width(100)
|
||||
.Height(100)
|
||||
|
||||
.Background(Colors.Blue.Darken1);
|
||||
|
||||
stack.Item().Width(300).Height(150).Background(Colors.Blue.Lighten3);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Examples.Engine
|
||||
{
|
||||
[TestFixture]
|
||||
public class ExampleTestBase
|
||||
{
|
||||
private readonly Size DefaultImageSize = new Size(400, 300);
|
||||
private const string ResultPath = "Result";
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
if (Directory.Exists(ResultPath))
|
||||
Directory.Delete(ResultPath, true);
|
||||
|
||||
Directory.CreateDirectory(ResultPath);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RunTest()
|
||||
{
|
||||
GetType()
|
||||
.GetMethods()
|
||||
.Where(IsExampleMethod)
|
||||
.ToList()
|
||||
.ForEach(HandleExample);
|
||||
}
|
||||
|
||||
private bool IsExampleMethod(MethodInfo method)
|
||||
{
|
||||
var parameters = method.GetParameters();
|
||||
return parameters.Length == 1 && parameters.First().ParameterType == typeof(IContainer);
|
||||
}
|
||||
|
||||
private T GetAttribute<T>(MethodInfo methodInfo) where T : Attribute
|
||||
{
|
||||
return methodInfo.GetCustomAttributes().FirstOrDefault(y => y is T) as T;
|
||||
}
|
||||
|
||||
private void HandleExample(MethodInfo methodInfo)
|
||||
{
|
||||
var size = GetAttribute<ImageSizeAttribute>(methodInfo)?.Size ?? DefaultImageSize;
|
||||
var fileName = GetAttribute<FileNameAttribute>(methodInfo)?.FileName ?? methodInfo.Name;
|
||||
var showResult = GetAttribute<ShowResultAttribute>(methodInfo) != null;
|
||||
var shouldIgnore = GetAttribute<IgnoreAttribute>(methodInfo) != null;
|
||||
|
||||
if (shouldIgnore)
|
||||
return;
|
||||
|
||||
var container = new Container();
|
||||
methodInfo.Invoke(this, new object[] {container});
|
||||
|
||||
Func<int, string> fileNameSchema = i => $"{fileName.ToLower()}-${i}.png";
|
||||
|
||||
try
|
||||
{
|
||||
var document = new SimpleDocument(container, size);
|
||||
document.GenerateImages(fileNameSchema);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception($"Cannot perform test {fileName}", e);
|
||||
}
|
||||
|
||||
if (showResult)
|
||||
Process.Start("explorer", fileNameSchema(0));
|
||||
}
|
||||
|
||||
private byte[] RenderPage(Element element, Size size)
|
||||
{
|
||||
// scale the result so it is more readable
|
||||
const float scalingFactor = 2;
|
||||
|
||||
var imageInfo = new SKImageInfo((int)(size.Width * scalingFactor), (int)(size.Height * scalingFactor));
|
||||
using var surface = SKSurface.Create(imageInfo);
|
||||
surface.Canvas.Scale(scalingFactor);
|
||||
|
||||
var canvas = new Drawing.Canvas(surface.Canvas);
|
||||
element?.Draw(canvas, size);
|
||||
|
||||
surface.Canvas.Save();
|
||||
return surface.Snapshot().Encode(SKEncodedImageFormat.Png, 100).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace QuestPDF.Examples.Engine
|
||||
{
|
||||
public class FileNameAttribute : Attribute
|
||||
{
|
||||
public string FileName { get; }
|
||||
|
||||
public FileNameAttribute(string fileName)
|
||||
{
|
||||
FileName = fileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace QuestPDF.Examples.Engine
|
||||
{
|
||||
public class IgnoreAttribute : Attribute
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using System;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples.Engine
|
||||
{
|
||||
public class ImageSizeAttribute : Attribute
|
||||
{
|
||||
private int Width { get; }
|
||||
private int Height { get; }
|
||||
|
||||
public Size Size => new Size(Width, Height);
|
||||
|
||||
public ImageSizeAttribute(int width, int height)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples.Engine
|
||||
{
|
||||
public enum RenderingTestResult
|
||||
{
|
||||
Pdf,
|
||||
Images
|
||||
}
|
||||
|
||||
public class RenderingTest
|
||||
{
|
||||
private string FileNamePrefix = "test";
|
||||
private Size Size { get; set; }
|
||||
private bool ShowResult { get; set; }
|
||||
private RenderingTestResult ResultType { get; set; } = RenderingTestResult.Images;
|
||||
|
||||
private RenderingTest()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public static RenderingTest Create()
|
||||
{
|
||||
return new RenderingTest();
|
||||
}
|
||||
|
||||
public RenderingTest FileName([CallerMemberName] string fileName = "test")
|
||||
{
|
||||
FileNamePrefix = fileName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RenderingTest PageSize(Size size)
|
||||
{
|
||||
Size = size;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RenderingTest PageSize(int width, int height)
|
||||
{
|
||||
return PageSize(new Size(width, height));
|
||||
}
|
||||
|
||||
public RenderingTest ProducePdf()
|
||||
{
|
||||
ResultType = RenderingTestResult.Pdf;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RenderingTest ProduceImages()
|
||||
{
|
||||
ResultType = RenderingTestResult.Images;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RenderingTest ShowResults()
|
||||
{
|
||||
ShowResult = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void Render(Action<IContainer> content)
|
||||
{
|
||||
var container = new Container();
|
||||
content(container);
|
||||
|
||||
var maxPages = ResultType == RenderingTestResult.Pdf ? 1000 : 10;
|
||||
var document = new SimpleDocument(container, Size, maxPages);
|
||||
|
||||
if (ResultType == RenderingTestResult.Images)
|
||||
{
|
||||
Func<int, string> fileNameSchema = i => $"{FileNamePrefix}-${i}.png";
|
||||
document.GenerateImages(fileNameSchema);
|
||||
|
||||
if (ShowResult)
|
||||
Process.Start("explorer", fileNameSchema(0));
|
||||
}
|
||||
|
||||
if (ResultType == RenderingTestResult.Pdf)
|
||||
{
|
||||
var fileName = $"{FileNamePrefix}.pdf";
|
||||
document.GeneratePdf(fileName);
|
||||
|
||||
if (ShowResult)
|
||||
Process.Start("explorer", fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace QuestPDF.Examples.Engine
|
||||
{
|
||||
public class ShowResultAttribute : Attribute
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
@@ -7,27 +8,35 @@ namespace QuestPDF.Examples.Engine
|
||||
{
|
||||
public class SimpleDocument : IDocument
|
||||
{
|
||||
public const int ImageScalingFactor = 2;
|
||||
|
||||
private IContainer Container { get; }
|
||||
private Size Size { get; }
|
||||
private int MaxPages { get; }
|
||||
|
||||
public SimpleDocument(IContainer container, Size size)
|
||||
public SimpleDocument(IContainer container, Size size, int maxPages)
|
||||
{
|
||||
Container = container;
|
||||
Size = size;
|
||||
MaxPages = maxPages;
|
||||
}
|
||||
|
||||
public DocumentMetadata GetMetadata()
|
||||
{
|
||||
return new DocumentMetadata()
|
||||
{
|
||||
RasterDpi = PageSizes.PointsPerInch * 2,
|
||||
Size = Size
|
||||
RasterDpi = PageSizes.PointsPerInch * ImageScalingFactor,
|
||||
DocumentLayoutExceptionThreshold = MaxPages
|
||||
};
|
||||
}
|
||||
|
||||
public void Compose(IContainer container)
|
||||
|
||||
public void Compose(IDocumentContainer container)
|
||||
{
|
||||
container.Background("#FFF").Element(Container.Child);
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Size(new PageSize(Size.Width, Size.Height));
|
||||
page.Content().Container().Background(Colors.White).Element(Container as Container);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
@@ -19,25 +20,31 @@ namespace QuestPDF.Examples
|
||||
public static IContainer ValueCell(this IContainer container) => container.Cell(false);
|
||||
}
|
||||
|
||||
public class FrameExample: ExampleTestBase
|
||||
public class FrameExample
|
||||
{
|
||||
[ImageSize(550, 400)]
|
||||
[ShowResult]
|
||||
[Test]
|
||||
public void Frame(IContainer container)
|
||||
{
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(25)
|
||||
.Stack(stack =>
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(550, 400)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
for(var i=1; i<=4; i++)
|
||||
{
|
||||
stack.Item().Row(row =>
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(25)
|
||||
.Stack(stack =>
|
||||
{
|
||||
row.RelativeColumn(2).LabelCell().Text(Placeholders.Label());
|
||||
row.RelativeColumn(3).ValueCell().Text(Placeholders.Paragraph());
|
||||
for(var i=1; i<=4; i++)
|
||||
{
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(2).LabelCell().Text(Placeholders.Label());
|
||||
row.RelativeColumn(3).ValueCell().Text(Placeholders.Paragraph());
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Net;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
@@ -27,27 +28,33 @@ namespace QuestPDF.Examples
|
||||
}
|
||||
}
|
||||
|
||||
public class LoremPicsumExample : ExampleTestBase
|
||||
public class LoremPicsumExample
|
||||
{
|
||||
[ShowResult]
|
||||
[ImageSize(350, 280)]
|
||||
public void LoremPicsum(IContainer container)
|
||||
[Test]
|
||||
public void LoremPicsum()
|
||||
{
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(25)
|
||||
.Stack(column =>
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(350, 280)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
column.Spacing(10);
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(25)
|
||||
.Stack(column =>
|
||||
{
|
||||
column.Spacing(10);
|
||||
|
||||
column
|
||||
.Item()
|
||||
.Component(new LoremPicsum(true));
|
||||
column
|
||||
.Item()
|
||||
.Component(new LoremPicsum(true));
|
||||
|
||||
column
|
||||
.Item()
|
||||
.AlignRight()
|
||||
.Text("From Lorem Picsum");
|
||||
column
|
||||
.Item()
|
||||
.AlignRight()
|
||||
.Text("From Lorem Picsum");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+140
-106
@@ -1,132 +1,166 @@
|
||||
using QuestPDF.Examples.Engine;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class Examples : ExampleTestBase
|
||||
public class Examples
|
||||
{
|
||||
public void Padding(IContainer container)
|
||||
[Test]
|
||||
public void Padding()
|
||||
{
|
||||
container
|
||||
.Background("#FDD")
|
||||
.Padding(50)
|
||||
|
||||
.Background("#AFA")
|
||||
.PaddingVertical(50)
|
||||
|
||||
.Background("#77F")
|
||||
.PaddingHorizontal(50)
|
||||
|
||||
.Background("#444");
|
||||
}
|
||||
|
||||
public void Border(IContainer container)
|
||||
{
|
||||
container
|
||||
.Background("#EEE")
|
||||
.Padding(25)
|
||||
|
||||
.AlignBottom()
|
||||
.AlignCenter()
|
||||
.BorderBottom(2)
|
||||
.BorderColor("#000")
|
||||
|
||||
.Background("FFF")
|
||||
.Padding(5)
|
||||
.Text("Sample text", TextStyle.Default.FontType("Segoe UI emoji").Alignment(HorizontalAlignment.Center));
|
||||
}
|
||||
|
||||
public void Alignment(IContainer container)
|
||||
{
|
||||
container
|
||||
.Stack(column =>
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(200, 150)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
column
|
||||
.Item()
|
||||
.Height(100)
|
||||
.Background("#FFF")
|
||||
|
||||
.AlignLeft()
|
||||
.AlignMiddle()
|
||||
container
|
||||
.Background("#FDD")
|
||||
.Padding(50)
|
||||
|
||||
.Background("#AFA")
|
||||
.PaddingVertical(50)
|
||||
|
||||
.Background("#77F")
|
||||
.PaddingHorizontal(50)
|
||||
|
||||
.Width(50)
|
||||
.Height(50)
|
||||
.Background("#444");
|
||||
|
||||
column
|
||||
.Item()
|
||||
.Height(100)
|
||||
.Background("#DDD")
|
||||
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
|
||||
.Width(50)
|
||||
.Height(50)
|
||||
.Background("#222");
|
||||
|
||||
column
|
||||
.Item()
|
||||
.Height(100)
|
||||
.Background("#BBB")
|
||||
|
||||
.AlignRight()
|
||||
.AlignMiddle()
|
||||
|
||||
.Width(50)
|
||||
.Height(50)
|
||||
.Background("#000");
|
||||
});
|
||||
}
|
||||
|
||||
public void Expand(IContainer container)
|
||||
[Test]
|
||||
public void Border()
|
||||
{
|
||||
container
|
||||
.Stack(column =>
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(200, 150)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
column
|
||||
.Item()
|
||||
.Height(150)
|
||||
.Row(row =>
|
||||
container
|
||||
.Background("#EEE")
|
||||
.Padding(25)
|
||||
|
||||
.AlignBottom()
|
||||
.AlignCenter()
|
||||
.BorderBottom(2)
|
||||
.BorderColor("#000")
|
||||
|
||||
.Background("FFF")
|
||||
.Padding(5)
|
||||
.AlignCenter()
|
||||
.Text("Sample text", TextStyle.Default.FontType("Segoe UI emoji"));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Alignment()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(200, 150)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Stack(column =>
|
||||
{
|
||||
row.RelativeColumn()
|
||||
.Extend()
|
||||
.Background("FFF")
|
||||
column
|
||||
.Item()
|
||||
.Height(100)
|
||||
.Background("#FFF")
|
||||
|
||||
.AlignLeft()
|
||||
.AlignMiddle()
|
||||
|
||||
.Height(50)
|
||||
.Width(50)
|
||||
.Background("444");
|
||||
|
||||
row.RelativeColumn()
|
||||
.Extend()
|
||||
.Background("BBB")
|
||||
|
||||
.Height(50)
|
||||
.ExtendHorizontal()
|
||||
.Background("444");
|
||||
});
|
||||
.Background("#444");
|
||||
|
||||
column
|
||||
.Item()
|
||||
.Height(150)
|
||||
.Row(row =>
|
||||
{
|
||||
row.RelativeColumn()
|
||||
.Extend()
|
||||
.Background("BBB")
|
||||
column
|
||||
.Item()
|
||||
.Height(100)
|
||||
.Background("#DDD")
|
||||
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
|
||||
.ExtendVertical()
|
||||
.Width(50)
|
||||
.Background("444");
|
||||
|
||||
row.RelativeColumn()
|
||||
.Extend()
|
||||
.Background("BBB")
|
||||
.Height(50)
|
||||
.Background("#222");
|
||||
|
||||
column
|
||||
.Item()
|
||||
.Height(100)
|
||||
.Background("#BBB")
|
||||
|
||||
.AlignRight()
|
||||
.AlignMiddle()
|
||||
|
||||
.ExtendVertical()
|
||||
.ExtendHorizontal()
|
||||
.Background("444");
|
||||
.Width(50)
|
||||
.Height(50)
|
||||
.Background("#000");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Expand()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(200, 150)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Stack(column =>
|
||||
{
|
||||
column
|
||||
.Item()
|
||||
.Height(150)
|
||||
.Row(row =>
|
||||
{
|
||||
row.RelativeColumn()
|
||||
.Extend()
|
||||
.Background("FFF")
|
||||
|
||||
.Height(50)
|
||||
.Width(50)
|
||||
.Background("444");
|
||||
|
||||
row.RelativeColumn()
|
||||
.Extend()
|
||||
.Background("BBB")
|
||||
|
||||
.Height(50)
|
||||
.ExtendHorizontal()
|
||||
.Background("444");
|
||||
});
|
||||
|
||||
column
|
||||
.Item()
|
||||
.Height(150)
|
||||
.Row(row =>
|
||||
{
|
||||
row.RelativeColumn()
|
||||
.Extend()
|
||||
.Background("BBB")
|
||||
|
||||
.ExtendVertical()
|
||||
.Width(50)
|
||||
.Background("444");
|
||||
|
||||
row.RelativeColumn()
|
||||
.Extend()
|
||||
.Background("BBB")
|
||||
|
||||
.ExtendVertical()
|
||||
.ExtendHorizontal()
|
||||
.Background("444");
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,4 +16,10 @@
|
||||
<ProjectReference Include="..\QuestPDF\QuestPDF.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="quo-vadis.txt">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class TextBenchmark
|
||||
{
|
||||
[Test]
|
||||
public void Generate()
|
||||
{
|
||||
var chapters = GetBookChapters().ToList();
|
||||
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.FileName()
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(x => ComposeBook(x, chapters));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Benchmark()
|
||||
{
|
||||
var chapters = GetBookChapters().ToList();
|
||||
|
||||
var results = PerformTest(16).ToList();
|
||||
|
||||
Console.WriteLine($"Min: {results.Min():F}");
|
||||
Console.WriteLine($"Max: {results.Max():F}");
|
||||
Console.WriteLine($"Avg: {results.Average():F}");
|
||||
|
||||
void GenerateDocument()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.FileName()
|
||||
.ProducePdf()
|
||||
.Render(x => ComposeBook(x, chapters));
|
||||
}
|
||||
|
||||
IEnumerable<float> PerformTest(int attempts)
|
||||
{
|
||||
foreach (var i in Enumerable.Range(0, attempts))
|
||||
{
|
||||
var timer = new Stopwatch();
|
||||
|
||||
timer.Start();
|
||||
GenerateDocument();
|
||||
timer.Stop();
|
||||
|
||||
Console.WriteLine($"Attempt {i}: {timer.ElapsedMilliseconds:F}");
|
||||
yield return timer.ElapsedMilliseconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BookChapter
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Content { get; set; }
|
||||
}
|
||||
|
||||
private static IEnumerable<BookChapter> GetBookChapters()
|
||||
{
|
||||
var book = File.ReadAllLines("quo-vadis.txt");
|
||||
|
||||
var chapterPointers = book
|
||||
.Select((line, index) => new
|
||||
{
|
||||
LineNumber = index,
|
||||
Text = line
|
||||
})
|
||||
.Where(x => x.Text.Length < 50 && x.Text.Contains("Rozdział") || x.Text.Contains("-----"))
|
||||
.Select(x => x.LineNumber)
|
||||
.ToList();
|
||||
|
||||
foreach (var index in Enumerable.Range(0, chapterPointers.Count - 1))
|
||||
{
|
||||
var chapter = chapterPointers[index];
|
||||
|
||||
var title = book[chapter];
|
||||
|
||||
var lineFrom = chapterPointers[index];
|
||||
var lineTo = chapterPointers[index + 1] - 1;
|
||||
|
||||
var lines = book.Skip(lineFrom + 1).Take(lineTo - lineFrom).Where(x => !string.IsNullOrWhiteSpace(x));
|
||||
var content = string.Join(Environment.NewLine, lines);
|
||||
|
||||
yield return new BookChapter
|
||||
{
|
||||
Title = title,
|
||||
Content = content
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void ComposeBook(IContainer 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
|
||||
.Padding(50)
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
decoration
|
||||
.Content()
|
||||
.Stack(stack =>
|
||||
{
|
||||
stack.Item().Element(Title);
|
||||
stack.Item().PageBreak();
|
||||
stack.Item().Element(TableOfContents);
|
||||
stack.Item().PageBreak();
|
||||
|
||||
Chapters(stack);
|
||||
|
||||
stack.Item().Element(Acknowledgements);
|
||||
});
|
||||
|
||||
decoration.Footer().Element(Footer);
|
||||
});
|
||||
}
|
||||
|
||||
void Title(IContainer container)
|
||||
{
|
||||
container
|
||||
.Extend()
|
||||
.PaddingBottom(200)
|
||||
.AlignBottom()
|
||||
.Stack(stack =>
|
||||
{
|
||||
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));
|
||||
});
|
||||
}
|
||||
|
||||
void TableOfContents(IContainer container)
|
||||
{
|
||||
container.Stack(stack =>
|
||||
{
|
||||
SectionTitle(stack, "Spis treści");
|
||||
|
||||
foreach (var chapter in chapters)
|
||||
{
|
||||
stack.Item().InternalLink(chapter.Title).Row(row =>
|
||||
{
|
||||
row.RelativeColumn().Text(chapter.Title, normalStyle);
|
||||
row.ConstantColumn(100).AlignRight().Text(text => text.PageNumberOfLocation(chapter.Title, normalStyle));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Chapters(StackDescriptor stack)
|
||||
{
|
||||
foreach (var chapter in chapters)
|
||||
{
|
||||
stack.Item().Element(container => Chapter(container, chapter.Title, chapter.Content));
|
||||
}
|
||||
}
|
||||
|
||||
void Chapter(IContainer container, string title, string content)
|
||||
{
|
||||
container.Stack(stack =>
|
||||
{
|
||||
SectionTitle(stack, title);
|
||||
|
||||
stack.Item().Text(text =>
|
||||
{
|
||||
text.ParagraphSpacing(5);
|
||||
text.Span(content, normalStyle);
|
||||
});
|
||||
|
||||
stack.Item().PageBreak();
|
||||
});
|
||||
}
|
||||
|
||||
void Acknowledgements(IContainer container)
|
||||
{
|
||||
container.Stack(stack =>
|
||||
{
|
||||
SectionTitle(stack, "Podziękowania");
|
||||
|
||||
stack.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).Underlined());
|
||||
text.Span(". Dziękuję za wspieranie polskiego czytelnictwa!");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void SectionTitle(StackDescriptor stack, string text)
|
||||
{
|
||||
stack.Item().Location(text).Text(text, subtitleStyle);
|
||||
stack.Item().PaddingTop(10).PaddingBottom(50).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).ExtendHorizontal();
|
||||
}
|
||||
|
||||
void Footer(IContainer container)
|
||||
{
|
||||
container
|
||||
.AlignCenter()
|
||||
.Text(text =>
|
||||
{
|
||||
text.CurrentPageNumber();
|
||||
text.Span(" / ");
|
||||
text.TotalPages();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
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 TextExamples
|
||||
{
|
||||
[Test]
|
||||
public void TextElements()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 400)
|
||||
.FileName()
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.Box()
|
||||
.Border(1)
|
||||
.Padding(10)
|
||||
.Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(TextStyle.Default);
|
||||
text.AlignLeft();
|
||||
text.ParagraphSpacing(10);
|
||||
|
||||
text.Span(Placeholders.LoremIpsum());
|
||||
|
||||
text.NewLine();
|
||||
|
||||
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).Underlined());
|
||||
text.Span("and this is slightly bigger text.", TextStyle.Default.Size(16));
|
||||
|
||||
text.NewLine();
|
||||
|
||||
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.Span(".");
|
||||
|
||||
text.NewLine();
|
||||
|
||||
text.Span("This is page number ");
|
||||
text.CurrentPageNumber();
|
||||
text.Span(" out of ");
|
||||
text.TotalPages();
|
||||
|
||||
text.NewLine();
|
||||
|
||||
text.ExternalLocation("Please visit QuestPDF website", "https://www.questpdf.com");
|
||||
|
||||
text.NewLine();
|
||||
|
||||
/*
|
||||
text.Span("Let's start with bold text. ", TextStyle.Default.Bold().BackgroundColor(Colors.Grey.Lighten3).Size(16));
|
||||
text.Span("Then something bigger. ", TextStyle.Default.Size(28).Color(Colors.DeepOrange.Darken2).BackgroundColor(Colors.Yellow.Lighten3).Underlined());
|
||||
text.Span("And tiny \r\n teeny-tiny. ", TextStyle.Default.Size(6));
|
||||
text.Span("Stroked text also works fine. ", TextStyle.Default.Size(14).Stroked().BackgroundColor(Colors.Grey.Lighten4));
|
||||
|
||||
text.Span("0123456789-0123456789-0123456789-0123456789-0123456789-0123456789-0123456789", TextStyle.Default.Size(18));
|
||||
|
||||
text.NewLine();
|
||||
text.NewLine();
|
||||
text.NewLine();
|
||||
text.Span("Is it time for lorem ipsum? ", TextStyle.Default.Size(12).Underlined().BackgroundColor(Colors.Grey.Lighten3));
|
||||
text.Span(Placeholders.LoremIpsum(), TextStyle.Default.Size(12));
|
||||
|
||||
text.Span("Before element - ");
|
||||
text.Element().PaddingBottom(-10).Background(Colors.Red.Lighten4).Height(20).PaddingHorizontal(5).AlignMiddle().Text("Text inside text", TextStyle.Default.Size(8));
|
||||
text.Span(" - end of element.");
|
||||
|
||||
text.NewLine();
|
||||
// text.Span("And now some colors: ", TextStyle.Default.Size(16).Color(Colors.Green.Medium));
|
||||
//
|
||||
// foreach (var i in Enumerable.Range(1, 100))
|
||||
// {
|
||||
// text.Span($"{i}: {Placeholders.Sentence()} ", TextStyle.Default.Size(12 + i / 5).LineHeight(2.75f - i / 50f).Color(Placeholders.Color()).BackgroundColor(Placeholders.BackgroundColor()));
|
||||
// }*/
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PageNumber()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 400)
|
||||
.FileName()
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.Box()
|
||||
.Border(1)
|
||||
.Padding(10)
|
||||
.Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(TextStyle.Default);
|
||||
text.AlignLeft();
|
||||
text.ParagraphSpacing(10);
|
||||
|
||||
text.Span("This is page number ");
|
||||
text.CurrentPageNumber();
|
||||
text.Span(" out of ");
|
||||
text.TotalPages();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -18,8 +18,8 @@ namespace QuestPDF.ReportSample
|
||||
HeaderFields = HeaderFields(),
|
||||
|
||||
LogoData = Helpers.GetImage("Logo.png"),
|
||||
Sections = Enumerable.Range(0, 8).Select(x => GenerateSection()).ToList(),
|
||||
Photos = Enumerable.Range(2, 6).Select(x => GetReportPhotos()).ToList()
|
||||
Sections = Enumerable.Range(0, 40).Select(x => GenerateSection()).ToList(),
|
||||
Photos = Enumerable.Range(0, 25).Select(x => GetReportPhotos()).ToList()
|
||||
};
|
||||
|
||||
List<ReportHeaderField> HeaderFields()
|
||||
@@ -52,8 +52,8 @@ namespace QuestPDF.ReportSample
|
||||
ReportSection GenerateSection()
|
||||
{
|
||||
var sectionLength = Helpers.Random.NextDouble() > 0.75
|
||||
? Helpers.Random.Next(10, 20)
|
||||
: Helpers.Random.Next(3, 11);
|
||||
? Helpers.Random.Next(20, 40)
|
||||
: Helpers.Random.Next(5, 10);
|
||||
|
||||
return new ReportSection
|
||||
{
|
||||
@@ -100,8 +100,7 @@ namespace QuestPDF.ReportSample
|
||||
{
|
||||
Label = "Photos",
|
||||
Photos = Enumerable
|
||||
.Range(0, Helpers.Random.Next(1, 10))
|
||||
.Select(x => Helpers.Random.Next(0, 128))
|
||||
.Range(0, Helpers.Random.Next(1, 15))
|
||||
.Select(x => Placeholders.Image(400, 300))
|
||||
.ToList()
|
||||
};
|
||||
@@ -111,7 +110,7 @@ namespace QuestPDF.ReportSample
|
||||
{
|
||||
return new ReportPhoto()
|
||||
{
|
||||
PhotoData = Placeholders.Image(400, 300),
|
||||
PhotoData = Placeholders.Image(800, 600),
|
||||
|
||||
Comments = Placeholders.Sentence(),
|
||||
Date = DateTime.Now - TimeSpan.FromDays(Helpers.Random.NextDouble() * 100),
|
||||
|
||||
@@ -20,8 +20,8 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
.Stack(stack =>
|
||||
{
|
||||
stack.Spacing(5);
|
||||
stack.Item(PhotoWithMaps);
|
||||
stack.Item(PhotoDetails);
|
||||
stack.Item().Element(PhotoWithMaps);
|
||||
stack.Item().Element(PhotoDetails);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -48,13 +48,13 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
{
|
||||
grid.Columns(6);
|
||||
|
||||
grid.Element().LabelCell().Text("Date", Typography.Normal);
|
||||
grid.Element(2).ValueCell().Text(Model.Date?.ToString("g") ?? string.Empty, Typography.Normal);
|
||||
grid.Element().LabelCell().Text("Location", Typography.Normal);
|
||||
grid.Element(2).ValueCell().Text(Model.Location.Format(), Typography.Normal);
|
||||
grid.Item().LabelCell().Text("Date", Typography.Normal);
|
||||
grid.Item(2).ValueCell().Text(Model.Date?.ToString("g") ?? string.Empty, Typography.Normal);
|
||||
grid.Item().LabelCell().Text("Location", Typography.Normal);
|
||||
grid.Item(2).ValueCell().Text(Model.Location.Format(), Typography.Normal);
|
||||
|
||||
grid.Element().LabelCell().Text("Comments", Typography.Normal);
|
||||
grid.Element(5).ValueCell().Text(Model.Comments, Typography.Normal);
|
||||
grid.Item().LabelCell().Text("Comments", Typography.Normal);
|
||||
grid.Item(5).ValueCell().Text(Model.Comments, Typography.Normal);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,24 +17,24 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
{
|
||||
container
|
||||
.EnsureSpace()
|
||||
.Decoration(section =>
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
section
|
||||
decoration
|
||||
.Header()
|
||||
.PaddingBottom(5)
|
||||
.Text(Model.Title, Typography.Headline);
|
||||
|
||||
section.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Stack(stack =>
|
||||
decoration.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Stack(stack =>
|
||||
{
|
||||
foreach (var part in Model.Parts)
|
||||
{
|
||||
stack.Item().Row(row =>
|
||||
stack.Item().EnsureSpace(25).Row(row =>
|
||||
{
|
||||
row.ConstantColumn(150).LabelCell().Text(part.Label, Typography.Normal);
|
||||
var frame = row.RelativeColumn().ValueCell();
|
||||
|
||||
if (part is ReportSectionText text)
|
||||
frame.Text(text.Text, Typography.Normal);
|
||||
frame.ShowEntire().Text(text.Text, Typography.Normal);
|
||||
|
||||
if (part is ReportSectionMap map)
|
||||
frame.Element(x => MapElement(x, map));
|
||||
@@ -72,12 +72,12 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
return;
|
||||
}
|
||||
|
||||
container.Debug("Photo gallery").Grid(grid =>
|
||||
container.Debug("Photos").Grid(grid =>
|
||||
{
|
||||
grid.Spacing(5);
|
||||
grid.Columns(3);
|
||||
|
||||
model.Photos.ForEach(x => grid.Element().AspectRatio(4 / 3f).Image(Placeholders.Image));
|
||||
model.Photos.ForEach(x => grid.Item().AspectRatio(4 / 3f).Image(Placeholders.Image));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,21 +18,31 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
{
|
||||
return new DocumentMetadata()
|
||||
{
|
||||
Title = Model.Title,
|
||||
Size = PageSizes.A4
|
||||
Title = Model.Title
|
||||
};
|
||||
}
|
||||
|
||||
public void Compose(IContainer container)
|
||||
public void Compose(IDocumentContainer container)
|
||||
{
|
||||
container
|
||||
.PaddingVertical(40)
|
||||
.PaddingHorizontal(50)
|
||||
.Page(page =>
|
||||
{
|
||||
page.Header(ComposeHeader);
|
||||
page.Content(ComposeContent);
|
||||
page.Footer().AlignCenter().PageNumber("Page {number}");
|
||||
page.MarginVertical(40);
|
||||
page.MarginHorizontal(50);
|
||||
|
||||
page.Size(PageSizes.A4);
|
||||
|
||||
page.Header().Element(ComposeHeader);
|
||||
page.Content().Element(ComposeContent);
|
||||
|
||||
page.Footer().AlignCenter().Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(Typography.Normal);
|
||||
|
||||
text.CurrentPageNumber();
|
||||
text.Span(" / ");
|
||||
text.TotalPages();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -57,10 +67,10 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
|
||||
foreach (var field in Model.HeaderFields)
|
||||
{
|
||||
grid.Element().Stack(row =>
|
||||
{
|
||||
row.Item().AlignLeft().Text(field.Label, Typography.Normal.SemiBold());
|
||||
row.Item().Text(field.Value, Typography.Normal);
|
||||
grid.Item().Text(text =>
|
||||
{
|
||||
text.Span($"{field.Label}: ", Typography.Normal.SemiBold());
|
||||
text.Span(field.Value, Typography.Normal);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -75,6 +85,8 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
|
||||
stack.Item().Component(new TableOfContentsTemplate(Model.Sections));
|
||||
|
||||
stack.Item().PageBreak();
|
||||
|
||||
foreach (var section in Model.Sections)
|
||||
stack.Item().Location(section.Title).Component(new SectionTemplate(section));
|
||||
|
||||
|
||||
@@ -16,21 +16,21 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
public void Compose(IContainer container)
|
||||
{
|
||||
container
|
||||
.Decoration(section =>
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
section
|
||||
decoration
|
||||
.Header()
|
||||
.PaddingBottom(5)
|
||||
.Text("Table of contents", Typography.Headline);
|
||||
|
||||
section.Content().Stack(stack =>
|
||||
decoration.Content().Stack(stack =>
|
||||
{
|
||||
stack.Spacing(5);
|
||||
|
||||
for (var i = 0; i < Sections.Count; i++)
|
||||
stack.Item(c => DrawLink(c, i+1, Sections[i].Title));
|
||||
stack.Item().Element(c => DrawLink(c, i+1, Sections[i].Title));
|
||||
|
||||
stack.Item(c => DrawLink(c, Sections.Count+1, "Photos"));
|
||||
stack.Item().Element(c => DrawLink(c, Sections.Count+1, "Photos"));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -43,6 +43,7 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
{
|
||||
row.ConstantColumn(25).Text($"{number}.", Typography.Normal);
|
||||
row.RelativeColumn().Text(locationName, Typography.Normal);
|
||||
row.ConstantColumn(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName, Typography.Normal));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,11 @@ namespace QuestPDF.ReportSample
|
||||
[Test]
|
||||
public void PerformanceBenchmark()
|
||||
{
|
||||
// target document length should be around 100 pages
|
||||
|
||||
// test size
|
||||
const int testSize = 100;
|
||||
const decimal performanceTarget = 5; // documents per second
|
||||
const int testSize = 10;
|
||||
const decimal performanceTarget = 1; // documents per second
|
||||
|
||||
// create report models
|
||||
var reports = Enumerable
|
||||
@@ -55,8 +57,8 @@ namespace QuestPDF.ReportSample
|
||||
Console.WriteLine($"Time per document: {performance:N} ms");
|
||||
Console.WriteLine($"Documents per second: {speed:N} d/s");
|
||||
|
||||
if (speed < performanceTarget)
|
||||
throw new Exception("Rendering algorithm is too slow.");
|
||||
//if (speed < performanceTarget)
|
||||
// throw new Exception("Rendering algorithm is too slow.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,6 @@ namespace QuestPDF.ReportSample
|
||||
{
|
||||
public static TextStyle Title => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Darken3).Size(26).Black();
|
||||
public static TextStyle Headline => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Medium).Size(16).SemiBold();
|
||||
public static TextStyle Normal => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Black).Size(11).LineHeight(1.25f).AlignLeft();
|
||||
public static TextStyle Normal => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Black).Size(11).LineHeight(1.1f);
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,10 @@ using QuestPDF.UnitTests.TestEngine;
|
||||
namespace QuestPDF.UnitTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class WrapWhenLittleSpaceTests
|
||||
public class EnsureSpaceTests
|
||||
{
|
||||
[Test]
|
||||
public void Measure_ReturnsWrap_WhenNotEnoughSpace()
|
||||
public void Measure_ReturnsWrap_WhenChildReturnsWrap()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new EnsureSpace
|
||||
@@ -19,11 +19,54 @@ namespace QuestPDF.UnitTests
|
||||
MinHeight = 200
|
||||
})
|
||||
.MeasureElement(new Size(400, 100))
|
||||
.ExpectChildMeasure(new Size(400, 100), new Wrap())
|
||||
.CheckMeasureResult(new Wrap());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_Continues_WhenEnoughSpace()
|
||||
public void Measure_ReturnsWrap_WhenChildReturnsPartialRender_AndNotEnoughSpace()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new EnsureSpace
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
MinHeight = 200
|
||||
})
|
||||
.MeasureElement(new Size(400, 100))
|
||||
.ExpectChildMeasure(new Size(400, 100), new PartialRender(300, 50))
|
||||
.CheckMeasureResult(new Wrap());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsPartialRender_WhenChildReturnsPartialRender_AndEnoughSpace()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new EnsureSpace
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
MinHeight = 200
|
||||
})
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure(new Size(400, 300), new PartialRender(300, 250))
|
||||
.CheckMeasureResult(new PartialRender(300, 250));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsFullRender_WhenChildReturnsFullRender_AndNotEnoughSpace()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new EnsureSpace
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
MinHeight = 200
|
||||
})
|
||||
.MeasureElement(new Size(400, 100))
|
||||
.ExpectChildMeasure(new Size(400, 100), new FullRender(300, 50))
|
||||
.CheckMeasureResult(new FullRender(300, 50));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsFullRender_WhenChildReturnsFullRender_AndEnoughSpace()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new EnsureSpace
|
||||
|
||||
+117
-47
@@ -29,40 +29,40 @@ namespace QuestPDF.UnitTests
|
||||
{
|
||||
grid.AlignLeft();
|
||||
|
||||
grid.Element(6).Element(childA);
|
||||
grid.Element(4).Element(childB);
|
||||
grid.Element(4).Element(childC);
|
||||
grid.Element(2).Element(childD);
|
||||
grid.Element(8).Element(childE);
|
||||
grid.Item(6).Element(childA);
|
||||
grid.Item(4).Element(childB);
|
||||
grid.Item(4).Element(childC);
|
||||
grid.Item(2).Element(childD);
|
||||
grid.Item(8).Element(childE);
|
||||
});
|
||||
|
||||
// assert
|
||||
var expected = new Container();
|
||||
|
||||
expected.Stack(stack =>
|
||||
expected.Container().Stack(stack =>
|
||||
{
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(6).Element(childA);
|
||||
row.RelativeColumn(4).Element(childB);
|
||||
row.RelativeColumn(2).Element(new Empty());
|
||||
row.RelativeColumn(6).Container().Element(childA);
|
||||
row.RelativeColumn(4).Container().Element(childB);
|
||||
row.RelativeColumn(2);
|
||||
});
|
||||
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(4).Element(childC);
|
||||
row.RelativeColumn(2).Element(childD);
|
||||
row.RelativeColumn(6).Element(new Empty());
|
||||
row.RelativeColumn(4).Container().Element(childC);
|
||||
row.RelativeColumn(2).Container().Element(childD);
|
||||
row.RelativeColumn(6);
|
||||
});
|
||||
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(8).Element(childE);
|
||||
row.RelativeColumn(4).Element(new Empty());
|
||||
row.RelativeColumn(8).Container().Element(childE);
|
||||
row.RelativeColumn(4);
|
||||
});
|
||||
});
|
||||
|
||||
structure.Child.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering().IncludingAllRuntimeProperties());
|
||||
structure.Should().BeEquivalentTo(expected, o => o.WithTracing().WithAutoConversion().WithStrictOrdering().IncludingAllRuntimeProperties());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -83,43 +83,43 @@ namespace QuestPDF.UnitTests
|
||||
{
|
||||
grid.AlignCenter();
|
||||
|
||||
grid.Element(6).Element(childA);
|
||||
grid.Element(4).Element(childB);
|
||||
grid.Element(4).Element(childC);
|
||||
grid.Element(2).Element(childD);
|
||||
grid.Element(8).Element(childE);
|
||||
grid.Item(6).Element(childA);
|
||||
grid.Item(4).Element(childB);
|
||||
grid.Item(4).Element(childC);
|
||||
grid.Item(2).Element(childD);
|
||||
grid.Item(8).Element(childE);
|
||||
});
|
||||
|
||||
// assert
|
||||
var expected = new Container();
|
||||
|
||||
expected.Stack(stack =>
|
||||
expected.Container().Stack(stack =>
|
||||
{
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(1).Element(new Empty());
|
||||
row.RelativeColumn(6).Element(childA);
|
||||
row.RelativeColumn(4).Element(childB);
|
||||
row.RelativeColumn(1).Element(new Empty());
|
||||
row.RelativeColumn(1);
|
||||
row.RelativeColumn(6).Container().Element(childA);
|
||||
row.RelativeColumn(4).Container().Element(childB);
|
||||
row.RelativeColumn(1);
|
||||
});
|
||||
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(3).Element(new Empty());
|
||||
row.RelativeColumn(4).Element(childC);
|
||||
row.RelativeColumn(2).Element(childD);
|
||||
row.RelativeColumn(3).Element(new Empty());
|
||||
row.RelativeColumn(3);
|
||||
row.RelativeColumn(4).Container().Element(childC);
|
||||
row.RelativeColumn(2).Container().Element(childD);
|
||||
row.RelativeColumn(3);
|
||||
});
|
||||
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(2).Element(new Empty());
|
||||
row.RelativeColumn(8).Element(childE);
|
||||
row.RelativeColumn(2).Element(new Empty());
|
||||
row.RelativeColumn(2);
|
||||
row.RelativeColumn(8).Container().Element(childE);
|
||||
row.RelativeColumn(2);
|
||||
});
|
||||
});
|
||||
|
||||
structure.Child.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering().IncludingAllRuntimeProperties());
|
||||
structure.Should().BeEquivalentTo(expected, o => o.WithTracing().WithAutoConversion().WithStrictOrdering().IncludingAllRuntimeProperties());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -140,11 +140,11 @@ namespace QuestPDF.UnitTests
|
||||
{
|
||||
grid.AlignRight();
|
||||
|
||||
grid.Element(6).Element(childA);
|
||||
grid.Element(4).Element(childB);
|
||||
grid.Element(4).Element(childC);
|
||||
grid.Element(2).Element(childD);
|
||||
grid.Element(8).Element(childE);
|
||||
grid.Item(6).Element(childA);
|
||||
grid.Item(4).Element(childB);
|
||||
grid.Item(4).Element(childC);
|
||||
grid.Item(2).Element(childD);
|
||||
grid.Item(8).Element(childE);
|
||||
});
|
||||
|
||||
// assert
|
||||
@@ -154,26 +154,96 @@ namespace QuestPDF.UnitTests
|
||||
{
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(2).Element(new Empty());
|
||||
row.RelativeColumn(6).Element(childA);
|
||||
row.RelativeColumn(4).Element(childB);
|
||||
row.RelativeColumn(2);
|
||||
row.RelativeColumn(6).Container().Element(childA);
|
||||
row.RelativeColumn(4).Container().Element(childB);
|
||||
});
|
||||
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(6).Element(new Empty());
|
||||
row.RelativeColumn(4).Element(childC);
|
||||
row.RelativeColumn(2).Element(childD);
|
||||
row.RelativeColumn(6);
|
||||
row.RelativeColumn(4).Container().Element(childC);
|
||||
row.RelativeColumn(2).Container().Element(childD);
|
||||
});
|
||||
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(4).Element(new Empty());
|
||||
row.RelativeColumn(8).Element(childE);
|
||||
row.RelativeColumn(4);
|
||||
row.RelativeColumn(8).Container().Element(childE);
|
||||
});
|
||||
});
|
||||
|
||||
structure.Child.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering().IncludingAllRuntimeProperties());
|
||||
structure.Should().BeEquivalentTo(expected, o => o.WithTracing().WithAutoConversion().WithStrictOrdering().IncludingAllRuntimeProperties());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Spacing
|
||||
|
||||
[Test]
|
||||
public void Spacing()
|
||||
{
|
||||
// arrange
|
||||
var structure = new Container();
|
||||
|
||||
var childA = TestPlan.CreateUniqueElement();
|
||||
var childB = TestPlan.CreateUniqueElement();
|
||||
var childC = TestPlan.CreateUniqueElement();
|
||||
var childD = TestPlan.CreateUniqueElement();
|
||||
|
||||
// act
|
||||
structure
|
||||
.Grid(grid =>
|
||||
{
|
||||
grid.Columns(16);
|
||||
grid.AlignCenter();
|
||||
|
||||
grid.VerticalSpacing(20);
|
||||
grid.HorizontalSpacing(30);
|
||||
|
||||
grid.Item(5).Element(childA);
|
||||
grid.Item(5).Element(childB);
|
||||
grid.Item(10).Element(childC);
|
||||
grid.Item(12).Element(childD);
|
||||
});
|
||||
|
||||
// assert
|
||||
var expected = new Container();
|
||||
|
||||
expected.Container().Stack(stack =>
|
||||
{
|
||||
stack.Spacing(20);
|
||||
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.Spacing(30);
|
||||
|
||||
row.RelativeColumn(3);
|
||||
row.RelativeColumn(5).Container().Element(childA);
|
||||
row.RelativeColumn(5).Container().Element(childB);
|
||||
row.RelativeColumn(3);
|
||||
});
|
||||
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.Spacing(30);
|
||||
|
||||
row.RelativeColumn(3);
|
||||
row.RelativeColumn(10).Container().Element(childC);
|
||||
row.RelativeColumn(3);
|
||||
});
|
||||
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.Spacing(30);
|
||||
|
||||
row.RelativeColumn(2);
|
||||
row.RelativeColumn(12).Container().Element(childD);
|
||||
row.RelativeColumn(2);
|
||||
});
|
||||
});
|
||||
|
||||
structure.Should().BeEquivalentTo(expected, o => o.WithTracing().WithAutoConversion().WithStrictOrdering().IncludingAllRuntimeProperties().AllowingInfiniteRecursion());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace QuestPDF.UnitTests
|
||||
.For(x => new PageBreak())
|
||||
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.CheckMeasureResult(new PartialRender(400, 300))
|
||||
.CheckMeasureResult(new PartialRender(Size.Zero))
|
||||
|
||||
.DrawElement(new Size(400, 300))
|
||||
.CheckDrawResult()
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.UnitTests.TestEngine;
|
||||
|
||||
namespace QuestPDF.UnitTests
|
||||
@@ -10,103 +9,108 @@ namespace QuestPDF.UnitTests
|
||||
[TestFixture]
|
||||
public class RowTests
|
||||
{
|
||||
#region Spacing
|
||||
#region Measure
|
||||
|
||||
[Test]
|
||||
public void Fluent_WithoutSpacing()
|
||||
public void Measure_ReturnsWrap_WhenLeftChildReturnsWrap()
|
||||
{
|
||||
// arrange
|
||||
var structure = new Container();
|
||||
|
||||
var childA = TestPlan.CreateUniqueElement();
|
||||
var childB = TestPlan.CreateUniqueElement();
|
||||
var childC = TestPlan.CreateUniqueElement();
|
||||
|
||||
// act
|
||||
structure
|
||||
.Row(stack =>
|
||||
TestPlan
|
||||
.For(x => new SimpleRow
|
||||
{
|
||||
stack.Spacing(0);
|
||||
|
||||
stack.ConstantColumn(100).Element(childA);
|
||||
stack.RelativeColumn(2).Element(childB);
|
||||
stack.RelativeColumn(3).Element(childC);
|
||||
});
|
||||
|
||||
// assert
|
||||
var expected = new Row()
|
||||
{
|
||||
Children = new List<RowElement>
|
||||
{
|
||||
new ConstantRowElement(100)
|
||||
{
|
||||
Child = childA
|
||||
},
|
||||
new RelativeRowElement(2)
|
||||
{
|
||||
Child = childB
|
||||
},
|
||||
new RelativeRowElement(3)
|
||||
{
|
||||
Child = childC
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
structure.Child.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering().IncludingAllRuntimeProperties());
|
||||
Left = x.CreateChild("left"),
|
||||
Right = x.CreateChild("right")
|
||||
})
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("left", new Size(400, 300), new Wrap())
|
||||
.CheckMeasureResult(new Wrap());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Fluent_WithSpacing()
|
||||
public void Measure_ReturnsWrap_WhenRightChildReturnsWrap()
|
||||
{
|
||||
// arrange
|
||||
var structure = new Container();
|
||||
|
||||
var childA = TestPlan.CreateUniqueElement();
|
||||
var childB = TestPlan.CreateUniqueElement();
|
||||
var childC = TestPlan.CreateUniqueElement();
|
||||
|
||||
// act
|
||||
structure
|
||||
.Row(stack =>
|
||||
TestPlan
|
||||
.For(x => new SimpleRow
|
||||
{
|
||||
stack.Spacing(25);
|
||||
|
||||
stack.ConstantColumn(100).Element(childA);
|
||||
stack.RelativeColumn(2).Element(childB);
|
||||
stack.RelativeColumn(3).Element(childC);
|
||||
});
|
||||
|
||||
// assert
|
||||
var expected = new Padding
|
||||
{
|
||||
Right = -25,
|
||||
Child = new Row()
|
||||
Left = x.CreateChild("left"),
|
||||
Right = x.CreateChild("right")
|
||||
})
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("left", new Size(400, 300), new FullRender(250, 150))
|
||||
.ExpectChildMeasure("right", new Size(150, 300), new Wrap())
|
||||
.CheckMeasureResult(new Wrap());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsPartialRender_WhenLeftChildReturnsPartialRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleRow
|
||||
{
|
||||
Children = new List<RowElement>
|
||||
{
|
||||
new ConstantRowElement(100)
|
||||
{
|
||||
Child = childA
|
||||
},
|
||||
new ConstantRowElement(25),
|
||||
new RelativeRowElement(2)
|
||||
{
|
||||
Child = childB
|
||||
},
|
||||
new ConstantRowElement(25),
|
||||
new RelativeRowElement(3)
|
||||
{
|
||||
Child = childC
|
||||
},
|
||||
new ConstantRowElement(25)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
structure.Child.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering().IncludingAllRuntimeProperties());
|
||||
Left = x.CreateChild("left"),
|
||||
Right = x.CreateChild("right")
|
||||
})
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("left", new Size(400, 300), new PartialRender(250, 150))
|
||||
.ExpectChildMeasure("right", new Size(150, 300), new FullRender(100, 100))
|
||||
.CheckMeasureResult(new PartialRender(350, 150));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsPartialRender_WhenRightChildReturnsPartialRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleRow
|
||||
{
|
||||
Left = x.CreateChild("left"),
|
||||
Right = x.CreateChild("right")
|
||||
})
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("left", new Size(400, 300), new FullRender(250, 150))
|
||||
.ExpectChildMeasure("right", new Size(150, 300), new PartialRender(100, 100))
|
||||
.CheckMeasureResult(new PartialRender(350, 150));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsFullRender_WhenBothChildrenReturnFullRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleRow
|
||||
{
|
||||
Left = x.CreateChild("left"),
|
||||
Right = x.CreateChild("right")
|
||||
})
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("left", new Size(400, 300), new FullRender(200, 150))
|
||||
.ExpectChildMeasure("right", new Size(200, 300), new FullRender(100, 100))
|
||||
.CheckMeasureResult(new FullRender(300, 150));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Draw
|
||||
|
||||
[Test]
|
||||
public void Draw()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleRow
|
||||
{
|
||||
Left = x.CreateChild("left"),
|
||||
Right = x.CreateChild("right")
|
||||
})
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("left", new Size(400, 300), new FullRender(250, 150))
|
||||
.ExpectChildDraw("left", new Size(250, 300))
|
||||
.ExpectCanvasTranslate(250, 0)
|
||||
.ExpectChildDraw("right", new Size(150, 300))
|
||||
.ExpectCanvasTranslate(-250, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// TODO: add tests for the spacing property
|
||||
// TODO: add tests for the tree builder method
|
||||
// TODO: add tests for relative column
|
||||
}
|
||||
}
|
||||
@@ -30,10 +30,10 @@ namespace QuestPDF.UnitTests
|
||||
Child = child.Object
|
||||
};
|
||||
|
||||
element.Draw(null, Size.Zero);
|
||||
element.Draw(null, Size.Zero);
|
||||
element.Draw(Size.Zero);
|
||||
element.Draw(Size.Zero);
|
||||
|
||||
child.Verify(x => x.Draw(It.IsAny<ICanvas>(), It.IsAny<Size>()), Times.Once);
|
||||
child.Verify(x => x.Draw(It.IsAny<Size>()), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
+188
-243
@@ -1,9 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.UnitTests.TestEngine;
|
||||
|
||||
@@ -13,278 +10,226 @@ namespace QuestPDF.UnitTests
|
||||
public class StackTests
|
||||
{
|
||||
#region Measure
|
||||
|
||||
|
||||
[Test]
|
||||
public void Measure_NoChildren_Empty()
|
||||
public void Measure_ReturnsWrap_WhenFirstChildWraps()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Stack())
|
||||
.MeasureElement(new Size(500, 1000))
|
||||
.CheckMeasureResult(new FullRender(Size.Zero));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsWrap_WhenPageable_AndAnyChildReturnsWrap()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Stack
|
||||
.For(x => new SimpleStack
|
||||
{
|
||||
Children = new []
|
||||
{
|
||||
x.CreateChild("a"),
|
||||
x.CreateChild("b"),
|
||||
x.CreateChild("c"),
|
||||
x.CreateChild("d")
|
||||
}
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
})
|
||||
.MeasureElement(new Size(500, 1000))
|
||||
.ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new FullRender(500, 200))
|
||||
.ExpectChildMeasure("b", expectedInput: new Size(500, 800), returns: new FullRender(500, 300))
|
||||
.ExpectChildMeasure("c", expectedInput: new Size(500, 500), returns: new Wrap())
|
||||
.CheckMeasureResult(new PartialRender(500, 500));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsWrap_WhenPageable_AndFirstChildReturnsWrap()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Stack
|
||||
{
|
||||
Children = new []
|
||||
{
|
||||
x.CreateChild("a"),
|
||||
x.CreateChild("b"),
|
||||
x.CreateChild("c")
|
||||
}
|
||||
})
|
||||
.MeasureElement(new Size(500, 1000))
|
||||
.ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new Wrap())
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("first", new Size(400, 300), new Wrap())
|
||||
.CheckMeasureResult(new Wrap());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Combined
|
||||
|
||||
[Test]
|
||||
public void WhenPageable_AndChildReturnsWrap()
|
||||
public void Measure_ReturnsPartialRender_WhenFirstChildReturnsPartialRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Stack
|
||||
.For(x => new SimpleStack
|
||||
{
|
||||
Children = new[]
|
||||
{
|
||||
x.CreateChild("a"),
|
||||
x.CreateChild("b"),
|
||||
x.CreateChild("c")
|
||||
}
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
})
|
||||
|
||||
// page 1: measure
|
||||
.MeasureElement(new Size(500, 1000))
|
||||
.ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new FullRender(400, 200))
|
||||
.ExpectChildMeasure("b", expectedInput: new Size(500, 800), returns: new Wrap())
|
||||
.CheckMeasureResult(new PartialRender(500, 200))
|
||||
|
||||
// page 1: draw
|
||||
.DrawElement(new Size(500, 1000))
|
||||
|
||||
.ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new FullRender(400, 200))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw("a", new Size(500, 200))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
|
||||
.ExpectChildMeasure("b", expectedInput: new Size(500, 800), returns: new Wrap())
|
||||
|
||||
.CheckDrawResult()
|
||||
|
||||
// // page 2: measure
|
||||
.MeasureElement(new Size(500, 900))
|
||||
.ExpectChildMeasure("b", expectedInput: new Size(500, 900), returns: new FullRender(300, 200))
|
||||
.ExpectChildMeasure("c", expectedInput: new Size(500, 700), returns: new FullRender(300, 300))
|
||||
.CheckMeasureResult(new FullRender(500, 500))
|
||||
|
||||
// page 2: draw
|
||||
.DrawElement(new Size(500, 900))
|
||||
|
||||
.ExpectChildMeasure("b", expectedInput: new Size(500, 900), returns: new FullRender(300, 200))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw("b", new Size(500, 200))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
|
||||
.ExpectChildMeasure("c", expectedInput: new Size(500, 700), returns: new FullRender(300, 300))
|
||||
.ExpectCanvasTranslate(0, 200)
|
||||
.ExpectChildDraw("c", new Size(500, 300))
|
||||
.ExpectCanvasTranslate(0, -200)
|
||||
|
||||
.CheckDrawResult();
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("first", new Size(400, 300), new PartialRender(300, 200))
|
||||
.CheckMeasureResult(new PartialRender(300, 200));
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void WhenPageable_AndChildReturnsPartialRender()
|
||||
public void Measure_ReturnsPartialRender_WhenSecondChildWraps()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Stack
|
||||
.For(x => new SimpleStack
|
||||
{
|
||||
Children = new[]
|
||||
{
|
||||
x.CreateChild("a"),
|
||||
x.CreateChild("b"),
|
||||
x.CreateChild("c")
|
||||
}
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
})
|
||||
|
||||
// page 1: measure
|
||||
.MeasureElement(new Size(500, 1000))
|
||||
.ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new FullRender(400, 200))
|
||||
.ExpectChildMeasure("b", expectedInput: new Size(500, 800), returns: new PartialRender(300, 300))
|
||||
.CheckMeasureResult(new PartialRender(500, 500))
|
||||
|
||||
// page 1: draw
|
||||
.DrawElement(new Size(500, 1000))
|
||||
|
||||
.ExpectChildMeasure("a", expectedInput: new Size(500, 1000), returns: new FullRender(400, 200))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw("a", new Size(500, 200))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
|
||||
.ExpectChildMeasure("b", expectedInput: new Size(500, 800), returns: new PartialRender(300, 300))
|
||||
.ExpectCanvasTranslate(0, 200)
|
||||
.ExpectChildDraw("b", new Size(500, 300))
|
||||
.ExpectCanvasTranslate(0, -200)
|
||||
|
||||
.CheckDrawResult()
|
||||
|
||||
// page 2: measure
|
||||
.MeasureElement(new Size(500, 900))
|
||||
.ExpectChildMeasure("b", expectedInput: new Size(500, 900), returns: new FullRender(300, 200))
|
||||
.ExpectChildMeasure("c", expectedInput: new Size(500, 700), returns: new FullRender(300, 300))
|
||||
.CheckMeasureResult(new FullRender(500, 500))
|
||||
|
||||
// page 2: draw
|
||||
.DrawElement(new Size(500, 900))
|
||||
|
||||
.ExpectChildMeasure("b", expectedInput: new Size(500, 900), returns: new FullRender(300, 200))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw("b", new Size(500, 200))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
|
||||
.ExpectChildMeasure("c", expectedInput: new Size(500, 700), returns: new FullRender(300, 300))
|
||||
.ExpectCanvasTranslate(0, 200)
|
||||
.ExpectChildDraw("c", new Size(500, 300))
|
||||
.ExpectCanvasTranslate(0, -200)
|
||||
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("first", new Size(400, 300), new FullRender(200, 100))
|
||||
.ExpectChildMeasure("second", new Size(400, 200), new Wrap())
|
||||
.CheckMeasureResult(new PartialRender(200, 100));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsPartialRender_WhenSecondChildReturnsPartialRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
})
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("first", new Size(400, 300), new FullRender(200, 100))
|
||||
.ExpectChildMeasure("second", new Size(400, 200), new PartialRender(300, 150))
|
||||
.CheckMeasureResult(new PartialRender(300, 250));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsFullRender_WhenSecondChildReturnsFullRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
})
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("first", new Size(400, 300), new FullRender(200, 100))
|
||||
.ExpectChildMeasure("second", new Size(400, 200), new FullRender(100, 50))
|
||||
.CheckMeasureResult(new FullRender(200, 150));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_UsesEmpty_WhenFirstChildIsRendered()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second"),
|
||||
|
||||
IsFirstRendered = true
|
||||
})
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("second", new Size(400, 300), new FullRender(200, 300))
|
||||
.CheckMeasureResult(new FullRender(200, 300));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Draw
|
||||
|
||||
[Test]
|
||||
public void Draw_WhenFirstChildWraps()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
})
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("first", new Size(400, 300), new Wrap())
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Spacing
|
||||
|
||||
[Test]
|
||||
public void Fluent_WithoutSpacing()
|
||||
public void Draw_WhenFirstChildPartiallyRenders()
|
||||
{
|
||||
// arrange
|
||||
var structure = new Container();
|
||||
|
||||
var childA = TestPlan.CreateUniqueElement();
|
||||
var childB = TestPlan.CreateUniqueElement();
|
||||
var childC = TestPlan.CreateUniqueElement();
|
||||
|
||||
// act
|
||||
structure
|
||||
.Stack(stack =>
|
||||
TestPlan
|
||||
.For(x => new SimpleStack
|
||||
{
|
||||
stack.Spacing(0);
|
||||
|
||||
stack.Item().Element(childA);
|
||||
stack.Item().Element(childB);
|
||||
stack.Item().Element(childC);
|
||||
});
|
||||
|
||||
// assert
|
||||
var expected = new Stack
|
||||
{
|
||||
Children = new List<Element>
|
||||
{
|
||||
new Container
|
||||
{
|
||||
Child = childA
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Child = childB
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Child = childC
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
structure.Child.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering().IncludingAllRuntimeProperties());
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
})
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("first", new Size(400, 300), new PartialRender(200, 100))
|
||||
.ExpectChildDraw("first", new Size(400, 100))
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Fluent_WithSpacing()
|
||||
public void Draw_WhenFirstChildFullyRenders_AndSecondChildWraps()
|
||||
{
|
||||
// arrange
|
||||
var structure = new Container();
|
||||
|
||||
var childA = TestPlan.CreateUniqueElement();
|
||||
var childB = TestPlan.CreateUniqueElement();
|
||||
var childC = TestPlan.CreateUniqueElement();
|
||||
|
||||
// act
|
||||
structure
|
||||
.Stack(stack =>
|
||||
TestPlan
|
||||
.For(x => new SimpleStack
|
||||
{
|
||||
stack.Spacing(100);
|
||||
|
||||
stack.Item().Element(childA);
|
||||
stack.Item().Element(childB);
|
||||
stack.Item().Element(childC);
|
||||
});
|
||||
|
||||
// assert
|
||||
var expected = new Padding
|
||||
{
|
||||
Bottom = -100,
|
||||
Child = new Stack
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
})
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("first", new Size(400, 300), new FullRender(200, 100))
|
||||
.ExpectChildDraw("first", new Size(400, 100))
|
||||
.ExpectChildMeasure("second", new Size(400, 200), new Wrap())
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Draw_WhenFirstChildFullyRenders_AndSecondChildPartiallyRenders()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleStack
|
||||
{
|
||||
Children = new List<Element>
|
||||
{
|
||||
new Padding
|
||||
{
|
||||
Bottom = 100,
|
||||
Child = new Container
|
||||
{
|
||||
Child = childA
|
||||
}
|
||||
},
|
||||
new Padding
|
||||
{
|
||||
Bottom = 100,
|
||||
Child = new Container
|
||||
{
|
||||
Child = childB
|
||||
}
|
||||
},
|
||||
new Padding
|
||||
{
|
||||
Bottom = 100,
|
||||
Child = new Container
|
||||
{
|
||||
Child = childC
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
structure.Child.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering().IncludingAllRuntimeProperties());
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
})
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("first", new Size(400, 300), new FullRender(200, 100))
|
||||
.ExpectChildDraw("first", new Size(400, 100))
|
||||
.ExpectChildMeasure("second", new Size(400, 200), new PartialRender(250, 150))
|
||||
.ExpectCanvasTranslate(0, 100)
|
||||
.ExpectChildDraw("second", new Size(400, 150))
|
||||
.ExpectCanvasTranslate(0, -100)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Draw_WhenFirstChildFullyRenders_AndSecondChildFullyRenders()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second")
|
||||
})
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("first", new Size(400, 300), new FullRender(200, 100))
|
||||
.ExpectChildDraw("first", new Size(400, 100))
|
||||
.ExpectChildMeasure("second", new Size(400, 200), new FullRender(250, 150))
|
||||
.ExpectCanvasTranslate(0, 100)
|
||||
.ExpectChildDraw("second", new Size(400, 150))
|
||||
.ExpectCanvasTranslate(0, -100)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Draw_UsesEmpty_WhenFirstChildIsRendered()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second"),
|
||||
|
||||
IsFirstRendered = true
|
||||
})
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("second", new Size(400, 300), new PartialRender(200, 300))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw("second", new Size(400, 300))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.CheckState<SimpleStack>(x => x.IsFirstRendered)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Draw_TogglesFirstRenderedFlag_WhenSecondFullyRenders()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new SimpleStack
|
||||
{
|
||||
First = x.CreateChild("first"),
|
||||
Second = x.CreateChild("second"),
|
||||
|
||||
IsFirstRendered = true
|
||||
})
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("second", new Size(400, 300), new FullRender(200, 300))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw("second", new Size(400, 300))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.CheckDrawResult()
|
||||
.CheckState<SimpleStack>(x => !x.IsFirstRendered);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// TODO: add tests for the spacing property
|
||||
// TODO: add tests for the tree builder method
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,12 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
public Action<SKImage, Position, Size> DrawImageFunc { get; set; }
|
||||
public Action<string, Position, TextStyle> DrawTextFunc { get; set; }
|
||||
public Action<Position, Size, string> DrawRectFunc { get; set; }
|
||||
|
||||
|
||||
public void Translate(Position vector) => TranslateFunc(vector);
|
||||
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();
|
||||
|
||||
@@ -11,6 +11,6 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
public Action<Size> DrawFunc { get; set; }
|
||||
|
||||
internal override ISpacePlan Measure(Size availableSpace) => MeasureFunc(availableSpace);
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace) => DrawFunc(availableSpace);
|
||||
internal override void Draw(Size availableSpace) => DrawFunc(availableSpace);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using System.Text.Json;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.UnitTests.TestEngine.Operations;
|
||||
|
||||
@@ -49,9 +50,9 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
TranslateFunc = position =>
|
||||
{
|
||||
var expected = GetExpected<CanvasTranslateOperationBase>();
|
||||
|
||||
Assert.AreEqual(expected.Position.X, position.X);
|
||||
Assert.AreEqual(expected.Position.Y, position.Y);
|
||||
|
||||
Assert.AreEqual(expected.Position.X, position.X, "Translate X");
|
||||
Assert.AreEqual(expected.Position.Y, position.Y, "Translate Y");
|
||||
|
||||
//position.Should().BeEquivalentTo(expected.Position);
|
||||
},
|
||||
@@ -59,13 +60,13 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
{
|
||||
var expected = GetExpected<CanvasDrawRectangleOperationBase>();
|
||||
|
||||
Assert.AreEqual(expected.Position.X, position.X);
|
||||
Assert.AreEqual(expected.Position.Y, position.Y);
|
||||
Assert.AreEqual(expected.Position.X, position.X, "Draw rectangle: X");
|
||||
Assert.AreEqual(expected.Position.Y, position.Y, "Draw rectangle: Y");
|
||||
|
||||
Assert.AreEqual(expected.Size.Width, size.Width);
|
||||
Assert.AreEqual(expected.Size.Height, size.Height);
|
||||
Assert.AreEqual(expected.Size.Width, size.Width, "Draw rectangle: width");
|
||||
Assert.AreEqual(expected.Size.Height, size.Height, "Draw rectangle: height");
|
||||
|
||||
Assert.AreEqual(expected.Color, color);
|
||||
Assert.AreEqual(expected.Color, color, "Draw rectangle: color");
|
||||
|
||||
/*position.Should().BeEquivalentTo(expected.Position);
|
||||
size.Should().BeEquivalentTo(expected.Size);
|
||||
@@ -77,12 +78,12 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
|
||||
Assert.AreEqual(expected.Text, text);
|
||||
|
||||
Assert.AreEqual(expected.Position.X, position.X);
|
||||
Assert.AreEqual(expected.Position.Y, position.Y);
|
||||
Assert.AreEqual(expected.Position.X, position.X, "Draw text: X");
|
||||
Assert.AreEqual(expected.Position.Y, position.Y, "Draw text: Y");
|
||||
|
||||
Assert.AreEqual(expected.Style.Color, style.Color);
|
||||
Assert.AreEqual(expected.Style.FontType, style.FontType);
|
||||
Assert.AreEqual(expected.Style.Size, style.Size);
|
||||
Assert.AreEqual(expected.Style.Color, style.Color, "Draw text: color");
|
||||
Assert.AreEqual(expected.Style.FontType, style.FontType, "Draw text: font");
|
||||
Assert.AreEqual(expected.Style.Size, style.Size, "Draw text: size");
|
||||
|
||||
/*text.Should().Be(expected.Text);
|
||||
position.Should().BeEquivalentTo(expected.Position);
|
||||
@@ -92,11 +93,11 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
{
|
||||
var expected = GetExpected<CanvasDrawImageOperationBase>();
|
||||
|
||||
Assert.AreEqual(expected.Position.X, position.X);
|
||||
Assert.AreEqual(expected.Position.Y, position.Y);
|
||||
Assert.AreEqual(expected.Position.X, position.X, "Draw image: X");
|
||||
Assert.AreEqual(expected.Position.Y, position.Y, "Draw image: Y");
|
||||
|
||||
Assert.AreEqual(expected.Size.Width, size.Width);
|
||||
Assert.AreEqual(expected.Size.Height, size.Height);
|
||||
Assert.AreEqual(expected.Size.Width, size.Width, "Draw image: width");
|
||||
Assert.AreEqual(expected.Size.Height, size.Height, "Draw image: height");
|
||||
|
||||
/*position.Should().BeEquivalentTo(expected.Position);
|
||||
size.Should().BeEquivalentTo(expected.Size);*/
|
||||
@@ -117,8 +118,8 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
|
||||
Assert.AreEqual(expected.ChildId, id);
|
||||
|
||||
Assert.AreEqual(expected.Input.Width, availableSpace.Width);
|
||||
Assert.AreEqual(expected.Input.Height, availableSpace.Height);
|
||||
Assert.AreEqual(expected.Input.Width, availableSpace.Width, $"Measure: width of child '{expected.ChildId}'");
|
||||
Assert.AreEqual(expected.Input.Height, availableSpace.Height, $"Measure: height of child '{expected.ChildId}'");
|
||||
|
||||
// id.Should().Be(expected.ChildId);
|
||||
// availableSpace.Should().Be(expected.Input);
|
||||
@@ -131,8 +132,8 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
|
||||
Assert.AreEqual(expected.ChildId, id);
|
||||
|
||||
Assert.AreEqual(expected.Input.Width, availableSpace.Width);
|
||||
Assert.AreEqual(expected.Input.Height, availableSpace.Height);
|
||||
Assert.AreEqual(expected.Input.Width, availableSpace.Width, $"Draw: width of child '{expected.ChildId}'");
|
||||
Assert.AreEqual(expected.Input.Height, availableSpace.Height, $"Draw: width of child '{expected.ChildId}'");
|
||||
|
||||
/*id.Should().Be(expected.ChildId);
|
||||
availableSpace.Should().Be(expected.Input);*/
|
||||
@@ -205,6 +206,8 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
|
||||
public TestPlan CheckMeasureResult(ISpacePlan expected)
|
||||
{
|
||||
Element.HandleVisitor(x => x?.Initialize(null, Canvas));
|
||||
|
||||
var actual = Element.Measure(OperationInput);
|
||||
|
||||
Assert.AreEqual(expected.GetType(), actual.GetType());
|
||||
@@ -214,8 +217,8 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
|
||||
if (expectedSize != null)
|
||||
{
|
||||
Assert.AreEqual(expectedSize.Width, actualSize.Width);
|
||||
Assert.AreEqual(expectedSize.Height, actualSize.Height);
|
||||
Assert.AreEqual(expectedSize.Width, actualSize.Width, "Measure: width");
|
||||
Assert.AreEqual(expectedSize.Height, actualSize.Height, "Measure: height");
|
||||
}
|
||||
|
||||
return this;
|
||||
@@ -223,15 +226,29 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
|
||||
public TestPlan CheckDrawResult()
|
||||
{
|
||||
Element.Draw(Canvas, OperationInput);
|
||||
Element.HandleVisitor(x => x?.Initialize(null, Canvas));
|
||||
Element.Draw(OperationInput);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestPlan CheckState(Func<Element, bool> condition)
|
||||
{
|
||||
Assert.IsTrue(condition(Element), "Checking condition");
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestPlan CheckState<T>(Func<T, bool> condition) where T : Element
|
||||
{
|
||||
Assert.IsTrue(Element is T);
|
||||
Assert.IsTrue(condition(Element as T), "Checking condition");
|
||||
return this;
|
||||
}
|
||||
|
||||
public static Element CreateUniqueElement()
|
||||
{
|
||||
return new Text
|
||||
return new DynamicImage
|
||||
{
|
||||
Value = Guid.NewGuid().ToString("N")
|
||||
Source = Placeholders.Image
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
internal class Canvas : ICanvas
|
||||
{
|
||||
internal SKCanvas SkiaCanvas { get; }
|
||||
|
||||
public Canvas(SKCanvas skiaCanvas)
|
||||
{
|
||||
SkiaCanvas = skiaCanvas;
|
||||
}
|
||||
|
||||
~Canvas()
|
||||
{
|
||||
SkiaCanvas.Dispose();
|
||||
}
|
||||
|
||||
public void Translate(Position vector)
|
||||
{
|
||||
SkiaCanvas.Translate(vector.X, vector.Y);
|
||||
}
|
||||
|
||||
public void DrawRectangle(Position vector, Size size, string color)
|
||||
{
|
||||
if (size.Width < Size.Epsilon || size.Height < Size.Epsilon)
|
||||
return;
|
||||
|
||||
var paint = color.ColorToPaint();
|
||||
SkiaCanvas.DrawRect(vector.X, vector.Y, size.Width, size.Height, paint);
|
||||
}
|
||||
|
||||
public void DrawText(string text, Position vector, TextStyle style)
|
||||
{
|
||||
SkiaCanvas.DrawText(text, vector.X, vector.Y, style.ToPaint());
|
||||
}
|
||||
|
||||
public void DrawImage(SKImage image, Position vector, Size size)
|
||||
{
|
||||
SkiaCanvas.DrawImage(image, new SKRect(vector.X, vector.Y, size.Width, size.Height));
|
||||
}
|
||||
|
||||
public void DrawExternalLink(string url, Size size)
|
||||
{
|
||||
SkiaCanvas.DrawUrlAnnotation(new SKRect(0, 0, size.Width, size.Height), url);
|
||||
}
|
||||
|
||||
public void DrawLocationLink(string locationName, Size size)
|
||||
{
|
||||
SkiaCanvas.DrawLinkDestinationAnnotation(new SKRect(0, 0, size.Width, size.Height), locationName);
|
||||
}
|
||||
|
||||
public void DrawLocation(string locationName)
|
||||
{
|
||||
SkiaCanvas.DrawNamedDestinationAnnotation(new SKPoint(0, 0), locationName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Concurrent;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
@@ -7,7 +6,8 @@ namespace QuestPDF.Drawing
|
||||
{
|
||||
internal static class CanvasCache
|
||||
{
|
||||
private static ConcurrentDictionary<string, SKPaint> Paints = new ConcurrentDictionary<string, SKPaint>();
|
||||
internal static ConcurrentDictionary<string, SKPaint> Paints = new ConcurrentDictionary<string, SKPaint>();
|
||||
private static ConcurrentDictionary<string, SKFontMetrics> FontMetrics = new ConcurrentDictionary<string, SKFontMetrics>();
|
||||
private static ConcurrentDictionary<string, SKPaint> ColorPaint = new ConcurrentDictionary<string, SKPaint>();
|
||||
|
||||
internal static SKPaint ColorToPaint(this string color)
|
||||
@@ -36,28 +36,14 @@ namespace QuestPDF.Drawing
|
||||
Color = SKColor.Parse(style.Color),
|
||||
Typeface = SKTypeface.FromFamilyName(style.FontType, (int)style.FontWeight, (int)SKFontStyleWidth.Normal, slant),
|
||||
TextSize = style.Size,
|
||||
TextEncoding = SKTextEncoding.Utf32,
|
||||
|
||||
TextAlign = style.Alignment switch
|
||||
{
|
||||
HorizontalAlignment.Left => SKTextAlign.Left,
|
||||
HorizontalAlignment.Center => SKTextAlign.Center,
|
||||
HorizontalAlignment.Right => SKTextAlign.Right,
|
||||
_ => SKTextAlign.Left
|
||||
}
|
||||
TextEncoding = SKTextEncoding.Utf32
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal static TextMeasurement BreakText(this TextStyle style, string text, float availableWidth)
|
||||
internal static SKFontMetrics ToFontMetrics(this TextStyle style)
|
||||
{
|
||||
var index = (int)style.ToPaint().BreakText(text, availableWidth, out var width);
|
||||
|
||||
return new TextMeasurement()
|
||||
{
|
||||
LineIndex = index,
|
||||
FragmentWidth = width
|
||||
};
|
||||
return FontMetrics.GetOrAdd(style.ToString(), key => style.ToPaint().FontMetrics);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
internal class DocumentContainer : IDocumentContainer
|
||||
{
|
||||
internal List<IComponent> Pages { get; set; } = new List<IComponent>();
|
||||
|
||||
internal Container Compose()
|
||||
{
|
||||
var container = new Container();
|
||||
|
||||
container
|
||||
.Stack(stack =>
|
||||
{
|
||||
Pages
|
||||
.SelectMany(x => new List<Action>()
|
||||
{
|
||||
() => stack.Item().PageBreak(),
|
||||
() => stack.Item().Component(x)
|
||||
})
|
||||
.Skip(1)
|
||||
.ToList()
|
||||
.ForEach(x => x());
|
||||
});
|
||||
|
||||
return container;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,8 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
@@ -14,115 +12,78 @@ namespace QuestPDF.Drawing
|
||||
{
|
||||
internal static void GeneratePdf(Stream stream, IDocument document)
|
||||
{
|
||||
var content = ElementExtensions.Create(document.Compose);
|
||||
var metadata = document.GetMetadata();
|
||||
var canvas = new PdfCanvas(stream, metadata);
|
||||
RenderDocument(canvas, document);
|
||||
}
|
||||
|
||||
internal static ICollection<byte[]> GenerateImages(IDocument document)
|
||||
{
|
||||
var metadata = document.GetMetadata();
|
||||
var canvas = new ImageCanvas(metadata);
|
||||
RenderDocument(canvas, document);
|
||||
|
||||
return canvas.Images;
|
||||
}
|
||||
|
||||
private static void RenderDocument<TCanvas>(TCanvas canvas, IDocument document)
|
||||
where TCanvas : ICanvas, IRenderingCanvas
|
||||
{
|
||||
var container = new DocumentContainer();
|
||||
document.Compose(container);
|
||||
var content = container.Compose();
|
||||
|
||||
using var pdf = SKDocument.CreatePdf(stream, MapMetadata(metadata));
|
||||
var totalPages = 1;
|
||||
var metadata = document.GetMetadata();
|
||||
var pageContext = new PageContext();
|
||||
|
||||
RenderPass(pageContext, new FreeCanvas(), content, metadata);
|
||||
RenderPass(pageContext, canvas, content, metadata);
|
||||
}
|
||||
|
||||
private static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DocumentMetadata documentMetadata)
|
||||
where TCanvas : ICanvas, IRenderingCanvas
|
||||
{
|
||||
content.HandleVisitor(x => x?.Initialize(pageContext, canvas));
|
||||
content.HandleVisitor(x => (x as IStateResettable)?.ResetState());
|
||||
|
||||
canvas.BeginDocument();
|
||||
|
||||
var currentPage = 1;
|
||||
|
||||
while(true)
|
||||
{
|
||||
var spacePlan = content.Measure(metadata.Size);
|
||||
pageContext.SetPageNumber(currentPage);
|
||||
var spacePlan = content.Measure(Size.Max) as Size;
|
||||
|
||||
using var skiaCanvas = pdf.BeginPage(metadata.Size.Width, metadata.Size.Height);
|
||||
var canvas = new Canvas(skiaCanvas);
|
||||
if (spacePlan == null)
|
||||
break;
|
||||
|
||||
try
|
||||
{
|
||||
content.Draw(canvas, metadata.Size);
|
||||
canvas.BeginPage(spacePlan);
|
||||
content.Draw(spacePlan);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
pdf.Close();
|
||||
canvas.EndDocument();
|
||||
throw new DocumentDrawingException("An exception occured during document drawing.", exception);
|
||||
}
|
||||
|
||||
pdf.EndPage();
|
||||
canvas.EndPage();
|
||||
|
||||
if (totalPages >= metadata.DocumentLayoutExceptionThreshold)
|
||||
if (currentPage >= documentMetadata.DocumentLayoutExceptionThreshold)
|
||||
{
|
||||
pdf.Close();
|
||||
canvas.EndDocument();
|
||||
throw new DocumentLayoutException("Composed layout generates infinite document.");
|
||||
}
|
||||
|
||||
if (spacePlan is FullRender)
|
||||
break;
|
||||
|
||||
totalPages++;
|
||||
currentPage++;
|
||||
}
|
||||
|
||||
pdf.Close();
|
||||
}
|
||||
|
||||
internal static IEnumerable<byte[]> GenerateImages(IDocument document)
|
||||
{
|
||||
var content = ElementExtensions.Create(document.Compose);
|
||||
var metadata = document.GetMetadata();
|
||||
|
||||
var totalPages = 1;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var spacePlan = content.Measure(metadata.Size);
|
||||
byte[] result;
|
||||
|
||||
try
|
||||
{
|
||||
result = RenderPage(content);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new DocumentDrawingException("An exception occured during document drawing.", exception);
|
||||
}
|
||||
|
||||
yield return result;
|
||||
|
||||
if (totalPages >= metadata.DocumentLayoutExceptionThreshold)
|
||||
{
|
||||
throw new DocumentLayoutException("Composed layout generates infinite document.");
|
||||
}
|
||||
|
||||
if (spacePlan is FullRender)
|
||||
break;
|
||||
|
||||
totalPages++;
|
||||
}
|
||||
|
||||
byte[] RenderPage(Element element)
|
||||
{
|
||||
// scale the result so it is more readable
|
||||
var scalingFactor = metadata.RasterDpi / (float) PageSizes.PointsPerInch;
|
||||
|
||||
var imageInfo = new SKImageInfo((int) (metadata.Size.Width * scalingFactor), (int) (metadata.Size.Height * scalingFactor));
|
||||
using var surface = SKSurface.Create(imageInfo);
|
||||
surface.Canvas.Scale(scalingFactor);
|
||||
|
||||
var canvas = new Canvas(surface.Canvas);
|
||||
element?.Draw(canvas, metadata.Size);
|
||||
|
||||
surface.Canvas.Save();
|
||||
return surface.Snapshot().Encode(SKEncodedImageFormat.Png, 100).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private static SKDocumentPdfMetadata MapMetadata(DocumentMetadata metadata)
|
||||
{
|
||||
return new SKDocumentPdfMetadata
|
||||
{
|
||||
Title = metadata.Title,
|
||||
Author = metadata.Author,
|
||||
Subject = metadata.Subject,
|
||||
Keywords = metadata.Keywords,
|
||||
Creator = metadata.Creator,
|
||||
Producer = metadata.Producer,
|
||||
|
||||
Creation = metadata.CreationDate,
|
||||
Modified = metadata.ModifiedDate,
|
||||
|
||||
RasterDpi = metadata.RasterDpi,
|
||||
EncodingQuality = metadata.ImageQuality,
|
||||
PdfA = metadata.PdfA
|
||||
};
|
||||
canvas.EndDocument();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,9 @@
|
||||
using System;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
public class DocumentMetadata
|
||||
{
|
||||
public Size Size { get; set; } = PageSizes.A4;
|
||||
public int ImageQuality { get; set; } = 101;
|
||||
public int RasterDpi { get; set; } = 72;
|
||||
public bool PdfA { get; set; }
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
internal class FreeCanvas : ICanvas, IRenderingCanvas
|
||||
{
|
||||
#region IRenderingCanvas
|
||||
|
||||
public void BeginDocument()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void EndDocument()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void BeginPage(Size size)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void EndPage()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICanvas
|
||||
|
||||
public void Translate(Position vector)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void DrawRectangle(Position vector, Size size, string color)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void DrawText(string text, Position position, TextStyle style)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void DrawImage(SKImage image, Position position, Size size)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void DrawExternalLink(string url, Size size)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void DrawLocationLink(string locationName, Size size)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void DrawLocation(string locationName)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Collections.Generic;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
internal class ImageCanvas : SkiaCanvasBase
|
||||
{
|
||||
private DocumentMetadata Metadata { get; }
|
||||
private SKSurface Surface { get; set; }
|
||||
|
||||
internal ICollection<byte[]> Images { get; } = new List<byte[]>();
|
||||
|
||||
public ImageCanvas(DocumentMetadata metadata)
|
||||
{
|
||||
Metadata = metadata;
|
||||
}
|
||||
|
||||
public override void BeginDocument()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override void EndDocument()
|
||||
{
|
||||
Canvas?.Dispose();
|
||||
Surface?.Dispose();
|
||||
}
|
||||
|
||||
public override void BeginPage(Size size)
|
||||
{
|
||||
var scalingFactor = Metadata.RasterDpi / (float) PageSizes.PointsPerInch;
|
||||
var imageInfo = new SKImageInfo((int) (size.Width * scalingFactor), (int) (size.Height * scalingFactor));
|
||||
|
||||
Surface = SKSurface.Create(imageInfo);
|
||||
Canvas = Surface.Canvas;
|
||||
|
||||
Canvas.Scale(scalingFactor);
|
||||
}
|
||||
|
||||
public override void EndPage()
|
||||
{
|
||||
Canvas.Save();
|
||||
var image = Surface.Snapshot().Encode(SKEncodedImageFormat.Png, 100).ToArray();
|
||||
Images.Add(image);
|
||||
|
||||
Canvas.Dispose();
|
||||
Surface.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using System.IO;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
internal class PdfCanvas : SkiaCanvasBase
|
||||
{
|
||||
private SKDocument Document { get; }
|
||||
|
||||
public PdfCanvas(Stream stream, DocumentMetadata documentMetadata)
|
||||
{
|
||||
Document = SKDocument.CreatePdf(stream, MapMetadata(documentMetadata));
|
||||
}
|
||||
|
||||
~PdfCanvas()
|
||||
{
|
||||
Document.Dispose();
|
||||
}
|
||||
|
||||
public override void BeginDocument()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override void EndDocument()
|
||||
{
|
||||
Canvas.Dispose();
|
||||
|
||||
Document.Close();
|
||||
Document.Dispose();
|
||||
}
|
||||
|
||||
public override void BeginPage(Size size)
|
||||
{
|
||||
Canvas = Document.BeginPage(size.Width, size.Height);
|
||||
}
|
||||
|
||||
public override void EndPage()
|
||||
{
|
||||
Document.EndPage();
|
||||
Canvas.Dispose();
|
||||
}
|
||||
|
||||
private static SKDocumentPdfMetadata MapMetadata(DocumentMetadata metadata)
|
||||
{
|
||||
return new SKDocumentPdfMetadata
|
||||
{
|
||||
Title = metadata.Title,
|
||||
Author = metadata.Author,
|
||||
Subject = metadata.Subject,
|
||||
Keywords = metadata.Keywords,
|
||||
Creator = metadata.Creator,
|
||||
Producer = metadata.Producer,
|
||||
|
||||
Creation = metadata.CreationDate,
|
||||
Modified = metadata.ModifiedDate,
|
||||
|
||||
RasterDpi = metadata.RasterDpi,
|
||||
EncodingQuality = metadata.ImageQuality,
|
||||
PdfA = metadata.PdfA
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
internal abstract class SkiaCanvasBase : ICanvas, IRenderingCanvas
|
||||
{
|
||||
internal SKCanvas Canvas { get; set; }
|
||||
|
||||
public abstract void BeginDocument();
|
||||
public abstract void EndDocument();
|
||||
|
||||
public abstract void BeginPage(Size size);
|
||||
public abstract void EndPage();
|
||||
|
||||
public void Translate(Position vector)
|
||||
{
|
||||
Canvas.Translate(vector.X, vector.Y);
|
||||
}
|
||||
|
||||
public void DrawRectangle(Position vector, Size size, string color)
|
||||
{
|
||||
if (size.Width < Size.Epsilon || size.Height < Size.Epsilon)
|
||||
return;
|
||||
|
||||
var paint = color.ColorToPaint();
|
||||
Canvas.DrawRect(vector.X, vector.Y, size.Width, size.Height, paint);
|
||||
}
|
||||
|
||||
public void DrawText(string text, Position vector, TextStyle style)
|
||||
{
|
||||
Canvas.DrawText(text, vector.X, vector.Y, style.ToPaint());
|
||||
}
|
||||
|
||||
public void DrawImage(SKImage image, Position vector, Size size)
|
||||
{
|
||||
Canvas.DrawImage(image, new SKRect(vector.X, vector.Y, size.Width, size.Height));
|
||||
}
|
||||
|
||||
public void DrawExternalLink(string url, Size size)
|
||||
{
|
||||
Canvas.DrawUrlAnnotation(new SKRect(0, 0, size.Width, size.Height), url);
|
||||
}
|
||||
|
||||
public void DrawLocationLink(string locationName, Size size)
|
||||
{
|
||||
Canvas.DrawLinkDestinationAnnotation(new SKRect(0, 0, size.Width, size.Height), locationName);
|
||||
}
|
||||
|
||||
public void DrawLocation(string locationName)
|
||||
{
|
||||
Canvas.DrawNamedDestinationAnnotation(new SKPoint(0, 0), locationName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace QuestPDF.Drawing.SpacePlan
|
||||
{
|
||||
internal class TextRender : FullRender
|
||||
{
|
||||
public float Ascent { get; set; }
|
||||
public float Descent { get; set; }
|
||||
|
||||
public TextRender(float width, float height) : base(width, height)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace QuestPDF.Elements
|
||||
public VerticalAlignment Vertical { get; set; } = VerticalAlignment.Top;
|
||||
public HorizontalAlignment Horizontal { get; set; } = HorizontalAlignment.Left;
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
if (Child == null)
|
||||
return;
|
||||
@@ -21,9 +21,9 @@ namespace QuestPDF.Elements
|
||||
var top = GetTopOffset(availableSpace, childSize);
|
||||
var left = GetLeftOffset(availableSpace, childSize);
|
||||
|
||||
canvas.Translate(new Position(left, top));
|
||||
Child.Draw(canvas, childSize);
|
||||
canvas.Translate(new Position(-left, -top));
|
||||
Canvas.Translate(new Position(left, top));
|
||||
Child.Draw(childSize);
|
||||
Canvas.Translate(new Position(-left, -top));
|
||||
}
|
||||
|
||||
private float GetTopOffset(Size availableSpace, Size childSize)
|
||||
|
||||
@@ -36,13 +36,13 @@ namespace QuestPDF.Elements
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
if (Child == null)
|
||||
return;
|
||||
|
||||
var size = GetTargetSize(availableSpace);
|
||||
Child?.Draw(canvas, size);
|
||||
Child?.Draw(size);
|
||||
}
|
||||
|
||||
private Size GetTargetSize(Size availableSpace)
|
||||
|
||||
@@ -7,10 +7,10 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
public string Color { get; set; } = Colors.Black;
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
canvas.DrawRectangle(Position.Zero, availableSpace, Color);
|
||||
Child?.Draw(canvas, availableSpace);
|
||||
Canvas.DrawRectangle(Position.Zero, availableSpace, Color);
|
||||
Child?.Draw(availableSpace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,26 +12,26 @@ namespace QuestPDF.Elements
|
||||
public float Bottom { get; set; }
|
||||
public float Left { get; set; }
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
Child?.Draw(canvas, availableSpace);
|
||||
Child?.Draw(availableSpace);
|
||||
|
||||
canvas.DrawRectangle(
|
||||
Canvas.DrawRectangle(
|
||||
new Position(-Left/2, -Top/2),
|
||||
new Size(availableSpace.Width + Left/2 + Right/2, Top),
|
||||
Color);
|
||||
|
||||
canvas.DrawRectangle(
|
||||
Canvas.DrawRectangle(
|
||||
new Position(-Left/2, -Top/2),
|
||||
new Size(Left, availableSpace.Height + Top/2 + Bottom/2),
|
||||
Color);
|
||||
|
||||
canvas.DrawRectangle(
|
||||
Canvas.DrawRectangle(
|
||||
new Position(-Left/2, availableSpace.Height-Bottom/2),
|
||||
new Size(availableSpace.Width + Left/2 + Right/2, Bottom),
|
||||
Color);
|
||||
|
||||
canvas.DrawRectangle(
|
||||
Canvas.DrawRectangle(
|
||||
new Position(availableSpace.Width-Right/2, -Top/2),
|
||||
new Size(Right, availableSpace.Height + Top/2 + Bottom/2),
|
||||
Color);
|
||||
|
||||
@@ -4,14 +4,14 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Box : ContainerElement
|
||||
{
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var targetSize = Child?.Measure(availableSpace) as Size;
|
||||
|
||||
if (targetSize == null)
|
||||
return;
|
||||
|
||||
Child?.Draw(canvas, targetSize);
|
||||
Child?.Draw(targetSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,9 @@ namespace QuestPDF.Elements
|
||||
return new FullRender(availableSpace);
|
||||
}
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var skiaCanvas = (canvas as Drawing.Canvas)?.SkiaCanvas;
|
||||
var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
|
||||
|
||||
if (Handler == null || skiaCanvas == null)
|
||||
return;
|
||||
|
||||
@@ -44,13 +44,13 @@ namespace QuestPDF.Elements
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var available = new Size(
|
||||
MathHelpers.Min(MaxWidth, availableSpace.Width),
|
||||
MathHelpers.Min(MaxHeight, availableSpace.Height));
|
||||
|
||||
Child?.Draw(canvas, available);
|
||||
Child?.Draw(available);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,79 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal enum DecorationType
|
||||
{
|
||||
Prepend,
|
||||
Append
|
||||
}
|
||||
|
||||
internal class SimpleDecoration : Element
|
||||
{
|
||||
public Element DecorationElement { get; set; } = Empty.Instance;
|
||||
public Element ContentElement { get; set; } = Empty.Instance;
|
||||
public DecorationType Type { get; set; }
|
||||
|
||||
internal override void HandleVisitor(Action<Element?> visit)
|
||||
{
|
||||
DecorationElement?.HandleVisitor(visit);
|
||||
ContentElement?.HandleVisitor(visit);
|
||||
|
||||
base.HandleVisitor(visit);
|
||||
}
|
||||
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
var decorationMeasure = DecorationElement?.Measure(availableSpace);
|
||||
|
||||
if (decorationMeasure is Wrap || decorationMeasure is PartialRender)
|
||||
return new Wrap();
|
||||
|
||||
var decorationSize = decorationMeasure as Size ?? Size.Zero;
|
||||
var contentMeasure = ContentElement?.Measure(new Size(availableSpace.Width, availableSpace.Height - decorationSize.Height)) ?? new FullRender(Size.Zero);
|
||||
|
||||
if (contentMeasure is Wrap)
|
||||
return new Wrap();
|
||||
|
||||
var contentSize = contentMeasure as Size ?? Size.Zero;
|
||||
var resultSize = new Size(availableSpace.Width, decorationSize.Height + contentSize.Height);
|
||||
|
||||
if (contentSize is PartialRender)
|
||||
return new PartialRender(resultSize);
|
||||
|
||||
if (contentSize is FullRender)
|
||||
return new FullRender(resultSize);
|
||||
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var decorationSize = DecorationElement?.Measure(availableSpace) as Size ?? Size.Zero;
|
||||
var contentSize = new Size(availableSpace.Width, availableSpace.Height - decorationSize.Height);
|
||||
|
||||
var translateHeight = Type == DecorationType.Prepend ? decorationSize.Height : contentSize.Height;
|
||||
Action drawDecoration = () => DecorationElement?.Draw(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; } = new Empty();
|
||||
public Element Content { get; set; } = new Empty();
|
||||
public Element Footer { get; set; } = new Empty();
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace QuestPDF.Elements
|
||||
return new FullRender(availableSpace.Width, availableSpace.Height);
|
||||
}
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var imageData = Source?.Invoke(availableSpace);
|
||||
|
||||
@@ -26,7 +26,8 @@ namespace QuestPDF.Elements
|
||||
InternalImage = SKImage.FromEncodedData(imageData)
|
||||
};
|
||||
|
||||
imageElement.Draw(canvas, availableSpace);
|
||||
imageElement.Initialize(PageContext, Canvas);
|
||||
imageElement.Draw(availableSpace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,14 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Empty : Element
|
||||
{
|
||||
internal static Empty Instance { get; } = new Empty();
|
||||
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
return new FullRender(Size.Zero);
|
||||
}
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -6,15 +6,15 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
public string Url { get; set; } = "https://www.questpdf.com";
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var targetSize = Child?.Measure(availableSpace) as Size;
|
||||
|
||||
if (targetSize == null)
|
||||
return;
|
||||
|
||||
canvas.DrawExternalLink(Url, targetSize);
|
||||
Child?.Draw(canvas, availableSpace);
|
||||
Canvas.DrawExternalLink(Url, targetSize);
|
||||
Child?.Draw(availableSpace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ namespace QuestPDF.Elements
|
||||
|
||||
container.Stack(stack =>
|
||||
{
|
||||
stack.Spacing(HorizontalSpacing);
|
||||
stack.Spacing(VerticalSpacing);
|
||||
|
||||
while (ChildrenQueue.Any())
|
||||
stack.Item().Row(BuildRow);
|
||||
|
||||
@@ -18,12 +18,12 @@ namespace QuestPDF.Elements
|
||||
return new FullRender(availableSpace);
|
||||
}
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
if (InternalImage == null)
|
||||
return;
|
||||
|
||||
canvas.DrawImage(InternalImage, Position.Zero, availableSpace);
|
||||
Canvas.DrawImage(InternalImage, Position.Zero, availableSpace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,15 +6,15 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
public string LocationName { get; set; }
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var targetSize = Child?.Measure(availableSpace) as Size;
|
||||
|
||||
if (targetSize == null)
|
||||
return;
|
||||
|
||||
canvas.DrawLocationLink(LocationName, targetSize);
|
||||
Child?.Draw(canvas, availableSpace);
|
||||
Canvas.DrawLocationLink(LocationName, targetSize);
|
||||
Child?.Draw(availableSpace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,27 @@
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class InternalLocation : ContainerElement
|
||||
internal class InternalLocation : ContainerElement, IStateResettable
|
||||
{
|
||||
public string LocationName { get; set; }
|
||||
private bool IsRendered { get; set; }
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
public void ResetState()
|
||||
{
|
||||
canvas.DrawLocation(LocationName);
|
||||
Child?.Draw(canvas, availableSpace);
|
||||
IsRendered = false;
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
if (!IsRendered)
|
||||
{
|
||||
PageContext.SetLocationPage(LocationName);
|
||||
|
||||
Canvas.DrawLocation(LocationName);
|
||||
IsRendered = true;
|
||||
}
|
||||
|
||||
Child?.Draw(availableSpace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Infrastructure;
|
||||
@@ -14,6 +15,12 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
public List<Layer> Children { get; set; } = new List<Layer>();
|
||||
|
||||
internal override void HandleVisitor(Action<Element?> visit)
|
||||
{
|
||||
Children.ForEach(x => x.HandleVisitor(visit));
|
||||
base.HandleVisitor(visit);
|
||||
}
|
||||
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
return Children
|
||||
@@ -21,12 +28,12 @@ namespace QuestPDF.Elements
|
||||
.Measure(availableSpace);
|
||||
}
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
Children
|
||||
.Where(x => x.Measure(availableSpace) is Size)
|
||||
.ToList()
|
||||
.ForEach(x => x.Draw(canvas, availableSpace));
|
||||
.ForEach(x => x.Draw(availableSpace));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,16 +39,16 @@ namespace QuestPDF.Elements
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
if (Child == null)
|
||||
return;
|
||||
|
||||
var internalSpace = InternalSpace(availableSpace);
|
||||
|
||||
canvas.Translate(new Position(Left, Top));
|
||||
Child?.Draw(canvas, internalSpace);
|
||||
canvas.Translate(new Position(-Left, -Top));
|
||||
Canvas.Translate(new Position(Left, Top));
|
||||
Child?.Draw(internalSpace);
|
||||
Canvas.Translate(new Position(-Left, -Top));
|
||||
}
|
||||
|
||||
private Size InternalSpace(Size availableSpace)
|
||||
|
||||
@@ -1,22 +1,44 @@
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Page : IComponent
|
||||
{
|
||||
public Element Header { get; set; } = new Empty();
|
||||
public Element Content { get; set; } = new Empty();
|
||||
public Element Footer { get; set; } = new Empty();
|
||||
public Size MinSize { get; set; } = PageSizes.A4;
|
||||
public Size MaxSize { get; set; } = PageSizes.A4;
|
||||
|
||||
public float MarginLeft { get; set; }
|
||||
public float MarginRight { get; set; }
|
||||
public float MarginTop { get; set; }
|
||||
public float MarginBottom { get; set; }
|
||||
|
||||
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.Decoration(section =>
|
||||
{
|
||||
section.Header().Element(Header);
|
||||
section.Content().Extend().Element(Content);
|
||||
section.Footer().Element(Footer);
|
||||
});
|
||||
container
|
||||
|
||||
.MinWidth(MinSize.Width)
|
||||
.MinHeight(MinSize.Height)
|
||||
|
||||
.MaxWidth(MaxSize.Width)
|
||||
.MaxHeight(MaxSize.Height)
|
||||
|
||||
.PaddingLeft(MarginLeft)
|
||||
.PaddingRight(MarginRight)
|
||||
.PaddingTop(MarginTop)
|
||||
.PaddingBottom(MarginBottom)
|
||||
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
decoration.Header().Element(Header);
|
||||
decoration.Content().Extend().Element(Content);
|
||||
decoration.Footer().Element(Footer);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,19 +3,24 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class PageBreak : Element
|
||||
internal class PageBreak : Element, IStateResettable
|
||||
{
|
||||
private bool IsRendered { get; set; }
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
IsRendered = false;
|
||||
}
|
||||
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
if (IsRendered)
|
||||
return new FullRender(Size.Zero);
|
||||
|
||||
return new PartialRender(availableSpace.Width, availableSpace.Height);
|
||||
|
||||
return new PartialRender(Size.Zero);
|
||||
}
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
IsRendered = true;
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Infrastructure;
|
||||
using Size = QuestPDF.Infrastructure.Size;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class PageNumber : Element
|
||||
{
|
||||
public string TextFormat { get; set; } = "";
|
||||
public TextStyle? TextStyle { get; set; }
|
||||
private Text? TextElement { get; set; }
|
||||
|
||||
private int Number { get; set; } = 1;
|
||||
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
InitializeTextElement();
|
||||
|
||||
TextElement.Value = TextFormat.Replace("{number}", Number.ToString());
|
||||
return TextElement.Measure(availableSpace);
|
||||
}
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
{
|
||||
InitializeTextElement();
|
||||
|
||||
TextElement.Draw(canvas, availableSpace);
|
||||
Number++;
|
||||
}
|
||||
|
||||
private void InitializeTextElement()
|
||||
{
|
||||
TextElement ??= new Text()
|
||||
{
|
||||
Style = TextStyle
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,16 +18,15 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
container
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Padding(5)
|
||||
.AlignMiddle()
|
||||
.AlignCenter()
|
||||
.Padding(5)
|
||||
.MaxHeight(32)
|
||||
.Element(x =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Text))
|
||||
x.Image(ImageData, ImageScaling.FitArea);
|
||||
x.MaxHeight(32).Image(ImageData, ImageScaling.FitArea);
|
||||
else
|
||||
x.Text(Text, TextStyle.Default.Size(14).SemiBold());
|
||||
x.Text(Text, TextStyle.Default.Size(14));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Rotate : ContainerElement
|
||||
{
|
||||
public float Angle { get; set; } = 0;
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
|
||||
|
||||
if (skiaCanvas == null)
|
||||
return;
|
||||
|
||||
var currentMatrix = skiaCanvas.TotalMatrix;
|
||||
|
||||
skiaCanvas.RotateDegrees(Angle);
|
||||
Child?.Draw(availableSpace);
|
||||
skiaCanvas.SetMatrix(currentMatrix);
|
||||
}
|
||||
}
|
||||
}
|
||||
+124
-50
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
@@ -9,9 +10,9 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
public float Width { get; set; } = 1;
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
Child?.Draw(canvas, availableSpace);
|
||||
Child?.Draw(availableSpace);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,78 +32,151 @@ namespace QuestPDF.Elements
|
||||
}
|
||||
}
|
||||
|
||||
internal class Row : Element
|
||||
internal class SimpleRow : Element
|
||||
{
|
||||
public List<RowElement> Children { get; set; } = new List<RowElement>();
|
||||
|
||||
float? ConstantWidthSum { get; set; }
|
||||
float? RelativeWidthSum { get; set; }
|
||||
internal Element Left { get; set; }
|
||||
internal Element Right { get; set; }
|
||||
|
||||
internal override void HandleVisitor(Action<Element?> visit)
|
||||
{
|
||||
Left.HandleVisitor(visit);
|
||||
Right.HandleVisitor(visit);
|
||||
|
||||
base.HandleVisitor(visit);
|
||||
}
|
||||
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
var sizes = Children
|
||||
.Select(x =>
|
||||
{
|
||||
var space = GetTargetSize(x, availableSpace);
|
||||
return x.Child.Measure(space);
|
||||
})
|
||||
.ToList();
|
||||
var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height)) as Size;
|
||||
|
||||
if (sizes.Any(x => x is Wrap))
|
||||
if (leftMeasurement == null)
|
||||
return new Wrap();
|
||||
|
||||
var height = sizes
|
||||
.Where(x => x is Size)
|
||||
.Cast<Size>()
|
||||
.DefaultIfEmpty(Size.Zero)
|
||||
.Max(x => x.Height);
|
||||
|
||||
if (sizes.All(x => x is FullRender))
|
||||
return new FullRender(availableSpace.Width, height);
|
||||
|
||||
if (sizes.Any(x => x is PartialRender))
|
||||
return new PartialRender(availableSpace.Width, height);
|
||||
var rightMeasurement = Right.Measure(new Size(availableSpace.Width - leftMeasurement.Width, availableSpace.Height)) as Size;
|
||||
|
||||
return new FullRender(Size.Zero);
|
||||
if (rightMeasurement == null)
|
||||
return new Wrap();
|
||||
|
||||
var totalWidth = leftMeasurement.Width + rightMeasurement.Width;
|
||||
var totalHeight = Math.Max(leftMeasurement.Height, rightMeasurement.Height);
|
||||
|
||||
var targetSize = new Size(totalWidth, totalHeight);
|
||||
|
||||
if (leftMeasurement is PartialRender || rightMeasurement is PartialRender)
|
||||
return new PartialRender(targetSize);
|
||||
|
||||
return new FullRender(targetSize);
|
||||
}
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var targetSpace = Measure(availableSpace) as Size;
|
||||
|
||||
if (targetSpace == null)
|
||||
return;
|
||||
var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height));
|
||||
var leftWidth = (leftMeasurement as Size)?.Width ?? 0;
|
||||
|
||||
var offset = 0f;
|
||||
Left.Draw(new Size(leftWidth, availableSpace.Height));
|
||||
|
||||
Canvas.Translate(new Position(leftWidth, 0));
|
||||
Right.Draw(new Size(availableSpace.Width - leftWidth, availableSpace.Height));
|
||||
Canvas.Translate(new Position(-leftWidth, 0));
|
||||
}
|
||||
}
|
||||
|
||||
internal class Row : Element
|
||||
{
|
||||
public ICollection<RowElement> Children { get; internal set; } = new List<RowElement>();
|
||||
public float Spacing { get; set; } = 0;
|
||||
|
||||
foreach (var column in Children)
|
||||
{
|
||||
var space = GetTargetSize(column, targetSpace);
|
||||
|
||||
canvas.Translate(new Position(offset, 0));
|
||||
column.Child.Draw(canvas, space);
|
||||
canvas.Translate(new Position(-offset, 0));
|
||||
|
||||
offset += space.Width;
|
||||
}
|
||||
internal override void HandleVisitor(Action<Element?> visit)
|
||||
{
|
||||
Children.ToList().ForEach(x => x.HandleVisitor(visit));
|
||||
base.HandleVisitor(visit);
|
||||
}
|
||||
|
||||
private Size GetTargetSize(RowElement rowElement, Size availableSpace)
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
if (rowElement is ConstantRowElement)
|
||||
return new Size(rowElement.Width, availableSpace.Height);
|
||||
return Compose(availableSpace.Width).Measure(availableSpace);
|
||||
}
|
||||
|
||||
ConstantWidthSum ??= Children
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
Compose(availableSpace.Width).Draw(availableSpace);
|
||||
}
|
||||
|
||||
#region structure
|
||||
|
||||
private Element Compose(float availableWidth)
|
||||
{
|
||||
var elements = AddSpacing(Children, Spacing);
|
||||
var rowElements = ReduceRows(elements, availableWidth);
|
||||
var tree = BuildTree(rowElements.ToArray());
|
||||
tree.HandleVisitor(x => x.Initialize(PageContext, Canvas));
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
private static ICollection<Element> ReduceRows(ICollection<RowElement> elements, float availableWidth)
|
||||
{
|
||||
var constantWidth = elements
|
||||
.Where(x => x is ConstantRowElement)
|
||||
.Cast<ConstantRowElement>()
|
||||
.Sum(x => x.Width);
|
||||
|
||||
RelativeWidthSum ??= Children
|
||||
|
||||
var relativeWidth = elements
|
||||
.Where(x => x is RelativeRowElement)
|
||||
.Cast<RelativeRowElement>()
|
||||
.Sum(x => x.Width);
|
||||
|
||||
var widthPerRelativeUnit = (availableWidth - constantWidth) / relativeWidth;
|
||||
|
||||
return new Size((availableSpace.Width - ConstantWidthSum.Value) * rowElement.Width / RelativeWidthSum.Value, availableSpace.Height);
|
||||
return elements
|
||||
.Select(x =>
|
||||
{
|
||||
if (x is RelativeRowElement r)
|
||||
return new ConstantRowElement(r.Width * widthPerRelativeUnit)
|
||||
{
|
||||
Child = x.Child
|
||||
};
|
||||
|
||||
return x;
|
||||
})
|
||||
.Select(x => new Constrained
|
||||
{
|
||||
MinWidth = x.Width,
|
||||
MaxWidth = x.Width,
|
||||
Child = x.Child
|
||||
})
|
||||
.Cast<Element>()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static ICollection<RowElement> AddSpacing(ICollection<RowElement> elements, float spacing)
|
||||
{
|
||||
if (spacing < Size.Epsilon)
|
||||
return elements;
|
||||
|
||||
return elements
|
||||
.SelectMany(x => new[] { new ConstantRowElement(spacing), x })
|
||||
.Skip(1)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static Element BuildTree(Span<Element> elements)
|
||||
{
|
||||
if (elements.IsEmpty)
|
||||
return Empty.Instance;
|
||||
|
||||
if (elements.Length == 1)
|
||||
return elements[0];
|
||||
|
||||
var half = elements.Length / 2;
|
||||
|
||||
return new SimpleRow
|
||||
{
|
||||
Left = BuildTree(elements.Slice(0, half)),
|
||||
Right = BuildTree(elements.Slice(half))
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Scale : ContainerElement
|
||||
{
|
||||
public float ScaleX { get; set; } = 1;
|
||||
public float ScaleY { get; set; } = 1;
|
||||
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
var targetSpace = new Size(
|
||||
Math.Abs(availableSpace.Width / ScaleX),
|
||||
Math.Abs(availableSpace.Height / ScaleY));
|
||||
|
||||
var measure = base.Measure(targetSpace) as Size;
|
||||
|
||||
if (measure == null)
|
||||
return new Wrap();
|
||||
|
||||
var targetSize = new Size(
|
||||
Math.Abs(measure.Width * ScaleX),
|
||||
Math.Abs(measure.Height * ScaleY));
|
||||
|
||||
if (measure is PartialRender)
|
||||
return new PartialRender(targetSize);
|
||||
|
||||
if (measure is FullRender)
|
||||
return new FullRender(targetSize);
|
||||
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
|
||||
|
||||
if (skiaCanvas == null)
|
||||
return;
|
||||
|
||||
var targetSpace = new Size(
|
||||
Math.Abs(availableSpace.Width / ScaleX),
|
||||
Math.Abs(availableSpace.Height / ScaleY));
|
||||
|
||||
var currentMatrix = skiaCanvas.TotalMatrix;
|
||||
|
||||
if (ScaleX < 0)
|
||||
skiaCanvas.Translate(availableSpace.Width, 0);
|
||||
|
||||
if (ScaleY < 0)
|
||||
skiaCanvas.Translate(0, availableSpace.Height);
|
||||
|
||||
skiaCanvas.Scale(ScaleX, ScaleY);
|
||||
Child?.Draw(targetSpace);
|
||||
skiaCanvas.SetMatrix(currentMatrix);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,15 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class ShowOnce : ContainerElement
|
||||
internal class ShowOnce : ContainerElement, IStateResettable
|
||||
{
|
||||
private bool IsRendered { get; set; }
|
||||
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
IsRendered = false;
|
||||
}
|
||||
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
if (Child == null || IsRendered)
|
||||
@@ -15,7 +20,7 @@ namespace QuestPDF.Elements
|
||||
return Child.Measure(availableSpace);
|
||||
}
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
if (Child == null || IsRendered)
|
||||
return;
|
||||
@@ -23,7 +28,7 @@ namespace QuestPDF.Elements
|
||||
if (Child.Measure(availableSpace) is FullRender)
|
||||
IsRendered = true;
|
||||
|
||||
Child.Draw(canvas, availableSpace);
|
||||
Child.Draw(availableSpace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal enum DecorationType
|
||||
{
|
||||
Prepend,
|
||||
Append
|
||||
}
|
||||
|
||||
internal class SimpleDecoration : Element
|
||||
{
|
||||
public Element DecorationElement { get; set; } = new Empty();
|
||||
public Element ContentElement { get; set; } = new Empty();
|
||||
public DecorationType Type { get; set; }
|
||||
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
var decorationMeasure = DecorationElement?.Measure(availableSpace);
|
||||
|
||||
if (decorationMeasure is Wrap || decorationMeasure is PartialRender)
|
||||
return new Wrap();
|
||||
|
||||
var decorationSize = decorationMeasure as Size ?? Size.Zero;
|
||||
var contentMeasure = ContentElement?.Measure(new Size(availableSpace.Width, availableSpace.Height - decorationSize.Height)) ?? new FullRender(Size.Zero);
|
||||
|
||||
if (contentMeasure is Wrap)
|
||||
return new Wrap();
|
||||
|
||||
var contentSize = contentMeasure as Size ?? Size.Zero;
|
||||
var resultSize = new Size(availableSpace.Width, decorationSize.Height + contentSize.Height);
|
||||
|
||||
if (contentSize is PartialRender)
|
||||
return new PartialRender(resultSize);
|
||||
|
||||
if (contentSize is FullRender)
|
||||
return new FullRender(resultSize);
|
||||
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
{
|
||||
var decorationSize = DecorationElement?.Measure(availableSpace) as Size ?? Size.Zero;
|
||||
var contentSize = new Size(availableSpace.Width, availableSpace.Height - decorationSize.Height);
|
||||
|
||||
var translateHeight = Type == DecorationType.Prepend ? decorationSize.Height : contentSize.Height;
|
||||
Action drawDecoration = () => DecorationElement?.Draw(canvas, new Size(availableSpace.Width, decorationSize.Height));
|
||||
Action drawContent = () => ContentElement?.Draw(canvas, new Size (availableSpace.Width, contentSize.Height));
|
||||
|
||||
var first = Type == DecorationType.Prepend ? drawDecoration : drawContent;
|
||||
var second = Type == DecorationType.Prepend ? drawContent : drawDecoration;
|
||||
|
||||
first();
|
||||
canvas.Translate(new Position(0, translateHeight));
|
||||
second();
|
||||
canvas.Translate(new Position(0, -translateHeight));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class SimpleRotate : ContainerElement
|
||||
{
|
||||
public int TurnCount { get; set; }
|
||||
public int NormalizedTurnCount => (TurnCount % 4 + 4) % 4;
|
||||
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
if (NormalizedTurnCount == 0 || NormalizedTurnCount == 2)
|
||||
return base.Measure(availableSpace);
|
||||
|
||||
availableSpace = new Size(availableSpace.Height, availableSpace.Width);
|
||||
var childSpace = base.Measure(availableSpace) as Size;
|
||||
|
||||
if (childSpace == null)
|
||||
return new Wrap();
|
||||
|
||||
var targetSpace = new Size(childSpace.Height, childSpace.Width);
|
||||
|
||||
if (childSpace is FullRender)
|
||||
return new FullRender(targetSpace);
|
||||
|
||||
if (childSpace is PartialRender)
|
||||
return new PartialRender(targetSpace);
|
||||
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
|
||||
|
||||
if (skiaCanvas == null)
|
||||
return;
|
||||
|
||||
var currentMatrix = skiaCanvas.TotalMatrix;
|
||||
|
||||
if (NormalizedTurnCount == 1 || NormalizedTurnCount == 2)
|
||||
skiaCanvas.Translate(availableSpace.Width, 0);
|
||||
|
||||
if (NormalizedTurnCount == 2 || NormalizedTurnCount == 3)
|
||||
skiaCanvas.Translate(0, availableSpace.Height);
|
||||
|
||||
skiaCanvas.RotateDegrees(NormalizedTurnCount * 90);
|
||||
|
||||
if (NormalizedTurnCount == 1 || NormalizedTurnCount == 3)
|
||||
availableSpace = new Size(availableSpace.Height, availableSpace.Width);
|
||||
|
||||
Child?.Draw(availableSpace);
|
||||
skiaCanvas.SetMatrix(currentMatrix);
|
||||
}
|
||||
}
|
||||
}
|
||||
+113
-55
@@ -1,81 +1,139 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
using IComponent = QuestPDF.Infrastructure.IComponent;
|
||||
using IContainer = QuestPDF.Infrastructure.IContainer;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Stack : Element
|
||||
internal class SimpleStack : Element, IStateResettable
|
||||
{
|
||||
public ICollection<Element?> Children { get; internal set; } = new List<Element?>();
|
||||
private Queue<Element?> ChildrenQueue { get; set; } = new Queue<Element?>();
|
||||
|
||||
private void Initialize()
|
||||
internal Element First { get; set; } = Empty.Instance;
|
||||
internal Element Second { get; set; } = Empty.Instance;
|
||||
|
||||
internal bool IsFirstRendered { get; set; } = false;
|
||||
|
||||
internal override void HandleVisitor(Action<Element?> visit)
|
||||
{
|
||||
if (ChildrenQueue.Count == 0)
|
||||
ChildrenQueue = new Queue<Element>(Children.Where(x => x != null));
|
||||
First?.HandleVisitor(visit);
|
||||
Second?.HandleVisitor(visit);
|
||||
|
||||
base.HandleVisitor(visit);
|
||||
}
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
IsFirstRendered = false;
|
||||
}
|
||||
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
Initialize();
|
||||
var firstElement = IsFirstRendered ? Empty.Instance : First;
|
||||
var firstSize = firstElement.Measure(availableSpace) as Size;
|
||||
|
||||
if (firstSize == null)
|
||||
return new Wrap();
|
||||
|
||||
if(!ChildrenQueue.Any())
|
||||
return new FullRender(Size.Zero);
|
||||
if (firstSize is PartialRender partialRender)
|
||||
return partialRender;
|
||||
|
||||
var spaceForSecond = new Size(availableSpace.Width, availableSpace.Height - firstSize.Height);
|
||||
var secondSize = Second.Measure(spaceForSecond) as Size;
|
||||
|
||||
var heightOnCurrentPage = 0f;
|
||||
if (secondSize == null)
|
||||
return new PartialRender(firstSize);
|
||||
|
||||
foreach (var renderer in ChildrenQueue)
|
||||
{
|
||||
var space = renderer.Measure(new Size(availableSpace.Width, availableSpace.Height - heightOnCurrentPage));
|
||||
var totalWidth = Math.Max(firstSize.Width, secondSize.Width);
|
||||
var totalHeight = firstSize.Height + secondSize.Height;
|
||||
var targetSize = new Size(totalWidth, totalHeight);
|
||||
|
||||
if (space is Wrap)
|
||||
{
|
||||
if (heightOnCurrentPage < Size.Epsilon)
|
||||
return new Wrap();
|
||||
|
||||
return new PartialRender(availableSpace.Width, heightOnCurrentPage);
|
||||
}
|
||||
|
||||
var size = space as Size;
|
||||
heightOnCurrentPage += size.Height;
|
||||
|
||||
if (space is PartialRender)
|
||||
return new PartialRender(availableSpace.Width, heightOnCurrentPage);
|
||||
}
|
||||
|
||||
return new FullRender(availableSpace.Width, heightOnCurrentPage);
|
||||
if (secondSize is PartialRender)
|
||||
return new PartialRender(targetSize);
|
||||
|
||||
return new FullRender(targetSize);
|
||||
}
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
Initialize();
|
||||
var firstElement = IsFirstRendered ? Empty.Instance : First;
|
||||
|
||||
var firstMeasurement = firstElement.Measure(availableSpace);
|
||||
|
||||
if (firstMeasurement is FullRender)
|
||||
IsFirstRendered = true;
|
||||
|
||||
var firstSize = firstMeasurement as Size;
|
||||
|
||||
if (firstSize != null)
|
||||
firstElement.Draw(new Size(availableSpace.Width, firstSize.Height));
|
||||
|
||||
if (firstMeasurement is Wrap || firstMeasurement is PartialRender)
|
||||
return;
|
||||
|
||||
var firstHeight = firstSize?.Height ?? 0;
|
||||
var spaceForSecond = new Size(availableSpace.Width, availableSpace.Height - firstHeight);
|
||||
var secondMeasurement = Second?.Measure(spaceForSecond) as Size;
|
||||
|
||||
if (secondMeasurement == null)
|
||||
return;
|
||||
|
||||
Canvas.Translate(new Position(0, firstHeight));
|
||||
Second.Draw(new Size(availableSpace.Width, secondMeasurement.Height));
|
||||
Canvas.Translate(new Position(0, -firstHeight));
|
||||
|
||||
var topOffset = 0f;
|
||||
if (secondMeasurement is FullRender)
|
||||
IsFirstRendered = false;
|
||||
}
|
||||
}
|
||||
|
||||
internal class Stack : IComponent
|
||||
{
|
||||
public ICollection<Element> Children { get; internal set; } = new List<Element>();
|
||||
public float Spacing { get; set; } = 0;
|
||||
|
||||
public void Compose(IContainer container)
|
||||
{
|
||||
var elements = AddSpacing(Spacing, Children);
|
||||
|
||||
while (ChildrenQueue.Any())
|
||||
container
|
||||
.PaddingBottom(-Spacing)
|
||||
.Element(BuildTree(elements.ToArray()));
|
||||
}
|
||||
|
||||
static ICollection<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 SimpleStack
|
||||
{
|
||||
var child = ChildrenQueue.Peek();
|
||||
|
||||
var restSpace = new Size(availableSpace.Width, availableSpace.Height - topOffset);
|
||||
var space = child.Measure(restSpace);
|
||||
|
||||
if (space is Wrap)
|
||||
break;
|
||||
|
||||
var size = space as Size;
|
||||
|
||||
canvas.Translate(new Position(0, topOffset));
|
||||
child.Draw(canvas, new Size(availableSpace.Width, size.Height));
|
||||
canvas.Translate(new Position(0, -topOffset));
|
||||
|
||||
topOffset += size.Height;
|
||||
|
||||
if (space is PartialRender)
|
||||
break;
|
||||
|
||||
ChildrenQueue.Dequeue();
|
||||
}
|
||||
First = BuildTree(elements.Slice(0, half)),
|
||||
Second = BuildTree(elements.Slice(half))
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Infrastructure;
|
||||
using Size = QuestPDF.Infrastructure.Size;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Text : Element
|
||||
{
|
||||
public string? Value { get; set; }
|
||||
public TextStyle? Style { get; set; } = new TextStyle();
|
||||
|
||||
private float LineHeight => Style.Size * Style.LineHeight;
|
||||
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
var lines = BreakLines(availableSpace.Width);
|
||||
|
||||
var realWidth = lines
|
||||
.Select(line => Style.BreakText(line, availableSpace.Width).FragmentWidth)
|
||||
.DefaultIfEmpty(0)
|
||||
.Max();
|
||||
|
||||
var realHeight = lines.Count * LineHeight;
|
||||
|
||||
if (realHeight > availableSpace.Height + Size.Epsilon)
|
||||
return new Wrap();
|
||||
|
||||
return new FullRender(realWidth, realHeight);
|
||||
}
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
{
|
||||
var lines = BreakLines(availableSpace.Width);
|
||||
|
||||
var offsetTop = 0f;
|
||||
var offsetLeft = GetLeftOffset();
|
||||
|
||||
canvas.Translate(new Position(0, Style.Size));
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
canvas.DrawText(line, new Position(offsetLeft, offsetTop), Style);
|
||||
offsetTop += LineHeight;
|
||||
}
|
||||
|
||||
canvas.Translate(new Position(0, -Style.Size));
|
||||
|
||||
float GetLeftOffset()
|
||||
{
|
||||
return Style.Alignment switch
|
||||
{
|
||||
HorizontalAlignment.Left => 0,
|
||||
HorizontalAlignment.Center => availableSpace.Width / 2,
|
||||
HorizontalAlignment.Right => availableSpace.Width,
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#region Word Wrap
|
||||
|
||||
private List<string> BreakLines(float maxWidth)
|
||||
{
|
||||
var lines = new List<string> ();
|
||||
|
||||
var remainingText = Value.Trim();
|
||||
|
||||
while(true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(remainingText))
|
||||
break;
|
||||
|
||||
var breakPoint = BreakLinePoint(remainingText, maxWidth);
|
||||
|
||||
if (breakPoint == 0)
|
||||
break;
|
||||
|
||||
var lastLine = remainingText.Substring(0, breakPoint).Trim();
|
||||
lines.Add(lastLine);
|
||||
|
||||
remainingText = remainingText.Substring(breakPoint).Trim();
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
private int BreakLinePoint(string text, float width)
|
||||
{
|
||||
var index = 0;
|
||||
var lengthBreak = Style.BreakText(text, width).LineIndex;
|
||||
|
||||
while (index <= text.Length)
|
||||
{
|
||||
var next = text.IndexOfAny (new [] { ' ', '\n' }, index);
|
||||
|
||||
if (next <= 0)
|
||||
return index == 0 || lengthBreak == text.Length ? lengthBreak : index;
|
||||
|
||||
if (next > lengthBreak)
|
||||
return index;
|
||||
|
||||
if (text[next] == '\n')
|
||||
return next;
|
||||
|
||||
index = next + 1;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Calculation
|
||||
{
|
||||
internal class TextDrawingRequest
|
||||
{
|
||||
public ICanvas Canvas { get; set; }
|
||||
public IPageContext PageContext { get; set; }
|
||||
|
||||
public int StartIndex { get; set; }
|
||||
public int EndIndex { get; set; }
|
||||
|
||||
public float TotalAscent { get; set; }
|
||||
public Size TextSize { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Elements.Text.Items;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Calculation
|
||||
{
|
||||
internal class TextLine
|
||||
{
|
||||
public ICollection<TextLineElement> Elements { get; set; }
|
||||
|
||||
public float TextHeight => Elements.Max(x => x.Measurement.Height);
|
||||
public float LineHeight => Elements.Max(x => x.Measurement.LineHeight * x.Measurement.Height);
|
||||
|
||||
public float Ascent => Elements.Min(x => x.Measurement.Ascent) - (LineHeight - TextHeight) / 2;
|
||||
public float Descent => Elements.Max(x => x.Measurement.Descent) + (LineHeight - TextHeight) / 2;
|
||||
|
||||
public float Width => Elements.Sum(x => x.Measurement.Width);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
internal class TextLineElement
|
||||
{
|
||||
public ITextBlockItem Item { get; set; }
|
||||
public TextMeasurementResult Measurement { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Calculation
|
||||
{
|
||||
internal class TextMeasurementRequest
|
||||
{
|
||||
public ICanvas Canvas { get; set; }
|
||||
public IPageContext PageContext { get; set; }
|
||||
|
||||
public int StartIndex { get; set; }
|
||||
public float AvailableWidth { get; set; }
|
||||
public bool IsFirstLineElement { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Calculation
|
||||
{
|
||||
internal class TextMeasurementResult
|
||||
{
|
||||
public float Width { get; set; }
|
||||
public float Height => Math.Abs(Descent) + Math.Abs(Ascent);
|
||||
|
||||
public float Ascent { get; set; }
|
||||
public float Descent { get; set; }
|
||||
|
||||
public float LineHeight { get; set; }
|
||||
|
||||
public int StartIndex { get; set; }
|
||||
public int EndIndex { get; set; }
|
||||
public int NextIndex { get; set; }
|
||||
public int TotalIndex { get; set; }
|
||||
|
||||
public bool IsLast => EndIndex == TotalIndex;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
internal interface ITextBlockItem
|
||||
{
|
||||
TextMeasurementResult? Measure(TextMeasurementRequest request);
|
||||
void Draw(TextDrawingRequest request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
internal class TextBlockElement : ITextBlockItem
|
||||
{
|
||||
public Element Element { get; set; } = Empty.Instance;
|
||||
|
||||
public TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
{
|
||||
Element.HandleVisitor(x => (x as IStateResettable)?.ResetState());
|
||||
Element.HandleVisitor(x => x.Initialize(request.PageContext, request.Canvas));
|
||||
|
||||
var measurement = Element.Measure(new Size(request.AvailableWidth, Size.Max.Height));
|
||||
|
||||
if (measurement is Wrap || measurement is PartialRender)
|
||||
return null;
|
||||
|
||||
var elementSize = measurement as Size;
|
||||
|
||||
return new TextMeasurementResult
|
||||
{
|
||||
Width = elementSize.Width,
|
||||
|
||||
Ascent = -elementSize.Height,
|
||||
Descent = 0,
|
||||
|
||||
LineHeight = 1,
|
||||
|
||||
StartIndex = 0,
|
||||
EndIndex = 0,
|
||||
TotalIndex = 0
|
||||
};
|
||||
}
|
||||
|
||||
public void Draw(TextDrawingRequest request)
|
||||
{
|
||||
Element.HandleVisitor(x => (x as IStateResettable)?.ResetState());
|
||||
Element.HandleVisitor(x => x.Initialize(request.PageContext, request.Canvas));
|
||||
|
||||
request.Canvas.Translate(new Position(0, request.TotalAscent));
|
||||
Element.Draw(new Size(request.TextSize.Width, -request.TotalAscent));
|
||||
request.Canvas.Translate(new Position(0, -request.TotalAscent));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
internal class TextBlockExternalLink : ITextBlockItem
|
||||
{
|
||||
public TextStyle Style { get; set; } = new TextStyle();
|
||||
public string Text { get; set; }
|
||||
public string Url { get; set; }
|
||||
|
||||
public TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
{
|
||||
return GetItem().MeasureWithoutCache(request);
|
||||
}
|
||||
|
||||
public 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.Translate(new Position(0, -request.TotalAscent));
|
||||
|
||||
GetItem().Draw(request);
|
||||
}
|
||||
|
||||
private TextBlockSpan GetItem()
|
||||
{
|
||||
return new TextBlockSpan
|
||||
{
|
||||
Style = Style,
|
||||
Text = Text
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
internal class TextBlockInternalLink : ITextBlockItem
|
||||
{
|
||||
public TextStyle Style { get; set; } = new TextStyle();
|
||||
public string Text { get; set; }
|
||||
public string LocationName { get; set; }
|
||||
|
||||
public TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
{
|
||||
return GetItem().MeasureWithoutCache(request);
|
||||
}
|
||||
|
||||
public 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.Translate(new Position(0, -request.TotalAscent));
|
||||
|
||||
GetItem().Draw(request);
|
||||
}
|
||||
|
||||
private TextBlockSpan GetItem()
|
||||
{
|
||||
return new TextBlockSpan
|
||||
{
|
||||
Style = Style,
|
||||
Text = Text
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
internal class TextBlockPageNumber : ITextBlockItem
|
||||
{
|
||||
public TextStyle Style { get; set; } = new TextStyle();
|
||||
public string SlotName { get; set; }
|
||||
|
||||
public TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
{
|
||||
return GetItem(request.PageContext).MeasureWithoutCache(request);
|
||||
}
|
||||
|
||||
public void Draw(TextDrawingRequest request)
|
||||
{
|
||||
GetItem(request.PageContext).Draw(request);
|
||||
}
|
||||
|
||||
private TextBlockSpan GetItem(IPageContext context)
|
||||
{
|
||||
var pageNumberPlaceholder = 123;
|
||||
|
||||
var pageNumber = context.GetRegisteredLocations().Contains(SlotName)
|
||||
? context.GetLocationPage(SlotName)
|
||||
: pageNumberPlaceholder;
|
||||
|
||||
return new TextBlockSpan
|
||||
{
|
||||
Style = Style,
|
||||
Text = pageNumber.ToString()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
using System.Collections.Generic;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
using QuestPDF.Infrastructure;
|
||||
using Size = QuestPDF.Infrastructure.Size;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
internal class TextBlockSpan : ITextBlockItem
|
||||
{
|
||||
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?>();
|
||||
|
||||
public TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
{
|
||||
var cacheKey = (request.StartIndex, request.AvailableWidth);
|
||||
|
||||
if (!MeasureCache.ContainsKey(cacheKey))
|
||||
MeasureCache[cacheKey] = MeasureWithoutCache(request);
|
||||
|
||||
return MeasureCache[cacheKey];
|
||||
}
|
||||
|
||||
internal TextMeasurementResult? MeasureWithoutCache(TextMeasurementRequest request)
|
||||
{
|
||||
const char space = ' ';
|
||||
|
||||
var paint = Style.ToPaint();
|
||||
var fontMetrics = Style.ToFontMetrics();
|
||||
|
||||
var startIndex = request.StartIndex;
|
||||
|
||||
if (request.IsFirstLineElement)
|
||||
{
|
||||
while (startIndex + 1 < Text.Length && Text[startIndex] == space)
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
if (Text.Length == 0)
|
||||
{
|
||||
return new TextMeasurementResult
|
||||
{
|
||||
Width = 0,
|
||||
|
||||
LineHeight = Style.LineHeight,
|
||||
Ascent = fontMetrics.Ascent,
|
||||
Descent = fontMetrics.Descent
|
||||
};
|
||||
}
|
||||
|
||||
// start breaking text from requested position
|
||||
var text = Text.Substring(startIndex);
|
||||
|
||||
var breakingIndex = (int)paint.BreakText(text, request.AvailableWidth);
|
||||
|
||||
if (breakingIndex <= 0)
|
||||
return null;
|
||||
|
||||
// break text only on spaces
|
||||
if (breakingIndex < text.Length)
|
||||
{
|
||||
var lastSpaceIndex = text.Substring(0, breakingIndex).LastIndexOf(space) - 1;
|
||||
|
||||
if (lastSpaceIndex <= 0)
|
||||
{
|
||||
if (!request.IsFirstLineElement)
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
breakingIndex = lastSpaceIndex + 1;
|
||||
}
|
||||
}
|
||||
|
||||
text = text.Substring(0, breakingIndex);
|
||||
|
||||
var endIndex = startIndex + breakingIndex;
|
||||
var nextIndex = endIndex;
|
||||
|
||||
while (nextIndex + 1 < Text.Length && Text[nextIndex] == space)
|
||||
nextIndex++;
|
||||
|
||||
// measure final text
|
||||
var width = paint.MeasureText(text);
|
||||
|
||||
return new TextMeasurementResult
|
||||
{
|
||||
Width = width,
|
||||
|
||||
Ascent = fontMetrics.Ascent,
|
||||
Descent = fontMetrics.Descent,
|
||||
|
||||
LineHeight = Style.LineHeight,
|
||||
|
||||
StartIndex = startIndex,
|
||||
EndIndex = endIndex,
|
||||
NextIndex = nextIndex,
|
||||
TotalIndex = Text.Length
|
||||
};
|
||||
}
|
||||
|
||||
public void Draw(TextDrawingRequest request)
|
||||
{
|
||||
var fontMetrics = Style.ToFontMetrics();
|
||||
|
||||
var text = Text.Substring(request.StartIndex, request.EndIndex - request.StartIndex);
|
||||
|
||||
request.Canvas.DrawRectangle(new Position(0, request.TotalAscent), new Size(request.TextSize.Width, request.TextSize.Height), Style.BackgroundColor);
|
||||
request.Canvas.DrawText(text, Position.Zero, Style);
|
||||
|
||||
// draw underline
|
||||
if (Style.IsUnderlined && fontMetrics.UnderlinePosition.HasValue)
|
||||
DrawLine(fontMetrics.UnderlinePosition.Value, fontMetrics.UnderlineThickness.Value);
|
||||
|
||||
// draw stroke
|
||||
if (Style.IsStroked && fontMetrics.StrikeoutPosition.HasValue)
|
||||
DrawLine(fontMetrics.StrikeoutPosition.Value, fontMetrics.StrikeoutThickness.Value);
|
||||
|
||||
void DrawLine(float offset, float thickness)
|
||||
{
|
||||
request.Canvas.DrawRectangle(new Position(0, offset - thickness / 2f), new Size(request.TextSize.Width, thickness), Style.Color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
using QuestPDF.Elements.Text.Items;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text
|
||||
{
|
||||
internal class TextBlock : Element, IStateResettable
|
||||
{
|
||||
public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
|
||||
public List<ITextBlockItem> Children { get; set; } = new List<ITextBlockItem>();
|
||||
|
||||
public Queue<ITextBlockItem> RenderingQueue { get; set; }
|
||||
public int CurrentElementIndex { get; set; }
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
RenderingQueue = new Queue<ITextBlockItem>(Children);
|
||||
CurrentElementIndex = 0;
|
||||
}
|
||||
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
if (!RenderingQueue.Any())
|
||||
return new FullRender(Size.Zero);
|
||||
|
||||
var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height);
|
||||
|
||||
if (!lines.Any())
|
||||
return new PartialRender(Size.Zero);
|
||||
|
||||
var width = lines.Max(x => x.Width);
|
||||
var height = lines.Sum(x => x.LineHeight);
|
||||
|
||||
if (width > availableSpace.Width + Size.Epsilon || height > availableSpace.Height + Size.Epsilon)
|
||||
return new Wrap();
|
||||
|
||||
var fullyRenderedItemsCount = lines
|
||||
.SelectMany(x => x.Elements)
|
||||
.GroupBy(x => x.Item)
|
||||
.Count(x => x.Any(y => y.Measurement.IsLast));
|
||||
|
||||
if (fullyRenderedItemsCount == RenderingQueue.Count)
|
||||
return new FullRender(width, height);
|
||||
|
||||
return new PartialRender(width, height);
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height).ToList();
|
||||
|
||||
if (!lines.Any())
|
||||
return;
|
||||
|
||||
var heightOffset = 0f;
|
||||
var widthOffset = 0f;
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
widthOffset = 0f;
|
||||
|
||||
var alignmentOffset = GetAlignmentOffset(line.Width);
|
||||
|
||||
Canvas.Translate(new Position(alignmentOffset, 0));
|
||||
Canvas.Translate(new Position(0, -line.Ascent));
|
||||
|
||||
foreach (var item in line.Elements)
|
||||
{
|
||||
var textDrawingRequest = new TextDrawingRequest
|
||||
{
|
||||
Canvas = Canvas,
|
||||
PageContext = PageContext,
|
||||
|
||||
StartIndex = item.Measurement.StartIndex,
|
||||
EndIndex = item.Measurement.EndIndex,
|
||||
|
||||
TextSize = new Size(item.Measurement.Width, line.LineHeight),
|
||||
TotalAscent = line.Ascent
|
||||
};
|
||||
|
||||
item.Item.Draw(textDrawingRequest);
|
||||
|
||||
Canvas.Translate(new Position(item.Measurement.Width, 0));
|
||||
widthOffset += item.Measurement.Width;
|
||||
}
|
||||
|
||||
Canvas.Translate(new Position(-alignmentOffset, 0));
|
||||
Canvas.Translate(new Position(-line.Width, line.Ascent));
|
||||
Canvas.Translate(new Position(0, line.LineHeight));
|
||||
|
||||
heightOffset += line.LineHeight;
|
||||
}
|
||||
|
||||
Canvas.Translate(new Position(0, -heightOffset));
|
||||
|
||||
lines
|
||||
.SelectMany(x => x.Elements)
|
||||
.GroupBy(x => x.Item)
|
||||
.Where(x => x.Any(y => y.Measurement.IsLast))
|
||||
.Select(x => x.Key)
|
||||
.ToList()
|
||||
.ForEach(x => RenderingQueue.Dequeue());
|
||||
|
||||
var lastElementMeasurement = lines.Last().Elements.Last().Measurement;
|
||||
CurrentElementIndex = lastElementMeasurement.IsLast ? 0 : lastElementMeasurement.NextIndex;
|
||||
|
||||
if (!RenderingQueue.Any())
|
||||
ResetState();
|
||||
|
||||
float GetAlignmentOffset(float lineWidth)
|
||||
{
|
||||
if (Alignment == HorizontalAlignment.Left)
|
||||
return 0;
|
||||
|
||||
var emptySpace = availableSpace.Width - lineWidth;
|
||||
|
||||
if (Alignment == HorizontalAlignment.Right)
|
||||
return emptySpace;
|
||||
|
||||
if (Alignment == HorizontalAlignment.Center)
|
||||
return emptySpace / 2;
|
||||
|
||||
throw new ArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<TextLine> DivideTextItemsIntoLines(float availableWidth, float availableHeight)
|
||||
{
|
||||
var queue = new Queue<ITextBlockItem>(RenderingQueue);
|
||||
var currentItemIndex = CurrentElementIndex;
|
||||
var currentHeight = 0f;
|
||||
|
||||
while (queue.Any())
|
||||
{
|
||||
var line = GetNextLine();
|
||||
|
||||
if (!line.Elements.Any())
|
||||
yield break;
|
||||
|
||||
if (currentHeight + line.LineHeight > availableHeight + Size.Epsilon)
|
||||
yield break;
|
||||
|
||||
currentHeight += line.LineHeight;
|
||||
yield return line;
|
||||
}
|
||||
|
||||
TextLine GetNextLine()
|
||||
{
|
||||
var currentWidth = 0f;
|
||||
|
||||
var currentLineElements = new List<TextLineElement>();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (!queue.Any())
|
||||
break;
|
||||
|
||||
var currentElement = queue.Peek();
|
||||
|
||||
var measurementRequest = new TextMeasurementRequest
|
||||
{
|
||||
Canvas = Canvas,
|
||||
PageContext = PageContext,
|
||||
|
||||
StartIndex = currentItemIndex,
|
||||
AvailableWidth = availableWidth - currentWidth,
|
||||
IsFirstLineElement = !currentLineElements.Any()
|
||||
};
|
||||
|
||||
var measurementResponse = currentElement.Measure(measurementRequest);
|
||||
|
||||
if (measurementResponse == null)
|
||||
break;
|
||||
|
||||
currentLineElements.Add(new TextLineElement
|
||||
{
|
||||
Item = currentElement,
|
||||
Measurement = measurementResponse
|
||||
});
|
||||
|
||||
currentWidth += measurementResponse.Width;
|
||||
currentItemIndex = measurementResponse.NextIndex;
|
||||
|
||||
if (!measurementResponse.IsLast)
|
||||
break;
|
||||
|
||||
currentItemIndex = 0;
|
||||
queue.Dequeue();
|
||||
}
|
||||
|
||||
return new TextLine
|
||||
{
|
||||
Elements = currentLineElements
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Translate : ContainerElement
|
||||
{
|
||||
public float TranslateX { get; set; } = 0;
|
||||
public float TranslateY { get; set; } = 0;
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
|
||||
|
||||
if (skiaCanvas == null)
|
||||
return;
|
||||
|
||||
skiaCanvas.Translate(TranslateX, TranslateY);
|
||||
base.Draw(availableSpace);
|
||||
skiaCanvas.Translate(-TranslateX, -TranslateY);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Unconstrained : ContainerElement
|
||||
{
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
var childSize = Child?.Measure(Size.Max) ?? new FullRender(Size.Zero);
|
||||
|
||||
if (childSize is PartialRender)
|
||||
return new PartialRender(Size.Zero);
|
||||
|
||||
if (childSize is FullRender)
|
||||
return new FullRender(Size.Zero);
|
||||
|
||||
return childSize;
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var measurement = Child?.Measure(Size.Max) as Size;
|
||||
|
||||
if (measurement == null)
|
||||
return;
|
||||
|
||||
Child?.Draw(measurement);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,7 @@ namespace QuestPDF.Fluent
|
||||
var descriptor = new ComponentDescriptor<T>(component);
|
||||
handler?.Invoke(descriptor);
|
||||
|
||||
component.Compose(element);
|
||||
component.Compose(element.Container());
|
||||
}
|
||||
|
||||
static void Component<T>(this IContainer element, Action<ComponentDescriptor<T>>? handler = null) where T : IComponent, new()
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace QuestPDF.Fluent
|
||||
}
|
||||
}
|
||||
|
||||
public static class SectionExtensions
|
||||
public static class DecorationExtensions
|
||||
{
|
||||
public static void Decoration(this IContainer element, Action<DecorationDescriptor> handler)
|
||||
{
|
||||
@@ -14,7 +14,7 @@ namespace QuestPDF.Fluent
|
||||
return container;
|
||||
}
|
||||
|
||||
public static T Element<T>(this IContainer element, T child) where T : IElement
|
||||
internal static T Element<T>(this IContainer element, T child) where T : IElement
|
||||
{
|
||||
if (element?.Child != null && element.Child is Empty == false)
|
||||
{
|
||||
@@ -33,21 +33,12 @@ namespace QuestPDF.Fluent
|
||||
|
||||
public static void Element<TParent>(this TParent parent, Action<IContainer> handler) where TParent : IContainer
|
||||
{
|
||||
handler(parent);
|
||||
handler(parent.Container());
|
||||
}
|
||||
|
||||
public static IContainer Element<TParent>(this TParent parent, Func<IContainer, IContainer> handler) where TParent : IContainer
|
||||
{
|
||||
return handler(parent);
|
||||
}
|
||||
|
||||
public static void PageNumber(this IContainer element, string textFormat = "{number}", TextStyle? style = null)
|
||||
{
|
||||
element.Element(new PageNumber
|
||||
{
|
||||
TextFormat = textFormat,
|
||||
TextStyle = style ?? TextStyle.Default
|
||||
});
|
||||
return handler(parent.Container()).Container();
|
||||
}
|
||||
|
||||
public static IContainer AspectRatio(this IContainer element, float ratio, AspectRatioOption option = AspectRatioOption.FitWidth)
|
||||
@@ -92,25 +83,7 @@ namespace QuestPDF.Fluent
|
||||
MinHeight = minHeight
|
||||
});
|
||||
}
|
||||
|
||||
public static void Text(this IContainer element, object text, TextStyle? style = null)
|
||||
{
|
||||
text ??= string.Empty;
|
||||
style ??= TextStyle.Default;
|
||||
|
||||
if (element is Alignment alignment)
|
||||
{
|
||||
style = style.Clone();
|
||||
style.Alignment = alignment.Horizontal;
|
||||
}
|
||||
|
||||
element.Element(new Text()
|
||||
{
|
||||
Value = text.ToString(),
|
||||
Style = style
|
||||
});
|
||||
}
|
||||
|
||||
public static void PageBreak(this IContainer element)
|
||||
{
|
||||
element.Element(new PageBreak());
|
||||
@@ -152,7 +125,7 @@ namespace QuestPDF.Fluent
|
||||
|
||||
public static void Canvas(this IContainer element, DrawOnCanvas handler)
|
||||
{
|
||||
element.Element(new Elements.Canvas
|
||||
element.Element(new Canvas
|
||||
{
|
||||
Handler = handler
|
||||
});
|
||||
@@ -162,5 +135,10 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
return element.Element(new Box());
|
||||
}
|
||||
|
||||
public static IContainer Unconstrained(this IContainer element)
|
||||
{
|
||||
return element.Element(new Unconstrained());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,10 @@ namespace QuestPDF.Fluent
|
||||
foreach (var imageData in document.GenerateImages())
|
||||
{
|
||||
var path = filePath(index);
|
||||
|
||||
if (File.Exists(path))
|
||||
File.Delete(path);
|
||||
|
||||
File.WriteAllBytes(path, imageData);
|
||||
index++;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace QuestPDF.Fluent
|
||||
public void AlignCenter() => Alignment(HorizontalAlignment.Center);
|
||||
public void AlignRight() => Alignment(HorizontalAlignment.Right);
|
||||
|
||||
public IContainer Element(int columns = 1)
|
||||
public IContainer Item(int columns = 1)
|
||||
{
|
||||
var container = new Container();
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
@@ -8,17 +10,62 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
internal Page Page { get; } = new Page();
|
||||
|
||||
public void Size(PageSize pageSize)
|
||||
{
|
||||
Page.MinSize = pageSize;
|
||||
Page.MaxSize = pageSize;
|
||||
}
|
||||
|
||||
public void ContinuousSize(float width)
|
||||
{
|
||||
Page.MinSize = new PageSize(width, 0);
|
||||
Page.MaxSize = new PageSize(width, Infrastructure.Size.Max.Height);
|
||||
}
|
||||
|
||||
public void MarginLeft(float value)
|
||||
{
|
||||
Page.MarginLeft = value;
|
||||
}
|
||||
|
||||
public void MarginRight(float value)
|
||||
{
|
||||
Page.MarginRight = value;
|
||||
}
|
||||
|
||||
public void MarginTop(float value)
|
||||
{
|
||||
Page.MarginTop = value;
|
||||
}
|
||||
|
||||
public void MarginBottom(float value)
|
||||
{
|
||||
Page.MarginBottom = value;
|
||||
}
|
||||
|
||||
public void MarginVertical(float value)
|
||||
{
|
||||
MarginTop(value);
|
||||
MarginBottom(value);
|
||||
}
|
||||
|
||||
public void MarginHorizontal(float value)
|
||||
{
|
||||
MarginLeft(value);
|
||||
MarginRight(value);
|
||||
}
|
||||
|
||||
public void Margin(float value)
|
||||
{
|
||||
MarginVertical(value);
|
||||
MarginHorizontal(value);
|
||||
}
|
||||
|
||||
public IContainer Header()
|
||||
{
|
||||
var container = new Container();
|
||||
Page.Header = container;
|
||||
return container;
|
||||
}
|
||||
|
||||
public void Header(Action<IContainer> handler)
|
||||
{
|
||||
handler?.Invoke(Header());
|
||||
}
|
||||
|
||||
public IContainer Content()
|
||||
{
|
||||
@@ -27,31 +74,24 @@ namespace QuestPDF.Fluent
|
||||
return container;
|
||||
}
|
||||
|
||||
public void Content(Action<IContainer> handler)
|
||||
{
|
||||
handler?.Invoke(Content());
|
||||
}
|
||||
|
||||
public IContainer Footer()
|
||||
{
|
||||
var container = new Container();
|
||||
Page.Footer = container;
|
||||
return container;
|
||||
}
|
||||
|
||||
public void Footer(Action<IContainer> handler)
|
||||
{
|
||||
handler?.Invoke(Footer());
|
||||
}
|
||||
}
|
||||
|
||||
public static class PageExtensions
|
||||
{
|
||||
public static void Page(this IContainer document, Action<PageDescriptor> handler)
|
||||
public static IDocumentContainer Page(this IDocumentContainer document, Action<PageDescriptor> handler)
|
||||
{
|
||||
var descriptor = new PageDescriptor();
|
||||
handler(descriptor);
|
||||
document.Component(descriptor.Page);
|
||||
|
||||
(document as DocumentContainer).Pages.Add(descriptor.Page);
|
||||
|
||||
return document;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public static class RotateExtensions
|
||||
{
|
||||
private static IContainer SimpleRotate(this IContainer element, Action<SimpleRotate> handler)
|
||||
{
|
||||
var scale = element as SimpleRotate ?? new SimpleRotate();
|
||||
handler(scale);
|
||||
|
||||
return element.Element(scale);
|
||||
}
|
||||
|
||||
public static IContainer RotateLeft(this IContainer element)
|
||||
{
|
||||
return element.SimpleRotate(x => x.TurnCount--);
|
||||
}
|
||||
|
||||
public static IContainer RotateRight(this IContainer element)
|
||||
{
|
||||
return element.SimpleRotate(x => x.TurnCount++);
|
||||
}
|
||||
|
||||
/// <param name="angle">In degrees</param>
|
||||
public static IContainer Rotate(this IContainer element, float angle)
|
||||
{
|
||||
return element.Element(new Rotate
|
||||
{
|
||||
Angle = angle
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
@@ -8,19 +6,18 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
public class RowDescriptor
|
||||
{
|
||||
private List<RowElement> Elements { get; } = new List<RowElement>();
|
||||
private float RowSpacing { get; set; } = 0;
|
||||
|
||||
internal Row Row { get; } = new Row();
|
||||
|
||||
public void Spacing(float value)
|
||||
{
|
||||
RowSpacing = value;
|
||||
Row.Spacing = value;
|
||||
}
|
||||
|
||||
public IContainer ConstantColumn(float width)
|
||||
{
|
||||
var element = new ConstantRowElement(width);
|
||||
|
||||
Elements.Add(element);
|
||||
Row.Children.Add(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
@@ -28,36 +25,9 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
var element = new RelativeRowElement(width);
|
||||
|
||||
Elements.Add(element);
|
||||
Row.Children.Add(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
internal Element CreateRow()
|
||||
{
|
||||
if (Elements.Count == 0)
|
||||
return new Empty();
|
||||
|
||||
if (RowSpacing <= Size.Epsilon)
|
||||
return new Row
|
||||
{
|
||||
Children = Elements
|
||||
};
|
||||
|
||||
var children = Elements
|
||||
.SelectMany(x => new[] {x, new ConstantRowElement(RowSpacing) })
|
||||
.ToList();
|
||||
|
||||
var row = new Row
|
||||
{
|
||||
Children = children
|
||||
};
|
||||
|
||||
return new Padding
|
||||
{
|
||||
Right = -RowSpacing,
|
||||
Child = row
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static class RowExtensions
|
||||
@@ -66,7 +36,7 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
var descriptor = new RowDescriptor();
|
||||
handler(descriptor);
|
||||
element.Element(descriptor.CreateRow());
|
||||
element.Element(descriptor.Row);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public static class ScaleExtensions
|
||||
{
|
||||
private static IContainer Scale(this IContainer element, Action<Scale> handler)
|
||||
{
|
||||
var scale = element as Scale ?? new Scale();
|
||||
handler(scale);
|
||||
|
||||
return element.Element(scale);
|
||||
}
|
||||
|
||||
public static IContainer Scale(this IContainer element, float value)
|
||||
{
|
||||
return element.ScaleHorizontal(value).ScaleVertical(value);
|
||||
}
|
||||
|
||||
public static IContainer ScaleHorizontal(this IContainer element, float value)
|
||||
{
|
||||
return element.Scale(x => x.ScaleX = value);
|
||||
}
|
||||
|
||||
public static IContainer FlipHorizontal(this IContainer element)
|
||||
{
|
||||
return element.ScaleHorizontal(-1);
|
||||
}
|
||||
|
||||
public static IContainer ScaleVertical(this IContainer element, float value)
|
||||
{
|
||||
return element.Scale(x => x.ScaleY = value);
|
||||
}
|
||||
|
||||
public static IContainer FlipVertical(this IContainer element)
|
||||
{
|
||||
return element.ScaleVertical(-1);
|
||||
}
|
||||
|
||||
public static IContainer FlipOver(this IContainer element)
|
||||
{
|
||||
return element.FlipHorizontal().FlipVertical();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
@@ -8,57 +6,19 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
public class StackDescriptor
|
||||
{
|
||||
private List<Element> Items { get; } = new List<Element>();
|
||||
private float StackSpacing { get; set; } = 0;
|
||||
|
||||
internal Stack Stack { get; } = new Stack();
|
||||
|
||||
public void Spacing(float value)
|
||||
{
|
||||
StackSpacing = value;
|
||||
Stack.Spacing = value;
|
||||
}
|
||||
|
||||
public IContainer Item()
|
||||
{
|
||||
var container = new Container();
|
||||
Items.Add(container);
|
||||
Stack.Children.Add(container);
|
||||
return container;
|
||||
}
|
||||
|
||||
public void Item(Action<IContainer> handler)
|
||||
{
|
||||
handler?.Invoke(Item());
|
||||
}
|
||||
|
||||
internal Element CreateStack()
|
||||
{
|
||||
if (Items.Count == 0)
|
||||
return new Empty();
|
||||
|
||||
if (StackSpacing <= Size.Epsilon)
|
||||
return new Stack
|
||||
{
|
||||
Children = Items
|
||||
};
|
||||
|
||||
var children = Items
|
||||
.Select(x => new Padding
|
||||
{
|
||||
Bottom = StackSpacing,
|
||||
Child = x
|
||||
})
|
||||
.Cast<Element>()
|
||||
.ToList();
|
||||
|
||||
var stack = new Stack
|
||||
{
|
||||
Children = children
|
||||
};
|
||||
|
||||
return new Padding
|
||||
{
|
||||
Bottom = -StackSpacing,
|
||||
Child = stack
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static class StackExtensions
|
||||
@@ -67,7 +27,7 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
var descriptor = new StackDescriptor();
|
||||
handler(descriptor);
|
||||
element.Element(descriptor.CreateStack());
|
||||
element.Component(descriptor.Stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Elements.Text;
|
||||
using QuestPDF.Elements.Text.Items;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public class TextDescriptor
|
||||
{
|
||||
private ICollection<TextBlock> TextBlocks { get; } = new List<TextBlock>();
|
||||
private TextStyle DefaultStyle { get; set; } = TextStyle.Default;
|
||||
private HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
|
||||
private float Spacing { get; set; } = 0f;
|
||||
|
||||
public void DefaultTextStyle(TextStyle style)
|
||||
{
|
||||
DefaultStyle = style;
|
||||
}
|
||||
|
||||
public void AlignLeft()
|
||||
{
|
||||
Alignment = HorizontalAlignment.Left;
|
||||
}
|
||||
|
||||
public void AlignCenter()
|
||||
{
|
||||
Alignment = HorizontalAlignment.Center;
|
||||
}
|
||||
|
||||
public void AlignRight()
|
||||
{
|
||||
Alignment = HorizontalAlignment.Right;
|
||||
}
|
||||
|
||||
public void ParagraphSpacing(float value)
|
||||
{
|
||||
Spacing = value;
|
||||
}
|
||||
|
||||
private void AddItemToLastTextBlock(ITextBlockItem item)
|
||||
{
|
||||
if (!TextBlocks.Any())
|
||||
TextBlocks.Add(new TextBlock());
|
||||
|
||||
TextBlocks.Last().Children.Add(item);
|
||||
}
|
||||
|
||||
public void Span(string text, TextStyle? style = null)
|
||||
{
|
||||
style ??= DefaultStyle;
|
||||
|
||||
var items = text
|
||||
.Split(new[] { Environment.NewLine }, StringSplitOptions.None)
|
||||
.Select(x => new TextBlockSpan
|
||||
{
|
||||
Text = x,
|
||||
Style = style
|
||||
})
|
||||
.ToList();
|
||||
|
||||
AddItemToLastTextBlock(items.First());
|
||||
|
||||
items
|
||||
.Skip(1)
|
||||
.Select(x => new TextBlock
|
||||
{
|
||||
Children = new List<ITextBlockItem> { x }
|
||||
})
|
||||
.ToList()
|
||||
.ForEach(TextBlocks.Add);
|
||||
}
|
||||
|
||||
public void NewLine()
|
||||
{
|
||||
Span(Environment.NewLine);
|
||||
}
|
||||
|
||||
private void PageNumber(string slotName, TextStyle? style = null)
|
||||
{
|
||||
style ??= DefaultStyle;
|
||||
|
||||
AddItemToLastTextBlock(new TextBlockPageNumber()
|
||||
{
|
||||
Style = style,
|
||||
SlotName = slotName
|
||||
});
|
||||
}
|
||||
|
||||
public void CurrentPageNumber(TextStyle? style = null)
|
||||
{
|
||||
PageNumber(PageContext.CurrentPageSlot, style);
|
||||
}
|
||||
|
||||
public void TotalPages(TextStyle? style = null)
|
||||
{
|
||||
PageNumber(PageContext.TotalPagesSlot, style);
|
||||
}
|
||||
|
||||
public void PageNumberOfLocation(string locationName, TextStyle? style = null)
|
||||
{
|
||||
PageNumber(locationName, style);
|
||||
}
|
||||
|
||||
public void InternalLocation(string text, string locationName, TextStyle? style = null)
|
||||
{
|
||||
style ??= DefaultStyle;
|
||||
|
||||
AddItemToLastTextBlock(new TextBlockInternalLink
|
||||
{
|
||||
Style = style,
|
||||
Text = text,
|
||||
LocationName = locationName
|
||||
});
|
||||
}
|
||||
|
||||
public void ExternalLocation(string text, string url, TextStyle? style = null)
|
||||
{
|
||||
style ??= DefaultStyle;
|
||||
|
||||
AddItemToLastTextBlock(new TextBlockExternalLink
|
||||
{
|
||||
Style = style,
|
||||
Text = text,
|
||||
Url = url
|
||||
});
|
||||
}
|
||||
|
||||
public IContainer Element()
|
||||
{
|
||||
var container = new Container();
|
||||
|
||||
AddItemToLastTextBlock(new TextBlockElement
|
||||
{
|
||||
Element = container
|
||||
});
|
||||
|
||||
return container.Box();
|
||||
}
|
||||
|
||||
internal void Compose(IContainer container)
|
||||
{
|
||||
TextBlocks.ToList().ForEach(x => x.Alignment = Alignment);
|
||||
|
||||
container.Stack(stack =>
|
||||
{
|
||||
stack.Spacing(Spacing);
|
||||
|
||||
foreach (var textBlock in TextBlocks)
|
||||
stack.Item().Element(textBlock);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class TextExtensions
|
||||
{
|
||||
public static void Text(this IContainer element, Action<TextDescriptor> content)
|
||||
{
|
||||
var textBlock = new TextBlock();
|
||||
|
||||
if (element is Alignment alignment)
|
||||
textBlock.Alignment = alignment.Horizontal;
|
||||
|
||||
var descriptor = new TextDescriptor();
|
||||
content?.Invoke(descriptor);
|
||||
descriptor.Compose(element);
|
||||
}
|
||||
|
||||
public static void Text(this IContainer element, object text, TextStyle? style = null)
|
||||
{
|
||||
element.Text(x => x.Span(text.ToString(), style));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,11 @@ namespace QuestPDF.Fluent
|
||||
return style.Mutate(x => x.Color = value);
|
||||
}
|
||||
|
||||
public static TextStyle BackgroundColor(this TextStyle style, string value)
|
||||
{
|
||||
return style.Mutate(x => x.BackgroundColor = value);
|
||||
}
|
||||
|
||||
public static TextStyle FontType(this TextStyle style, string value)
|
||||
{
|
||||
return style.Mutate(x => x.FontType = value);
|
||||
@@ -37,31 +42,17 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
return style.Mutate(x => x.IsItalic = value);
|
||||
}
|
||||
|
||||
public static TextStyle Stroked(this TextStyle style, bool value = true)
|
||||
{
|
||||
return style.Mutate(x => x.IsStroked = value);
|
||||
}
|
||||
|
||||
public static TextStyle Underlined(this TextStyle style, bool value = true)
|
||||
{
|
||||
return style.Mutate(x => x.IsUnderlined = value);
|
||||
}
|
||||
|
||||
#region Alignmnet
|
||||
|
||||
public static TextStyle Alignment(this TextStyle style, HorizontalAlignment value)
|
||||
{
|
||||
return style.Mutate(x => x.Alignment = value);
|
||||
}
|
||||
|
||||
public static TextStyle AlignLeft(this TextStyle style)
|
||||
{
|
||||
return style.Alignment(HorizontalAlignment.Left);
|
||||
}
|
||||
|
||||
public static TextStyle AlignCenter(this TextStyle style)
|
||||
{
|
||||
return style.Alignment(HorizontalAlignment.Center);
|
||||
}
|
||||
|
||||
public static TextStyle AlignRight(this TextStyle style)
|
||||
{
|
||||
return style.Alignment(HorizontalAlignment.Right);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Weight
|
||||
|
||||
public static TextStyle Weight(this TextStyle style, FontWeight weight)
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public static class TranslateExtensions
|
||||
{
|
||||
private static IContainer Translate(this IContainer element, Action<Translate> handler)
|
||||
{
|
||||
var translate = element as Translate ?? new Translate();
|
||||
handler(translate);
|
||||
|
||||
return element.Element(translate);
|
||||
}
|
||||
|
||||
public static IContainer TranslateX(this IContainer element, float value)
|
||||
{
|
||||
return element.Translate(x => x.TranslateX = value);
|
||||
}
|
||||
|
||||
public static IContainer TranslateY(this IContainer element, float value)
|
||||
{
|
||||
return element.Translate(x => x.TranslateY = value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,46 +15,46 @@ namespace QuestPDF.Helpers
|
||||
{
|
||||
public const int PointsPerInch = 72;
|
||||
|
||||
public static Size A0 => new PageSize(2384.2f, 3370.8f);
|
||||
public static Size A1 => new PageSize(1684, 2384.2f);
|
||||
public static Size A2 => new PageSize(1190.7f, 1684);
|
||||
public static Size A3 => new PageSize(842, 1190.7f);
|
||||
public static Size A4 => new PageSize(595.4f, 842);
|
||||
public static Size A5 => new PageSize(419.6f, 595.4f);
|
||||
public static Size A6 => new PageSize(297.7f, 419.6f);
|
||||
public static Size A7 => new PageSize(209.8f, 297.7f);
|
||||
public static Size A8 => new PageSize(147.4f, 209.8f);
|
||||
public static Size A9 => new PageSize(104.9f, 147.4f);
|
||||
public static Size A10 => new PageSize(73.7f, 104.9f);
|
||||
public static PageSize A0 => new PageSize(2384.2f, 3370.8f);
|
||||
public static PageSize A1 => new PageSize(1684, 2384.2f);
|
||||
public static PageSize A2 => new PageSize(1190.7f, 1684);
|
||||
public static PageSize A3 => new PageSize(842, 1190.7f);
|
||||
public static PageSize A4 => new PageSize(595.4f, 842);
|
||||
public static PageSize A5 => new PageSize(419.6f, 595.4f);
|
||||
public static PageSize A6 => new PageSize(297.7f, 419.6f);
|
||||
public static PageSize A7 => new PageSize(209.8f, 297.7f);
|
||||
public static PageSize A8 => new PageSize(147.4f, 209.8f);
|
||||
public static PageSize A9 => new PageSize(104.9f, 147.4f);
|
||||
public static PageSize A10 => new PageSize(73.7f, 104.9f);
|
||||
|
||||
public static Size B0 => new PageSize(2835, 4008.7f);
|
||||
public static Size B1 => new PageSize(2004.3f, 2835);
|
||||
public static Size B2 => new PageSize(1417.5f, 2004.3f);
|
||||
public static Size B3 => new PageSize(1000.8f, 1417.5f);
|
||||
public static Size B4 => new PageSize(708.8f, 1000.8f);
|
||||
public static Size B5 => new PageSize(499, 708.8f);
|
||||
public static Size B6 => new PageSize(354.4f, 499);
|
||||
public static Size B7 => new PageSize(249.5f, 354.4f);
|
||||
public static Size B8 => new PageSize(175.8f, 249.5f);
|
||||
public static Size B9 => new PageSize(124.7f, 175.8f);
|
||||
public static Size B10 => new PageSize(87.9f, 124.7f);
|
||||
public static PageSize B0 => new PageSize(2835, 4008.7f);
|
||||
public static PageSize B1 => new PageSize(2004.3f, 2835);
|
||||
public static PageSize B2 => new PageSize(1417.5f, 2004.3f);
|
||||
public static PageSize B3 => new PageSize(1000.8f, 1417.5f);
|
||||
public static PageSize B4 => new PageSize(708.8f, 1000.8f);
|
||||
public static PageSize B5 => new PageSize(499, 708.8f);
|
||||
public static PageSize B6 => new PageSize(354.4f, 499);
|
||||
public static PageSize B7 => new PageSize(249.5f, 354.4f);
|
||||
public static PageSize B8 => new PageSize(175.8f, 249.5f);
|
||||
public static PageSize B9 => new PageSize(124.7f, 175.8f);
|
||||
public static PageSize B10 => new PageSize(87.9f, 124.7f);
|
||||
|
||||
public static Size Env10 => new PageSize(683.2f, 294.8f);
|
||||
public static Size EnvC4 => new PageSize(649.2f, 918.5f);
|
||||
public static Size EnvDL => new PageSize(311.9f, 623.7f);
|
||||
public static PageSize Env10 => new PageSize(683.2f, 294.8f);
|
||||
public static PageSize EnvC4 => new PageSize(649.2f, 918.5f);
|
||||
public static PageSize EnvDL => new PageSize(311.9f, 623.7f);
|
||||
|
||||
public static Size Executive => new PageSize(522, 756);
|
||||
public static Size Legal => new PageSize(612.4f, 1009.3f);
|
||||
public static Size Letter => new PageSize(612.4f, 791);
|
||||
public static PageSize Executive => new PageSize(522, 756);
|
||||
public static PageSize Legal => new PageSize(612.4f, 1009.3f);
|
||||
public static PageSize Letter => new PageSize(612.4f, 791);
|
||||
|
||||
public static Size ARCH_A => new PageSize(649.2f, 864.7f);
|
||||
public static Size ARCH_B => new PageSize(864.7f, 1295.6f);
|
||||
public static Size ARCH_C => new PageSize(1295.6f, 1729.3f);
|
||||
public static Size ARCH_D => new PageSize(1729.3f, 2591.2f);
|
||||
public static Size ARCH_E => new PageSize(2591.2f, 3455.9f);
|
||||
public static Size ARCH_E1 => new PageSize(2160.3f, 3024.9f);
|
||||
public static Size ARCH_E2 => new PageSize(1871.1f, 2735.8f);
|
||||
public static Size ARCH_E3 => new PageSize(1944.8f, 2809.5f);
|
||||
public static PageSize ARCH_A => new PageSize(649.2f, 864.7f);
|
||||
public static PageSize ARCH_B => new PageSize(864.7f, 1295.6f);
|
||||
public static PageSize ARCH_C => new PageSize(1295.6f, 1729.3f);
|
||||
public static PageSize ARCH_D => new PageSize(1729.3f, 2591.2f);
|
||||
public static PageSize ARCH_E => new PageSize(2591.2f, 3455.9f);
|
||||
public static PageSize ARCH_E1 => new PageSize(2160.3f, 3024.9f);
|
||||
public static PageSize ARCH_E2 => new PageSize(1871.1f, 2735.8f);
|
||||
public static PageSize ARCH_E3 => new PageSize(1944.8f, 2809.5f);
|
||||
}
|
||||
|
||||
public static class PageSizeExtensions
|
||||
|
||||
@@ -155,25 +155,25 @@ namespace QuestPDF.Helpers
|
||||
|
||||
private static readonly string[] BackgroundColors =
|
||||
{
|
||||
Colors.Red.Lighten2,
|
||||
Colors.Pink.Lighten2,
|
||||
Colors.Purple.Lighten2,
|
||||
Colors.DeepPurple.Lighten2,
|
||||
Colors.Indigo.Lighten2,
|
||||
Colors.Blue.Lighten2,
|
||||
Colors.LightBlue.Lighten2,
|
||||
Colors.Cyan.Lighten2,
|
||||
Colors.Teal.Lighten2,
|
||||
Colors.Green.Lighten2,
|
||||
Colors.LightGreen.Lighten2,
|
||||
Colors.Lime.Lighten2,
|
||||
Colors.Yellow.Lighten2,
|
||||
Colors.Amber.Lighten2,
|
||||
Colors.Orange.Lighten2,
|
||||
Colors.DeepOrange.Lighten2,
|
||||
Colors.Brown.Lighten2,
|
||||
Colors.Grey.Lighten2,
|
||||
Colors.BlueGrey.Lighten2
|
||||
Colors.Red.Lighten3,
|
||||
Colors.Pink.Lighten3,
|
||||
Colors.Purple.Lighten3,
|
||||
Colors.DeepPurple.Lighten3,
|
||||
Colors.Indigo.Lighten3,
|
||||
Colors.Blue.Lighten3,
|
||||
Colors.LightBlue.Lighten3,
|
||||
Colors.Cyan.Lighten3,
|
||||
Colors.Teal.Lighten3,
|
||||
Colors.Green.Lighten3,
|
||||
Colors.LightGreen.Lighten3,
|
||||
Colors.Lime.Lighten3,
|
||||
Colors.Yellow.Lighten3,
|
||||
Colors.Amber.Lighten3,
|
||||
Colors.Orange.Lighten3,
|
||||
Colors.DeepOrange.Lighten3,
|
||||
Colors.Brown.Lighten3,
|
||||
Colors.Grey.Lighten3,
|
||||
Colors.BlueGrey.Lighten3
|
||||
};
|
||||
|
||||
public static string BackgroundColor()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user