Compare commits

...

21 Commits

Author SHA1 Message Date
Marcin Ziąbek 7f94ae589a Updated release notes and code example 2021-08-03 01:43:48 +02:00
Marcin Ziąbek adfce6204e Improved testing environment by adding proper result files names, Added more code examples 2021-08-03 01:35:46 +02:00
Marcin Ziąbek 6d3c8d0708 Added elements: Rotate, Unconstrained 2021-08-01 21:09:16 +02:00
Marcin Ziąbek 3b8c300126 Merge pull request #17 from QuestPDF/2021.6
Merge 2021.6 for QuestPdf 2021.8
2021-08-01 16:58:44 +02:00
MarcinZiabek 239a042615 Fixed rendering for the "Rotate" element 2021-07-28 21:36:51 +02:00
Marcin Ziąbek 49fb6dadb3 Improved null safety. Fixed tests 2021-07-26 12:26:31 +02:00
Marcin Ziąbek c79a4a0c08 Fixed rendering images 2021-07-26 11:59:48 +02:00
Marcin Ziąbek 31191efd88 Multipass rendering. Total pages / location position support. Continuous pages. 2021-07-26 11:35:52 +02:00
Marcin Ziąbek 20fe49dd01 Update SECURITY.md 2021-07-18 18:10:55 +02:00
Marcin Ziąbek 7cdc1fcee2 Create SECURITY.md 2021-07-18 18:10:20 +02:00
Marcin Ziąbek 14f11232be New elements: rotate, scale, translate 2021-06-09 23:34:25 +02:00
Marcin Ziąbek c05c5e1597 Update readme.md
Updated code, added the support section
2021-05-15 16:09:01 +02:00
Marcin Ziąbek 07f1eb99b9 Fixed: stack should not apply spacing for empty items 2021-05-05 13:26:46 +02:00
Marcin Ziąbek 44d7538578 Examples to the documentation 2021-05-05 00:10:48 +02:00
Marcin Ziąbek b894262f7e Improved developer experience via injecting Element() and Component() content in separate containers 2021-05-03 21:04:23 +02:00
Marcin Ziąbek 4423774648 Fixed Grid spacing. Adjusted performance test 2021-05-03 13:32:46 +02:00
Marcin Ziąbek 5d4f1f530a Merge pull request #11 from QuestPDF/tree_stack
Improved Stack and Row
2021-05-03 11:40:57 +02:00
Marcin Ziąbek 3ba8185871 Merge pull request #10 from QuestPDF/2021.05
2021.05 Update
2021-05-03 11:39:59 +02:00
Marcin Ziąbek b6e0bd505c Merge branch 'main' into 2021.05 2021-05-03 11:39:17 +02:00
MarcinZiabek 4e3ba1d878 Fixed wrong import in QuestPDF.Examples.csproj 2021-04-16 10:22:41 +02:00
Marcin Ziąbek 6f0c6513da Bugfix: exception when providing null content into the Text element 2021-03-31 13:00:29 +02:00
80 changed files with 2226 additions and 860 deletions
+738 -206
View File
@@ -1,226 +1,758 @@
using System;
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 Decoration(IContainer container)
{
container
.Background("#FFF")
.Padding(25)
.Decoration(decoration =>
RenderingTest
.Create()
.PageSize(200, 150)
.FileName()
.Render(container =>
{
decoration
.Header()
.Background("#888")
.Padding(10)
.Text("Notes", TextStyle.Default.Size(16).Color("#FFF"));
decoration
.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.Item(8).Background("#DDD").Height(50).Padding(5).Text("This is a short text", textStyle);
grid.Item(4).Background("#BBB").Padding(5).Text("Showing how to...", textStyle);
grid.Item(2).Background("#999").Height(50);
grid.Item(4).Background("#AAA").Border(2).BorderColor("#666").Padding(5).Text("...generate", textStyle);
grid.Item(6).Background("#CCC").Padding(5).Text("simple grids", textStyle.Size(18).Bold());
grid.Item(8).Background("#DDD").Height(50);
});
}
[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()
.PageNumber("Page {pdf:currentPage}", 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(400, 250)
.FileName()
.Render(container =>
{
container
.Padding(25)
.Stack(stack =>
{
var scales = new[] { 0.75f, 1f, 1.25f, 1.5f };
foreach (var scale in scales)
{
stack
.Item()
.Border(1)
.Scale(scale)
.Padding(10)
.Text($"Content with {scale} scale.", TextStyle.Default.Size(20));
}
});
});
}
[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;
}
}
}
+52
View File
@@ -0,0 +1,52 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using QuestPDF.Drawing;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples.Engine
{
public class RenderingTest
{
private string FileNamePrefix = "test";
private Size Size { get; set; }
private RenderingTest()
{
}
public static RenderingTest Create()
{
return new RenderingTest();
}
public RenderingTest FileName([CallerMemberName] string fileName = "test")
{
FileNamePrefix = fileName;
return this;
}
public RenderingTest PageSize(int width, int height)
{
Size = new Size(width, height);
return this;
}
public void Render(Action<IContainer> content)
{
var container = new Container();
content(container);
Func<int, string> fileNameSchema = i => $"{FileNamePrefix}-${i}.png";
var document = new SimpleDocument(container, Size);
document.GenerateImages(fileNameSchema);
Process.Start("explorer", fileNameSchema(0));
}
}
}
@@ -1,9 +0,0 @@
using System;
namespace QuestPDF.Examples.Engine
{
public class ShowResultAttribute : Attribute
{
}
}
+12 -5
View File
@@ -1,4 +1,5 @@
using QuestPDF.Drawing;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
@@ -7,6 +8,8 @@ namespace QuestPDF.Examples.Engine
{
public class SimpleDocument : IDocument
{
public const int ImageScalingFactor = 2;
private IContainer Container { get; }
private Size Size { get; }
@@ -20,14 +23,18 @@ namespace QuestPDF.Examples.Engine
{
return new DocumentMetadata()
{
RasterDpi = PageSizes.PointsPerInch * 2,
Size = Size
RasterDpi = PageSizes.PointsPerInch * ImageScalingFactor,
DocumentLayoutExceptionThreshold = 10
};
}
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);
});
}
}
}
+20 -13
View File
@@ -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());
});
}
});
}
});
}
}
+23 -16
View File
@@ -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");
});
});
}
}
+139 -106
View File
@@ -1,132 +1,165 @@
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)
.Text("Sample text", TextStyle.Default.FontType("Segoe UI emoji").Alignment(HorizontalAlignment.Center));
});
}
[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");
});
});
});
}
+6 -7
View File
@@ -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, 50).Select(x => GenerateSection()).ToList(),
Photos = Enumerable.Range(0, 30).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),
@@ -72,7 +72,7 @@ namespace QuestPDF.ReportSample.Layouts
return;
}
container.Debug("Photo gallery").Grid(grid =>
container.Debug("Photos").Grid(grid =>
{
grid.Spacing(5);
grid.Columns(3);
@@ -18,21 +18,23 @@ 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.MarginVertical(40);
page.MarginHorizontal(50);
page.Size(PageSizes.A4);
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().PageNumber("Page {number}");
page.Footer().AlignCenter().PageNumber();
});
}
@@ -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().PageNumber($"Page {{pdf:{locationName}}}", Typography.Normal.AlignRight());
});
}
}
+3 -1
View File
@@ -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 decimal performanceTarget = 1; // documents per second
// create report models
var reports = Enumerable
+105 -34
View File
@@ -1,4 +1,5 @@
using FluentAssertions;
using FluentAssertions.Equivalency;
using NUnit.Framework;
using QuestPDF.Elements;
using QuestPDF.Fluent;
@@ -6,7 +7,7 @@ using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests
{
/*[TestFixture]
[TestFixture]
public class GridTests
{
#region Alignment
@@ -39,30 +40,30 @@ namespace QuestPDF.UnitTests
// 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(Empty.Instance);
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(Empty.Instance);
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(Empty.Instance);
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]
@@ -93,33 +94,33 @@ namespace QuestPDF.UnitTests
// assert
var expected = new Container();
expected.Stack(stack =>
expected.Container().Stack(stack =>
{
stack.Item().Row(row =>
{
row.RelativeColumn(1).Element(Empty.Instance);
row.RelativeColumn(6).Element(childA);
row.RelativeColumn(4).Element(childB);
row.RelativeColumn(1).Element(Empty.Instance);
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(Empty.Instance);
row.RelativeColumn(4).Element(childC);
row.RelativeColumn(2).Element(childD);
row.RelativeColumn(3).Element(Empty.Instance);
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(Empty.Instance);
row.RelativeColumn(8).Element(childE);
row.RelativeColumn(2).Element(Empty.Instance);
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]
@@ -154,28 +155,98 @@ namespace QuestPDF.UnitTests
{
stack.Item().Row(row =>
{
row.RelativeColumn(2).Element(Empty.Instance);
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(Empty.Instance);
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(Empty.Instance);
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
}
}
+1 -1
View File
@@ -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()
+3 -3
View File
@@ -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]
+2 -1
View File
@@ -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();
+1 -1
View File
@@ -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 -1
View File
@@ -205,6 +205,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());
@@ -223,7 +225,8 @@ namespace QuestPDF.UnitTests.TestEngine
public TestPlan CheckDrawResult()
{
Element.Draw(Canvas, OperationInput);
Element.HandleVisitor(x => x?.Initialize(null, Canvas));
Element.Draw(OperationInput);
return this;
}
-59
View File
@@ -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);
}
}
}
+36
View File
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
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;
}
}
}
+49 -84
View File
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
@@ -14,115 +16,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
View File
@@ -6,7 +6,6 @@ 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; }
+71
View File
@@ -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
}
}
+52
View File
@@ -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();
}
}
}
+65
View File
@@ -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
};
}
}
}
+58
View File
@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using QuestPDF.Elements;
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);
}
}
}
+4 -4
View File
@@ -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)
+2 -2
View File
@@ -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)
+3 -3
View File
@@ -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);
}
}
}
+6 -6
View File
@@ -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);
+2 -2
View File
@@ -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);
}
}
}
+2 -2
View File
@@ -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;
+2 -2
View File
@@ -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);
}
}
+14 -6
View File
@@ -15,7 +15,15 @@ namespace QuestPDF.Elements
{
public Element DecorationElement { get; set; } = Empty.Instance;
public Element ContentElement { get; set; } = Empty.Instance;
public DecorationType Type { get; set; }
public DecorationType Type { get; set; }
internal override void HandleVisitor(Action<Element?> visit)
{
DecorationElement?.HandleVisitor(visit);
ContentElement?.HandleVisitor(visit);
base.HandleVisitor(visit);
}
internal override ISpacePlan Measure(Size availableSpace)
{
@@ -42,22 +50,22 @@ namespace QuestPDF.Elements
throw new NotSupportedException();
}
internal override void Draw(ICanvas canvas, Size availableSpace)
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(canvas, new Size(availableSpace.Width, decorationSize.Height));
Action drawContent = () => ContentElement?.Draw(canvas, new Size (availableSpace.Width, 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));
Canvas.Translate(new Position(0, translateHeight));
second();
canvas.Translate(new Position(0, -translateHeight));
Canvas.Translate(new Position(0, -translateHeight));
}
}
+3 -2
View File
@@ -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);
}
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ namespace QuestPDF.Elements
return new FullRender(Size.Zero);
}
internal override void Draw(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
}
+3 -3
View File
@@ -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);
}
}
}
+1 -1
View File
@@ -30,7 +30,7 @@ namespace QuestPDF.Elements
container.Stack(stack =>
{
stack.Spacing(HorizontalSpacing);
stack.Spacing(VerticalSpacing);
while (ChildrenQueue.Any())
stack.Item().Row(BuildRow);
+2 -2
View File
@@ -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);
}
}
}
+3 -3
View File
@@ -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);
}
}
}
+17 -4
View File
@@ -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);
}
}
}
+10 -3
View File
@@ -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));
}
}
}
+4 -4
View File
@@ -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)
+28 -6
View File
@@ -1,22 +1,44 @@
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class Page : IComponent
{
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(decoration =>
{
decoration.Header().Element(Header);
decoration.Content().Extend().Element(Content);
decoration.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);
});
}
}
}
+11 -5
View File
@@ -1,21 +1,27 @@
using QuestPDF.Drawing.SpacePlan;
using System;
using QuestPDF.Drawing.SpacePlan;
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;
}
+30 -17
View File
@@ -1,3 +1,5 @@
using System;
using System.Text.RegularExpressions;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
using Size = QuestPDF.Infrastructure.Size;
@@ -7,33 +9,44 @@ 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;
private Text TextElement { get; set; } = new Text();
public TextStyle? TextStyle
{
get => TextElement?.Style;
set => TextElement.Style = value;
}
internal override void HandleVisitor(Action<Element?> visit)
{
TextElement?.HandleVisitor(visit);
base.HandleVisitor(visit);
}
internal override ISpacePlan Measure(Size availableSpace)
{
InitializeTextElement();
TextElement.Value = TextFormat.Replace("{number}", Number.ToString());
TextElement.Value = GetText();
return TextElement.Measure(availableSpace);
}
internal override void Draw(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
InitializeTextElement();
TextElement.Draw(canvas, availableSpace);
Number++;
TextElement.Value = GetText();
TextElement.Draw(availableSpace);
}
private void InitializeTextElement()
private string GetText()
{
TextElement ??= new Text()
{
Style = TextStyle
};
var result = TextFormat;
// replace known locations
foreach (var location in PageContext.GetRegisteredLocations())
result = result.Replace($"{{pdf:{location}}}", PageContext.GetLocationPage(location).ToString());
// placeholder unknown locations
result = Regex.Replace(result, @"{pdf:[ \w]+}", "123");
return result;
}
}
}
+23
View File
@@ -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);
}
}
}
+28 -11
View File
@@ -11,9 +11,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);
}
}
@@ -38,6 +38,14 @@ namespace QuestPDF.Elements
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 leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height)) as Size;
@@ -61,16 +69,16 @@ namespace QuestPDF.Elements
return new FullRender(targetSize);
}
internal override void Draw(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height));
var leftWidth = (leftMeasurement as Size)?.Width ?? 0;
Left.Draw(canvas, new Size(leftWidth, availableSpace.Height));
Left.Draw(new Size(leftWidth, availableSpace.Height));
canvas.Translate(new Position(leftWidth, 0));
Right.Draw(canvas, new Size(availableSpace.Width - leftWidth, availableSpace.Height));
canvas.Translate(new Position(-leftWidth, 0));
Canvas.Translate(new Position(leftWidth, 0));
Right.Draw(new Size(availableSpace.Width - leftWidth, availableSpace.Height));
Canvas.Translate(new Position(-leftWidth, 0));
}
}
@@ -78,15 +86,21 @@ namespace QuestPDF.Elements
{
public ICollection<RowElement> Children { get; internal set; } = new List<RowElement>();
public float Spacing { get; set; } = 0;
internal override void HandleVisitor(Action<Element?> visit)
{
Children.ToList().ForEach(x => x.HandleVisitor(visit));
base.HandleVisitor(visit);
}
internal override ISpacePlan Measure(Size availableSpace)
{
return Compose(availableSpace.Width).Measure(availableSpace);
}
internal override void Draw(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
Compose(availableSpace.Width).Draw(canvas, availableSpace);
Compose(availableSpace.Width).Draw(availableSpace);
}
#region structure
@@ -95,7 +109,10 @@ namespace QuestPDF.Elements
{
var elements = AddSpacing(Children, Spacing);
var rowElements = ReduceRows(elements, availableWidth);
return BuildTree(rowElements.ToArray());
var tree = BuildTree(rowElements.ToArray());
tree.HandleVisitor(x => x.Initialize(PageContext, Canvas));
return tree;
}
private static ICollection<Element> ReduceRows(ICollection<RowElement> elements, float availableWidth)
+60
View File
@@ -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);
}
}
}
+9 -4
View File
@@ -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);
}
}
}
+59
View File
@@ -0,0 +1,59 @@
using System;
using System.Linq;
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);
}
}
}
+20 -6
View File
@@ -10,13 +10,26 @@ using IContainer = QuestPDF.Infrastructure.IContainer;
namespace QuestPDF.Elements
{
internal class SimpleStack : Element
internal class SimpleStack : Element, IStateResettable
{
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)
{
First?.HandleVisitor(visit);
Second?.HandleVisitor(visit);
base.HandleVisitor(visit);
}
public void ResetState()
{
IsFirstRendered = false;
}
internal override ISpacePlan Measure(Size availableSpace)
{
var firstElement = IsFirstRendered ? Empty.Instance : First;
@@ -44,7 +57,7 @@ namespace QuestPDF.Elements
return new FullRender(targetSize);
}
internal override void Draw(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
var firstElement = IsFirstRendered ? Empty.Instance : First;
@@ -56,7 +69,7 @@ namespace QuestPDF.Elements
var firstSize = firstMeasurement as Size;
if (firstSize != null)
firstElement.Draw(canvas, new Size(availableSpace.Width, firstSize.Height));
firstElement.Draw(new Size(availableSpace.Width, firstSize.Height));
if (firstMeasurement is Wrap || firstMeasurement is PartialRender)
return;
@@ -68,9 +81,9 @@ namespace QuestPDF.Elements
if (secondMeasurement == null)
return;
canvas.Translate(new Position(0, firstHeight));
Second.Draw(canvas, new Size(availableSpace.Width, secondMeasurement.Height));
canvas.Translate(new Position(0, -firstHeight));
Canvas.Translate(new Position(0, firstHeight));
Second.Draw(new Size(availableSpace.Width, secondMeasurement.Height));
Canvas.Translate(new Position(0, -firstHeight));
if (secondMeasurement is FullRender)
IsFirstRendered = false;
@@ -97,6 +110,7 @@ namespace QuestPDF.Elements
return elements;
return elements
.Where(x => !(x is Empty))
.Select(x => new Padding
{
Bottom = spacing,
+4 -4
View File
@@ -32,22 +32,22 @@ namespace QuestPDF.Elements
return new FullRender(realWidth, realHeight);
}
internal override void Draw(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
var lines = BreakLines(availableSpace.Width);
var offsetTop = 0f;
var offsetLeft = GetLeftOffset();
canvas.Translate(new Position(0, Style.Size));
Canvas.Translate(new Position(0, Style.Size));
foreach (var line in lines)
{
canvas.DrawText(line, new Position(offsetLeft, offsetTop), Style);
Canvas.DrawText(line, new Position(offsetLeft, offsetTop), Style);
offsetTop += LineHeight;
}
canvas.Translate(new Position(0, -Style.Size));
Canvas.Translate(new Position(0, -Style.Size));
float GetLeftOffset()
{
+23
View File
@@ -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);
}
}
}
+32
View File
@@ -0,0 +1,32 @@
using System;
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);
}
}
}
+1 -1
View File
@@ -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()
+12 -7
View File
@@ -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,15 +33,15 @@ 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);
return handler(parent.Container()).Container();
}
public static void PageNumber(this IContainer element, string textFormat = "{number}", TextStyle? style = null)
public static void PageNumber(this IContainer element, string textFormat = "{pdf:currentPage} / {pdf:totalPages}", TextStyle? style = null)
{
element.Element(new PageNumber
{
@@ -104,7 +104,7 @@ namespace QuestPDF.Fluent
style.Alignment = alignment.Horizontal;
}
element.Element(new Text()
element.Element(new Text
{
Value = text.ToString(),
Style = style
@@ -152,7 +152,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 +162,10 @@ namespace QuestPDF.Fluent
{
return element.Element(new Box());
}
public static IContainer Unconstrained(this IContainer element)
{
return element.Element(new Unconstrained());
}
}
}
}
+4
View File
@@ -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++;
}
+57 -2
View File
@@ -1,5 +1,7 @@
using System;
using QuestPDF.Drawing;
using QuestPDF.Elements;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Fluent
@@ -8,6 +10,56 @@ 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();
@@ -32,11 +84,14 @@ namespace QuestPDF.Fluent
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;
}
}
}
+36
View File
@@ -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
});
}
}
}
+47
View File
@@ -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();
}
}
}
+27
View File
@@ -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);
}
}
}
+36 -36
View File
@@ -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
+10 -3
View File
@@ -1,4 +1,5 @@
using QuestPDF.Drawing.SpacePlan;
using System;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements;
namespace QuestPDF.Infrastructure
@@ -13,14 +14,20 @@ namespace QuestPDF.Infrastructure
set => Child = value as Element;
}
internal override void HandleVisitor(Action<Element?> visit)
{
base.HandleVisitor(visit);
Child?.HandleVisitor(visit);
}
internal override ISpacePlan Measure(Size availableSpace)
{
return Child?.Measure(availableSpace) ?? new FullRender(Size.Zero);
}
internal override void Draw(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
Child?.Draw(canvas, availableSpace);
Child?.Draw(availableSpace);
}
}
}
+17 -1
View File
@@ -1,10 +1,26 @@
using System;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements;
namespace QuestPDF.Infrastructure
{
internal abstract class Element : IElement
{
internal IPageContext PageContext { get; set; }
internal ICanvas Canvas { get; set; }
internal virtual void HandleVisitor(Action<Element?> visit)
{
visit(this);
}
internal void Initialize(IPageContext pageContext, ICanvas canvas)
{
PageContext = pageContext;
Canvas = canvas;
}
internal abstract ISpacePlan Measure(Size availableSpace);
internal abstract void Draw(ICanvas canvas, Size availableSpace);
internal abstract void Draw(Size availableSpace);
}
}
+1
View File
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using SkiaSharp;
namespace QuestPDF.Infrastructure
+1 -1
View File
@@ -5,6 +5,6 @@ namespace QuestPDF.Infrastructure
public interface IDocument
{
DocumentMetadata GetMetadata();
void Compose(IContainer container);
void Compose(IDocumentContainer container);
}
}
@@ -0,0 +1,7 @@
namespace QuestPDF.Infrastructure
{
public interface IDocumentContainer
{
}
}
+11
View File
@@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace QuestPDF.Infrastructure
{
public interface IPageContext
{
void SetLocationPage(string key);
int GetLocationPage(string key);
ICollection<string> GetRegisteredLocations();
}
}
@@ -0,0 +1,11 @@
namespace QuestPDF.Infrastructure
{
public interface IRenderingCanvas
{
void BeginDocument();
void EndDocument();
void BeginPage(Size size);
void EndPage();
}
}
@@ -0,0 +1,7 @@
namespace QuestPDF.Infrastructure
{
internal interface IStateResettable
{
void ResetState();
}
}
+45
View File
@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using QuestPDF.Elements;
namespace QuestPDF.Infrastructure
{
public class PageContext : IPageContext
{
public const string CurrentPageSlot = "currentPage";
public const string TotalPagesSlot = "totalPages";
private Dictionary<string, int> Locations { get; } = new Dictionary<string, int>();
private int PageNumber { get; set; }
internal void SetPageNumber(int number)
{
PageNumber = number;
Locations[CurrentPageSlot] = number;
if (!Locations.ContainsKey(TotalPagesSlot) || Locations[TotalPagesSlot] < number)
Locations[TotalPagesSlot] = number;
}
public void SetLocationPage(string key)
{
if (Locations.ContainsKey(key))
return;
Locations[key] = PageNumber;
}
public int GetLocationPage(string key)
{
if (!Locations.ContainsKey(key))
throw new ArgumentException($"The location '{key}' does not exists.");
return Locations[key];
}
public ICollection<string> GetRegisteredLocations()
{
return Locations.Keys;
}
}
}
+1
View File
@@ -8,6 +8,7 @@
public float Height { get; }
public static Size Zero => new Size(0, 0);
public static Size Max => new Size(14_400, 14_400);
public Size(float width, float height)
{
+2 -2
View File
@@ -4,9 +4,9 @@
<Authors>MarcinZiabek</Authors>
<Company>CodeFlint</Company>
<PackageId>QuestPDF</PackageId>
<Version>2021.5-alpha2</Version>
<Version>2021.8</Version>
<PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
<PackageReleaseNotes>New elements: Grid, Canvas, EnsureSpace, Layers. Predefined colors and fonts.</PackageReleaseNotes>
<PackageReleaseNotes>New elements: Unconstrained, Scale, ScaleHorizontal, ScaleVertical, FlipOver, FlipHorizontal, FlipVertical, Rotate, RotateLeft, RotateRight, TranslateX, TranslateY. Added support for more placeholders in the PageNumber element (current page, total number of pages, page number of any predefined location). Added support for documents with various page sizes. Various API improvements.</PackageReleaseNotes>
<LangVersion>8</LangVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageIcon>Logo.png</PackageIcon>
+14
View File
@@ -0,0 +1,14 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 2021.5.2 | :white_check_mark: |
| Older | :x: |
## Reporting a Vulnerability
Please report any code vulnerabilities using GitHub Issues. We are trying to respond within 1 day and analyze reported situation as soon as possible.
Please collaborate with us to find best solutation available.
Once the vulnerability is recognized and fixed, the issue is closed with appropraite message.
+23 -12
View File
@@ -16,6 +16,15 @@
6) **Enjoy fast PDF generation** - QuestPDF is created upon SkiaSharp, a well-known graphical library, and converts your data into PDF documents. It offers a highly optimized layouting engine capable of generating over 1000 PDF files per minute per core. The entire process is thread-safe.
## Support QuestPDF
All great frameworks and libraries started from zero. Please help us to make QuestPDF a commonly known library and an obvious choice in case of generating PDF documents. It can be as easy as:
- Giving this repository a star ⭐ so more people will know about it,
- Observing 🤩 the library to know about each new realease,
- Trying our sample project to see how easy it is to create an invoice 📊,
- Sharing your thoughts 💬 with us and your colleagues,
- Simply using it 👨‍💻 and suggesting new features,
- Creating new features 🆕 for everybody.
## Installation
@@ -25,7 +34,6 @@ The library is available as a nuget package. You can install it as any other nug
<img src="https://github.com/QuestPDF/example-invoice/raw/main/images/nuget.svg" width="200px">
</a>
## Documentation
1. [Release notes and roadmap](https://www.questpdf.com/documentation/releases.html) - everything that is planned for future library iterations, description of new features and information about potential breaking changes.
@@ -46,16 +54,19 @@ For tutorial, documentation and API reference, please visit [the QuestPDF docume
Here you can find an example code showing how easy is to write and understand the fluent API:
```csharp
public void Compose(IContainer container)
{
public void Compose(IDocumentContainer container)
{
container
.PaddingHorizontal(50)
.PaddingVertical(50)
.Page(page =>
{
page.Header(ComposeHeader);
page.Content(ComposeContent);
page.Footer().AlignCenter().PageNumber("Page {number}");
page.MarginVertical(60);
page.MarginHorizontal(40);
page.Size(PageSizes.A4);
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().PageNumber();
});
}
@@ -63,11 +74,11 @@ void ComposeHeader(IContainer container)
{
container.Row(row =>
{
row.RelativeColumn().Stack(column =>
row.RelativeColumn().Stack(stack =>
{
column.Element().Text($"Invoice #{Model.InvoiceNumber}", TextStyle.Default.Size(20).Bold());
column.Element().Text($"Issue date: {Model.IssueDate:d}");
column.Element().Text($"Due date: {Model.DueDate:d}");
stack.Item().Text($"Invoice #{Model.InvoiceNumber}", TextStyle.Default.Size(20).Bold());
stack.Item().Text($"Issue date: {Model.IssueDate:d}");
stack.Item().Text($"Due date: {Model.DueDate:d}");
});
row.ConstantColumn(100).Height(50).Placeholder();