Compare commits

...

40 Commits

Author SHA1 Message Date
Marcin Ziąbek 2772b5b4a4 Size component cleanup 2021-09-12 20:25:50 +02:00
Marcin Ziąbek 3dfe7a59e0 Fixed space removal 2021-09-12 20:15:41 +02:00
Marcin Ziąbek df5c5524ee Fixed text alignment 2021-09-12 20:10:52 +02:00
Marcin Ziąbek 5975b19445 Improved book layout in benchmark 2021-09-07 13:34:04 +02:00
Marcin Ziąbek fb7743e828 Improved text rendering (space corner cases) 2021-09-07 13:16:34 +02:00
Marcin Ziąbek 3bb46ecc42 Fixed text rendering (spacing corner case) 2021-08-29 19:18:58 +02:00
Marcin Ziąbek 3112ee10cf Better new line handling 2021-08-27 20:25:20 +02:00
Marcin Ziąbek 899c138842 Code structure improvements. Text rendering: support for words longer than available space 2021-08-27 19:51:28 +02:00
Marcin Ziąbek 5177dcfd2b Code structure improvements 2021-08-27 12:40:06 +02:00
Marcin Ziąbek 4e6415aee2 Example report fixes 2021-08-27 11:40:33 +02:00
Marcin Ziąbek d9b3f016e8 Text: rendering custom elements inside text 2021-08-27 01:55:20 +02:00
Marcin Ziąbek b9cfaf3846 Text component: internal and external links 2021-08-27 01:38:26 +02:00
Marcin Ziąbek e3e7a22af8 Fix: the Stack may not show last an element in certain scenarios 2021-08-27 01:38:10 +02:00
Marcin Ziąbek c938ea32b0 Code cleanup 2021-08-27 01:10:30 +02:00
Marcin Ziąbek 69419c8440 Text Rendering Optimization: Caching Measurement Results 2021-08-27 00:29:04 +02:00
Marcin Ziąbek 23e8aa3491 Text rendering optimization: style key, font metrics caching 2021-08-26 23:36:43 +02:00
Marcin Ziąbek fd5d51e764 Implemented text rendering benchmark 2021-08-26 22:25:33 +02:00
Marcin Ziąbek dbc479db07 Improved text rendering capabilities 2021-08-25 03:40:16 +02:00
Marcin Ziąbek edc595d199 Initial text rendering improvements 2021-08-09 22:35:39 +02:00
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
106 changed files with 12317 additions and 1119 deletions
+32
View File
@@ -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));
});
});
}
}
}
+760 -205
View File
@@ -1,226 +1,781 @@
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine; using QuestPDF.Examples.Engine;
using QuestPDF.Fluent; using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using SkiaSharp; using SkiaSharp;
namespace QuestPDF.Examples namespace QuestPDF.Examples
{ {
public class ElementExamples : ExampleTestBase [TestFixture]
public class ElementExamples
{ {
//[ShowResult] [Test]
[ImageSize(200, 150)] public void Placeholder()
public void Placeholder(IContainer container)
{ {
container RenderingTest
.Background("#FFF") .Create()
.Padding(25) .PageSize(200, 150)
.Placeholder(); .FileName()
} .Render(container =>
//[ShowResult]
[ImageSize(300, 300)]
public void Decoration(IContainer container)
{
container
.Background("#FFF")
.Padding(25)
.Decoration(decoration =>
{ {
decoration container
.Header() .Background("#FFF")
.Background("#888") .Padding(25)
.Padding(10) .Placeholder();
.Text("Notes", TextStyle.Default.Size(16).Color("#FFF"));
decoration
.Content()
.Background("#DDD")
.Padding(10)
.ExtendVertical()
.Text(Helpers.Placeholders.LoremIpsum());
}); });
} }
//[ShowResult] [Test]
[ImageSize(298, 421)] public void Decoration()
public void Page(IContainer container)
{ {
container RenderingTest
.Background("#FFF") .Create()
.Padding(15) .PageSize(300, 300)
.Page(page => .FileName()
.Render(container =>
{ {
page.Header() container
.Height(60) .Background("#FFF")
.Background("#BBB") .Padding(25)
.AlignCenter() .Decoration(decoration =>
.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
{ {
Color = SKColors.Red, decoration
StrokeWidth = 5 .Header()
}; .Background(Colors.Grey.Medium)
.Padding(10)
.Text("Notes", TextStyle.Default.Size(16).Color("#FFF"));
canvas.Translate(size.Width / 2, size.Height / 2); decoration
canvas.DrawCircle(0, 0, 50, paint); .Content()
}); .Background(Colors.Grey.Lighten3)
.Padding(10)
.ExtendVertical()
.Text(Helpers.Placeholders.LoremIpsum());
});
});
}
layers.Layer().Background("#8F00").Extend(); [Test]
layers.Layer().PaddingTop(40).Text("It works!", TextStyle.Default.Size(24)); 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;
}
}
}
+95
View File
@@ -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
{
}
}
+14 -5
View File
@@ -1,4 +1,5 @@
using QuestPDF.Drawing; using QuestPDF.Drawing;
using QuestPDF.Elements;
using QuestPDF.Fluent; using QuestPDF.Fluent;
using QuestPDF.Helpers; using QuestPDF.Helpers;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
@@ -7,27 +8,35 @@ namespace QuestPDF.Examples.Engine
{ {
public class SimpleDocument : IDocument public class SimpleDocument : IDocument
{ {
public const int ImageScalingFactor = 2;
private IContainer Container { get; } private IContainer Container { get; }
private Size Size { 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; Container = container;
Size = size; Size = size;
MaxPages = maxPages;
} }
public DocumentMetadata GetMetadata() public DocumentMetadata GetMetadata()
{ {
return new DocumentMetadata() return new DocumentMetadata()
{ {
RasterDpi = PageSizes.PointsPerInch * 2, RasterDpi = PageSizes.PointsPerInch * ImageScalingFactor,
Size = Size 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);
});
} }
} }
} }
+20 -13
View File
@@ -1,3 +1,4 @@
using NUnit.Framework;
using QuestPDF.Examples.Engine; using QuestPDF.Examples.Engine;
using QuestPDF.Fluent; using QuestPDF.Fluent;
using QuestPDF.Helpers; using QuestPDF.Helpers;
@@ -19,25 +20,31 @@ namespace QuestPDF.Examples
public static IContainer ValueCell(this IContainer container) => container.Cell(false); public static IContainer ValueCell(this IContainer container) => container.Cell(false);
} }
public class FrameExample: ExampleTestBase public class FrameExample
{ {
[ImageSize(550, 400)] [Test]
[ShowResult]
public void Frame(IContainer container) public void Frame(IContainer container)
{ {
container RenderingTest
.Background("#FFF") .Create()
.Padding(25) .PageSize(550, 400)
.Stack(stack => .FileName()
.Render(container =>
{ {
for(var i=1; i<=4; i++) container
{ .Background("#FFF")
stack.Item().Row(row => .Padding(25)
.Stack(stack =>
{ {
row.RelativeColumn(2).LabelCell().Text(Placeholders.Label()); for(var i=1; i<=4; i++)
row.RelativeColumn(3).ValueCell().Text(Placeholders.Paragraph()); {
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 System.Net;
using NUnit.Framework;
using QuestPDF.Examples.Engine; using QuestPDF.Examples.Engine;
using QuestPDF.Fluent; using QuestPDF.Fluent;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
@@ -27,27 +28,33 @@ namespace QuestPDF.Examples
} }
} }
public class LoremPicsumExample : ExampleTestBase public class LoremPicsumExample
{ {
[ShowResult] [Test]
[ImageSize(350, 280)] public void LoremPicsum()
public void LoremPicsum(IContainer container)
{ {
container RenderingTest
.Background("#FFF") .Create()
.Padding(25) .PageSize(350, 280)
.Stack(column => .FileName()
.Render(container =>
{ {
column.Spacing(10); container
.Background("#FFF")
.Padding(25)
.Stack(column =>
{
column.Spacing(10);
column column
.Item() .Item()
.Component(new LoremPicsum(true)); .Component(new LoremPicsum(true));
column column
.Item() .Item()
.AlignRight() .AlignRight()
.Text("From Lorem Picsum"); .Text("From Lorem Picsum");
});
}); });
} }
} }
+137 -103
View File
@@ -1,132 +1,166 @@
using QuestPDF.Examples.Engine; using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent; using QuestPDF.Fluent;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
namespace QuestPDF.Examples namespace QuestPDF.Examples
{ {
public class Examples : ExampleTestBase public class Examples
{ {
public void Padding(IContainer container) [Test]
public void Padding()
{ {
container RenderingTest
.Background("#FDD") .Create()
.Padding(50) .PageSize(200, 150)
.FileName()
.Background("#AFA") .Render(container =>
.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 =>
{ {
column container
.Item() .Background("#FDD")
.Height(100) .Padding(50)
.Background("#FFF")
.AlignLeft() .Background("#AFA")
.AlignMiddle() .PaddingVertical(50)
.Background("#77F")
.PaddingHorizontal(50)
.Width(50)
.Height(50)
.Background("#444"); .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 RenderingTest
.Stack(column => .Create()
.PageSize(200, 150)
.FileName()
.Render(container =>
{ {
column container
.Item() .Background("#EEE")
.Height(150) .Padding(25)
.Row(row =>
.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() column
.Extend() .Item()
.Background("FFF") .Height(100)
.Background("#FFF")
.AlignLeft()
.AlignMiddle()
.Height(50)
.Width(50) .Width(50)
.Background("444");
row.RelativeColumn()
.Extend()
.Background("BBB")
.Height(50) .Height(50)
.ExtendHorizontal() .Background("#444");
.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");
}); });
});
}
column [Test]
.Item() public void Expand()
.Height(150) {
.Row(row => RenderingTest
.Create()
.PageSize(200, 150)
.FileName()
.Render(container =>
{
container
.Stack(column =>
{ {
row.RelativeColumn() column
.Extend() .Item()
.Background("BBB") .Height(150)
.Row(row =>
{
row.RelativeColumn()
.Extend()
.Background("FFF")
.ExtendVertical() .Height(50)
.Width(50) .Width(50)
.Background("444"); .Background("444");
row.RelativeColumn() row.RelativeColumn()
.Extend() .Extend()
.Background("BBB") .Background("BBB")
.ExtendVertical() .Height(50)
.ExtendHorizontal() .ExtendHorizontal()
.Background("444"); .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" /> <ProjectReference Include="..\QuestPDF\QuestPDF.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Update="quo-vadis.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project> </Project>
+228
View File
@@ -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();
});
}
}
}
}
+122
View File
@@ -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
+6 -7
View File
@@ -18,8 +18,8 @@ namespace QuestPDF.ReportSample
HeaderFields = HeaderFields(), HeaderFields = HeaderFields(),
LogoData = Helpers.GetImage("Logo.png"), LogoData = Helpers.GetImage("Logo.png"),
Sections = Enumerable.Range(0, 8).Select(x => GenerateSection()).ToList(), Sections = Enumerable.Range(0, 40).Select(x => GenerateSection()).ToList(),
Photos = Enumerable.Range(2, 6).Select(x => GetReportPhotos()).ToList() Photos = Enumerable.Range(0, 25).Select(x => GetReportPhotos()).ToList()
}; };
List<ReportHeaderField> HeaderFields() List<ReportHeaderField> HeaderFields()
@@ -52,8 +52,8 @@ namespace QuestPDF.ReportSample
ReportSection GenerateSection() ReportSection GenerateSection()
{ {
var sectionLength = Helpers.Random.NextDouble() > 0.75 var sectionLength = Helpers.Random.NextDouble() > 0.75
? Helpers.Random.Next(10, 20) ? Helpers.Random.Next(20, 40)
: Helpers.Random.Next(3, 11); : Helpers.Random.Next(5, 10);
return new ReportSection return new ReportSection
{ {
@@ -100,8 +100,7 @@ namespace QuestPDF.ReportSample
{ {
Label = "Photos", Label = "Photos",
Photos = Enumerable Photos = Enumerable
.Range(0, Helpers.Random.Next(1, 10)) .Range(0, Helpers.Random.Next(1, 15))
.Select(x => Helpers.Random.Next(0, 128))
.Select(x => Placeholders.Image(400, 300)) .Select(x => Placeholders.Image(400, 300))
.ToList() .ToList()
}; };
@@ -111,7 +110,7 @@ namespace QuestPDF.ReportSample
{ {
return new ReportPhoto() return new ReportPhoto()
{ {
PhotoData = Placeholders.Image(400, 300), PhotoData = Placeholders.Image(800, 600),
Comments = Placeholders.Sentence(), Comments = Placeholders.Sentence(),
Date = DateTime.Now - TimeSpan.FromDays(Helpers.Random.NextDouble() * 100), Date = DateTime.Now - TimeSpan.FromDays(Helpers.Random.NextDouble() * 100),
@@ -28,13 +28,13 @@ namespace QuestPDF.ReportSample.Layouts
{ {
foreach (var part in Model.Parts) 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); row.ConstantColumn(150).LabelCell().Text(part.Label, Typography.Normal);
var frame = row.RelativeColumn().ValueCell(); var frame = row.RelativeColumn().ValueCell();
if (part is ReportSectionText text) if (part is ReportSectionText text)
frame.Text(text.Text, Typography.Normal); frame.ShowEntire().Text(text.Text, Typography.Normal);
if (part is ReportSectionMap map) if (part is ReportSectionMap map)
frame.Element(x => MapElement(x, map)); frame.Element(x => MapElement(x, map));
@@ -72,7 +72,7 @@ namespace QuestPDF.ReportSample.Layouts
return; return;
} }
container.Debug("Photo gallery").Grid(grid => container.Debug("Photos").Grid(grid =>
{ {
grid.Spacing(5); grid.Spacing(5);
grid.Columns(3); grid.Columns(3);
@@ -18,21 +18,31 @@ namespace QuestPDF.ReportSample.Layouts
{ {
return new DocumentMetadata() return new DocumentMetadata()
{ {
Title = Model.Title, Title = Model.Title
Size = PageSizes.A4
}; };
} }
public void Compose(IContainer container) public void Compose(IDocumentContainer container)
{ {
container container
.PaddingVertical(40)
.PaddingHorizontal(50)
.Page(page => .Page(page =>
{ {
page.MarginVertical(40);
page.MarginHorizontal(50);
page.Size(PageSizes.A4);
page.Header().Element(ComposeHeader); page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent); page.Content().Element(ComposeContent);
page.Footer().AlignCenter().PageNumber("Page {number}");
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) foreach (var field in Model.HeaderFields)
{ {
grid.Item().Stack(row => grid.Item().Text(text =>
{ {
row.Item().AlignLeft().Text(field.Label, Typography.Normal.SemiBold()); text.Span($"{field.Label}: ", Typography.Normal.SemiBold());
row.Item().Text(field.Value, Typography.Normal); text.Span(field.Value, Typography.Normal);
}); });
} }
}); });
@@ -75,6 +85,8 @@ namespace QuestPDF.ReportSample.Layouts
stack.Item().Component(new TableOfContentsTemplate(Model.Sections)); stack.Item().Component(new TableOfContentsTemplate(Model.Sections));
stack.Item().PageBreak();
foreach (var section in Model.Sections) foreach (var section in Model.Sections)
stack.Item().Location(section.Title).Component(new SectionTemplate(section)); stack.Item().Location(section.Title).Component(new SectionTemplate(section));
@@ -43,6 +43,7 @@ namespace QuestPDF.ReportSample.Layouts
{ {
row.ConstantColumn(25).Text($"{number}.", Typography.Normal); row.ConstantColumn(25).Text($"{number}.", Typography.Normal);
row.RelativeColumn().Text(locationName, Typography.Normal); row.RelativeColumn().Text(locationName, Typography.Normal);
row.ConstantColumn(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName, Typography.Normal));
}); });
} }
} }
+6 -4
View File
@@ -25,9 +25,11 @@ namespace QuestPDF.ReportSample
[Test] [Test]
public void PerformanceBenchmark() public void PerformanceBenchmark()
{ {
// target document length should be around 100 pages
// test size // test size
const int testSize = 100; const int testSize = 10;
const decimal performanceTarget = 5; // documents per second const decimal performanceTarget = 1; // documents per second
// create report models // create report models
var reports = Enumerable var reports = Enumerable
@@ -55,8 +57,8 @@ namespace QuestPDF.ReportSample
Console.WriteLine($"Time per document: {performance:N} ms"); Console.WriteLine($"Time per document: {performance:N} ms");
Console.WriteLine($"Documents per second: {speed:N} d/s"); Console.WriteLine($"Documents per second: {speed:N} d/s");
if (speed < performanceTarget) //if (speed < performanceTarget)
throw new Exception("Rendering algorithm is too slow."); // throw new Exception("Rendering algorithm is too slow.");
} }
} }
} }
+1 -1
View File
@@ -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 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 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);
} }
} }
+104 -34
View File
@@ -6,7 +6,7 @@ using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests namespace QuestPDF.UnitTests
{ {
/*[TestFixture] [TestFixture]
public class GridTests public class GridTests
{ {
#region Alignment #region Alignment
@@ -39,30 +39,30 @@ namespace QuestPDF.UnitTests
// assert // assert
var expected = new Container(); var expected = new Container();
expected.Stack(stack => expected.Container().Stack(stack =>
{ {
stack.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeColumn(6).Element(childA); row.RelativeColumn(6).Container().Element(childA);
row.RelativeColumn(4).Element(childB); row.RelativeColumn(4).Container().Element(childB);
row.RelativeColumn(2).Element(Empty.Instance); row.RelativeColumn(2);
}); });
stack.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeColumn(4).Element(childC); row.RelativeColumn(4).Container().Element(childC);
row.RelativeColumn(2).Element(childD); row.RelativeColumn(2).Container().Element(childD);
row.RelativeColumn(6).Element(Empty.Instance); row.RelativeColumn(6);
}); });
stack.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeColumn(8).Element(childE); row.RelativeColumn(8).Container().Element(childE);
row.RelativeColumn(4).Element(Empty.Instance); row.RelativeColumn(4);
}); });
}); });
structure.Child.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering().IncludingAllRuntimeProperties()); structure.Should().BeEquivalentTo(expected, o => o.WithTracing().WithAutoConversion().WithStrictOrdering().IncludingAllRuntimeProperties());
} }
[Test] [Test]
@@ -93,33 +93,33 @@ namespace QuestPDF.UnitTests
// assert // assert
var expected = new Container(); var expected = new Container();
expected.Stack(stack => expected.Container().Stack(stack =>
{ {
stack.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeColumn(1).Element(Empty.Instance); row.RelativeColumn(1);
row.RelativeColumn(6).Element(childA); row.RelativeColumn(6).Container().Element(childA);
row.RelativeColumn(4).Element(childB); row.RelativeColumn(4).Container().Element(childB);
row.RelativeColumn(1).Element(Empty.Instance); row.RelativeColumn(1);
}); });
stack.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeColumn(3).Element(Empty.Instance); row.RelativeColumn(3);
row.RelativeColumn(4).Element(childC); row.RelativeColumn(4).Container().Element(childC);
row.RelativeColumn(2).Element(childD); row.RelativeColumn(2).Container().Element(childD);
row.RelativeColumn(3).Element(Empty.Instance); row.RelativeColumn(3);
}); });
stack.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeColumn(2).Element(Empty.Instance); row.RelativeColumn(2);
row.RelativeColumn(8).Element(childE); row.RelativeColumn(8).Container().Element(childE);
row.RelativeColumn(2).Element(Empty.Instance); row.RelativeColumn(2);
}); });
}); });
structure.Child.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering().IncludingAllRuntimeProperties()); structure.Should().BeEquivalentTo(expected, o => o.WithTracing().WithAutoConversion().WithStrictOrdering().IncludingAllRuntimeProperties());
} }
[Test] [Test]
@@ -154,28 +154,98 @@ namespace QuestPDF.UnitTests
{ {
stack.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeColumn(2).Element(Empty.Instance); row.RelativeColumn(2);
row.RelativeColumn(6).Element(childA); row.RelativeColumn(6).Container().Element(childA);
row.RelativeColumn(4).Element(childB); row.RelativeColumn(4).Container().Element(childB);
}); });
stack.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeColumn(6).Element(Empty.Instance); row.RelativeColumn(6);
row.RelativeColumn(4).Element(childC); row.RelativeColumn(4).Container().Element(childC);
row.RelativeColumn(2).Element(childD); row.RelativeColumn(2).Container().Element(childD);
}); });
stack.Item().Row(row => stack.Item().Row(row =>
{ {
row.RelativeColumn(4).Element(Empty.Instance); row.RelativeColumn(4);
row.RelativeColumn(8).Element(childE); 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 #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()) .For(x => new PageBreak())
.MeasureElement(new Size(400, 300)) .MeasureElement(new Size(400, 300))
.CheckMeasureResult(new PartialRender(400, 300)) .CheckMeasureResult(new PartialRender(Size.Zero))
.DrawElement(new Size(400, 300)) .DrawElement(new Size(400, 300))
.CheckDrawResult() .CheckDrawResult()
+3 -3
View File
@@ -30,10 +30,10 @@ namespace QuestPDF.UnitTests
Child = child.Object Child = child.Object
}; };
element.Draw(null, Size.Zero); element.Draw(Size.Zero);
element.Draw(null, 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] [Test]
@@ -15,6 +15,7 @@ namespace QuestPDF.UnitTests.TestEngine
public void DrawRectangle(Position vector, Size size, string color) => DrawRectFunc(vector, size, color); 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 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 DrawImage(SKImage image, Position position, Size size) => DrawImageFunc(image, position, size);
public void DrawExternalLink(string url, Size size) public void DrawExternalLink(string url, Size size)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
+1 -1
View File
@@ -11,6 +11,6 @@ namespace QuestPDF.UnitTests.TestEngine
public Action<Size> DrawFunc { get; set; } public Action<Size> DrawFunc { get; set; }
internal override ISpacePlan Measure(Size availableSpace) => MeasureFunc(availableSpace); 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);
} }
} }
+7 -3
View File
@@ -4,6 +4,7 @@ using System.Text.Json;
using NUnit.Framework; using NUnit.Framework;
using QuestPDF.Drawing.SpacePlan; using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.TestEngine.Operations; using QuestPDF.UnitTests.TestEngine.Operations;
@@ -205,6 +206,8 @@ namespace QuestPDF.UnitTests.TestEngine
public TestPlan CheckMeasureResult(ISpacePlan expected) public TestPlan CheckMeasureResult(ISpacePlan expected)
{ {
Element.HandleVisitor(x => x?.Initialize(null, Canvas));
var actual = Element.Measure(OperationInput); var actual = Element.Measure(OperationInput);
Assert.AreEqual(expected.GetType(), actual.GetType()); Assert.AreEqual(expected.GetType(), actual.GetType());
@@ -223,7 +226,8 @@ namespace QuestPDF.UnitTests.TestEngine
public TestPlan CheckDrawResult() public TestPlan CheckDrawResult()
{ {
Element.Draw(Canvas, OperationInput); Element.HandleVisitor(x => x?.Initialize(null, Canvas));
Element.Draw(OperationInput);
return this; return this;
} }
@@ -242,9 +246,9 @@ namespace QuestPDF.UnitTests.TestEngine
public static Element CreateUniqueElement() public static Element CreateUniqueElement()
{ {
return new Text return new DynamicImage
{ {
Value = Guid.NewGuid().ToString("N") Source = Placeholders.Image
}; };
} }
} }
-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);
}
}
}
+5 -19
View File
@@ -1,5 +1,4 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using SkiaSharp; using SkiaSharp;
@@ -7,7 +6,8 @@ namespace QuestPDF.Drawing
{ {
internal static class CanvasCache 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>(); private static ConcurrentDictionary<string, SKPaint> ColorPaint = new ConcurrentDictionary<string, SKPaint>();
internal static SKPaint ColorToPaint(this string color) internal static SKPaint ColorToPaint(this string color)
@@ -36,28 +36,14 @@ namespace QuestPDF.Drawing
Color = SKColor.Parse(style.Color), Color = SKColor.Parse(style.Color),
Typeface = SKTypeface.FromFamilyName(style.FontType, (int)style.FontWeight, (int)SKFontStyleWidth.Normal, slant), Typeface = SKTypeface.FromFamilyName(style.FontType, (int)style.FontWeight, (int)SKFontStyleWidth.Normal, slant),
TextSize = style.Size, TextSize = style.Size,
TextEncoding = SKTextEncoding.Utf32, TextEncoding = SKTextEncoding.Utf32
TextAlign = style.Alignment switch
{
HorizontalAlignment.Left => SKTextAlign.Left,
HorizontalAlignment.Center => SKTextAlign.Center,
HorizontalAlignment.Right => SKTextAlign.Right,
_ => SKTextAlign.Left
}
}; };
} }
} }
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 FontMetrics.GetOrAdd(style.ToString(), key => style.ToPaint().FontMetrics);
return new TextMeasurement()
{
LineIndex = index,
FragmentWidth = width
};
} }
} }
} }
+35
View File
@@ -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;
}
}
}
+48 -87
View File
@@ -3,10 +3,8 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using QuestPDF.Drawing.Exceptions; using QuestPDF.Drawing.Exceptions;
using QuestPDF.Drawing.SpacePlan; using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Fluent; using QuestPDF.Elements;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Drawing namespace QuestPDF.Drawing
{ {
@@ -14,115 +12,78 @@ namespace QuestPDF.Drawing
{ {
internal static void GeneratePdf(Stream stream, IDocument document) internal static void GeneratePdf(Stream stream, IDocument document)
{ {
var content = ElementExtensions.Create(document.Compose);
var metadata = document.GetMetadata(); var metadata = document.GetMetadata();
var canvas = new PdfCanvas(stream, metadata);
RenderDocument(canvas, document);
}
using var pdf = SKDocument.CreatePdf(stream, MapMetadata(metadata)); internal static ICollection<byte[]> GenerateImages(IDocument document)
var totalPages = 1; {
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();
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) 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); if (spacePlan == null)
var canvas = new Canvas(skiaCanvas); break;
try try
{ {
content.Draw(canvas, metadata.Size); canvas.BeginPage(spacePlan);
content.Draw(spacePlan);
} }
catch (Exception exception) catch (Exception exception)
{ {
pdf.Close(); canvas.EndDocument();
throw new DocumentDrawingException("An exception occured during document drawing.", exception); 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."); throw new DocumentLayoutException("Composed layout generates infinite document.");
} }
if (spacePlan is FullRender) if (spacePlan is FullRender)
break; break;
totalPages++; currentPage++;
} }
pdf.Close(); canvas.EndDocument();
}
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
};
} }
} }
} }
-3
View File
@@ -1,12 +1,9 @@
using System; using System;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Drawing namespace QuestPDF.Drawing
{ {
public class DocumentMetadata public class DocumentMetadata
{ {
public Size Size { get; set; } = PageSizes.A4;
public int ImageQuality { get; set; } = 101; public int ImageQuality { get; set; } = 101;
public int RasterDpi { get; set; } = 72; public int RasterDpi { get; set; } = 72;
public bool PdfA { get; set; } 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
};
}
}
}
+55
View File
@@ -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);
}
}
}
+13
View File
@@ -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)
{
}
}
}
+4 -4
View File
@@ -8,7 +8,7 @@ namespace QuestPDF.Elements
public VerticalAlignment Vertical { get; set; } = VerticalAlignment.Top; public VerticalAlignment Vertical { get; set; } = VerticalAlignment.Top;
public HorizontalAlignment Horizontal { get; set; } = HorizontalAlignment.Left; public HorizontalAlignment Horizontal { get; set; } = HorizontalAlignment.Left;
internal override void Draw(ICanvas canvas, Size availableSpace) internal override void Draw(Size availableSpace)
{ {
if (Child == null) if (Child == null)
return; return;
@@ -21,9 +21,9 @@ namespace QuestPDF.Elements
var top = GetTopOffset(availableSpace, childSize); var top = GetTopOffset(availableSpace, childSize);
var left = GetLeftOffset(availableSpace, childSize); var left = GetLeftOffset(availableSpace, childSize);
canvas.Translate(new Position(left, top)); Canvas.Translate(new Position(left, top));
Child.Draw(canvas, childSize); Child.Draw(childSize);
canvas.Translate(new Position(-left, -top)); Canvas.Translate(new Position(-left, -top));
} }
private float GetTopOffset(Size availableSpace, Size childSize) private float GetTopOffset(Size availableSpace, Size childSize)
+2 -2
View File
@@ -36,13 +36,13 @@ namespace QuestPDF.Elements
throw new NotSupportedException(); throw new NotSupportedException();
} }
internal override void Draw(ICanvas canvas, Size availableSpace) internal override void Draw(Size availableSpace)
{ {
if (Child == null) if (Child == null)
return; return;
var size = GetTargetSize(availableSpace); var size = GetTargetSize(availableSpace);
Child?.Draw(canvas, size); Child?.Draw(size);
} }
private Size GetTargetSize(Size availableSpace) private Size GetTargetSize(Size availableSpace)
+3 -3
View File
@@ -7,10 +7,10 @@ namespace QuestPDF.Elements
{ {
public string Color { get; set; } = Colors.Black; 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); Canvas.DrawRectangle(Position.Zero, availableSpace, Color);
Child?.Draw(canvas, availableSpace); Child?.Draw(availableSpace);
} }
} }
} }
+6 -6
View File
@@ -12,26 +12,26 @@ namespace QuestPDF.Elements
public float Bottom { get; set; } public float Bottom { get; set; }
public float Left { 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 Position(-Left/2, -Top/2),
new Size(availableSpace.Width + Left/2 + Right/2, Top), new Size(availableSpace.Width + Left/2 + Right/2, Top),
Color); Color);
canvas.DrawRectangle( Canvas.DrawRectangle(
new Position(-Left/2, -Top/2), new Position(-Left/2, -Top/2),
new Size(Left, availableSpace.Height + Top/2 + Bottom/2), new Size(Left, availableSpace.Height + Top/2 + Bottom/2),
Color); Color);
canvas.DrawRectangle( Canvas.DrawRectangle(
new Position(-Left/2, availableSpace.Height-Bottom/2), new Position(-Left/2, availableSpace.Height-Bottom/2),
new Size(availableSpace.Width + Left/2 + Right/2, Bottom), new Size(availableSpace.Width + Left/2 + Right/2, Bottom),
Color); Color);
canvas.DrawRectangle( Canvas.DrawRectangle(
new Position(availableSpace.Width-Right/2, -Top/2), new Position(availableSpace.Width-Right/2, -Top/2),
new Size(Right, availableSpace.Height + Top/2 + Bottom/2), new Size(Right, availableSpace.Height + Top/2 + Bottom/2),
Color); Color);
+2 -2
View File
@@ -4,14 +4,14 @@ namespace QuestPDF.Elements
{ {
internal class Box : ContainerElement 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; var targetSize = Child?.Measure(availableSpace) as Size;
if (targetSize == null) if (targetSize == null)
return; return;
Child?.Draw(canvas, targetSize); Child?.Draw(targetSize);
} }
} }
} }
+2 -2
View File
@@ -15,9 +15,9 @@ namespace QuestPDF.Elements
return new FullRender(availableSpace); 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) if (Handler == null || skiaCanvas == null)
return; return;
+2 -2
View File
@@ -44,13 +44,13 @@ namespace QuestPDF.Elements
throw new NotSupportedException(); throw new NotSupportedException();
} }
internal override void Draw(ICanvas canvas, Size availableSpace) internal override void Draw(Size availableSpace)
{ {
var available = new Size( var available = new Size(
MathHelpers.Min(MaxWidth, availableSpace.Width), MathHelpers.Min(MaxWidth, availableSpace.Width),
MathHelpers.Min(MaxHeight, availableSpace.Height)); MathHelpers.Min(MaxHeight, availableSpace.Height));
Child?.Draw(canvas, available); Child?.Draw(available);
} }
} }
+13 -5
View File
@@ -17,6 +17,14 @@ namespace QuestPDF.Elements
public Element ContentElement { get; set; } = Empty.Instance; public Element ContentElement { get; set; } = Empty.Instance;
public DecorationType Type { get; set; } public DecorationType Type { get; set; }
internal override void HandleVisitor(Action<Element?> visit)
{
DecorationElement?.HandleVisitor(visit);
ContentElement?.HandleVisitor(visit);
base.HandleVisitor(visit);
}
internal override ISpacePlan Measure(Size availableSpace) internal override ISpacePlan Measure(Size availableSpace)
{ {
var decorationMeasure = DecorationElement?.Measure(availableSpace); var decorationMeasure = DecorationElement?.Measure(availableSpace);
@@ -42,22 +50,22 @@ namespace QuestPDF.Elements
throw new NotSupportedException(); 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 decorationSize = DecorationElement?.Measure(availableSpace) as Size ?? Size.Zero;
var contentSize = new Size(availableSpace.Width, availableSpace.Height - decorationSize.Height); var contentSize = new Size(availableSpace.Width, availableSpace.Height - decorationSize.Height);
var translateHeight = Type == DecorationType.Prepend ? decorationSize.Height : contentSize.Height; var translateHeight = Type == DecorationType.Prepend ? decorationSize.Height : contentSize.Height;
Action drawDecoration = () => DecorationElement?.Draw(canvas, new Size(availableSpace.Width, decorationSize.Height)); Action drawDecoration = () => DecorationElement?.Draw(new Size(availableSpace.Width, decorationSize.Height));
Action drawContent = () => ContentElement?.Draw(canvas, new Size (availableSpace.Width, contentSize.Height)); Action drawContent = () => ContentElement?.Draw(new Size (availableSpace.Width, contentSize.Height));
var first = Type == DecorationType.Prepend ? drawDecoration : drawContent; var first = Type == DecorationType.Prepend ? drawDecoration : drawContent;
var second = Type == DecorationType.Prepend ? drawContent : drawDecoration; var second = Type == DecorationType.Prepend ? drawContent : drawDecoration;
first(); first();
canvas.Translate(new Position(0, translateHeight)); Canvas.Translate(new Position(0, translateHeight));
second(); 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); 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); var imageData = Source?.Invoke(availableSpace);
@@ -26,7 +26,8 @@ namespace QuestPDF.Elements
InternalImage = SKImage.FromEncodedData(imageData) 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); 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"; 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; var targetSize = Child?.Measure(availableSpace) as Size;
if (targetSize == null) if (targetSize == null)
return; return;
canvas.DrawExternalLink(Url, targetSize); Canvas.DrawExternalLink(Url, targetSize);
Child?.Draw(canvas, availableSpace); Child?.Draw(availableSpace);
} }
} }
} }
+1 -1
View File
@@ -30,7 +30,7 @@ namespace QuestPDF.Elements
container.Stack(stack => container.Stack(stack =>
{ {
stack.Spacing(HorizontalSpacing); stack.Spacing(VerticalSpacing);
while (ChildrenQueue.Any()) while (ChildrenQueue.Any())
stack.Item().Row(BuildRow); stack.Item().Row(BuildRow);
+2 -2
View File
@@ -18,12 +18,12 @@ namespace QuestPDF.Elements
return new FullRender(availableSpace); return new FullRender(availableSpace);
} }
internal override void Draw(ICanvas canvas, Size availableSpace) internal override void Draw(Size availableSpace)
{ {
if (InternalImage == null) if (InternalImage == null)
return; 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; } 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; var targetSize = Child?.Measure(availableSpace) as Size;
if (targetSize == null) if (targetSize == null)
return; return;
canvas.DrawLocationLink(LocationName, targetSize); Canvas.DrawLocationLink(LocationName, targetSize);
Child?.Draw(canvas, availableSpace); Child?.Draw(availableSpace);
} }
} }
} }
+17 -4
View File
@@ -2,14 +2,27 @@
namespace QuestPDF.Elements namespace QuestPDF.Elements
{ {
internal class InternalLocation : ContainerElement internal class InternalLocation : ContainerElement, IStateResettable
{ {
public string LocationName { get; set; } public string LocationName { get; set; }
private bool IsRendered { get; set; }
internal override void Draw(ICanvas canvas, Size availableSpace) public void ResetState()
{ {
canvas.DrawLocation(LocationName); IsRendered = false;
Child?.Draw(canvas, availableSpace); }
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 System.Linq;
using QuestPDF.Drawing.SpacePlan; using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
@@ -14,6 +15,12 @@ namespace QuestPDF.Elements
{ {
public List<Layer> Children { get; set; } = new List<Layer>(); public List<Layer> Children { get; set; } = new List<Layer>();
internal override void HandleVisitor(Action<Element?> visit)
{
Children.ForEach(x => x.HandleVisitor(visit));
base.HandleVisitor(visit);
}
internal override ISpacePlan Measure(Size availableSpace) internal override ISpacePlan Measure(Size availableSpace)
{ {
return Children return Children
@@ -21,12 +28,12 @@ namespace QuestPDF.Elements
.Measure(availableSpace); .Measure(availableSpace);
} }
internal override void Draw(ICanvas canvas, Size availableSpace) internal override void Draw(Size availableSpace)
{ {
Children Children
.Where(x => x.Measure(availableSpace) is Size) .Where(x => x.Measure(availableSpace) is Size)
.ToList() .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(); throw new NotSupportedException();
} }
internal override void Draw(ICanvas canvas, Size availableSpace) internal override void Draw(Size availableSpace)
{ {
if (Child == null) if (Child == null)
return; return;
var internalSpace = InternalSpace(availableSpace); var internalSpace = InternalSpace(availableSpace);
canvas.Translate(new Position(Left, Top)); Canvas.Translate(new Position(Left, Top));
Child?.Draw(canvas, internalSpace); Child?.Draw(internalSpace);
canvas.Translate(new Position(-Left, -Top)); Canvas.Translate(new Position(-Left, -Top));
} }
private Size InternalSpace(Size availableSpace) private Size InternalSpace(Size availableSpace)
+28 -6
View File
@@ -1,22 +1,44 @@
using QuestPDF.Fluent; using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
namespace QuestPDF.Elements namespace QuestPDF.Elements
{ {
internal class Page : IComponent 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 Header { get; set; } = Empty.Instance;
public Element Content { get; set; } = Empty.Instance; public Element Content { get; set; } = Empty.Instance;
public Element Footer { get; set; } = Empty.Instance; public Element Footer { get; set; } = Empty.Instance;
public void Compose(IContainer container) public void Compose(IContainer container)
{ {
container.Decoration(decoration => container
{
decoration.Header().Element(Header); .MinWidth(MinSize.Width)
decoration.Content().Extend().Element(Content); .MinHeight(MinSize.Height)
decoration.Footer().Element(Footer);
}); .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);
});
} }
} }
} }
+8 -3
View File
@@ -3,19 +3,24 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements namespace QuestPDF.Elements
{ {
internal class PageBreak : Element internal class PageBreak : Element, IStateResettable
{ {
private bool IsRendered { get; set; } private bool IsRendered { get; set; }
public void ResetState()
{
IsRendered = false;
}
internal override ISpacePlan Measure(Size availableSpace) internal override ISpacePlan Measure(Size availableSpace)
{ {
if (IsRendered) if (IsRendered)
return new FullRender(Size.Zero); 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; IsRendered = true;
} }
-39
View File
@@ -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
};
}
}
}
+3 -4
View File
@@ -18,16 +18,15 @@ namespace QuestPDF.Elements
{ {
container container
.Background(Colors.Grey.Lighten2) .Background(Colors.Grey.Lighten2)
.Padding(5)
.AlignMiddle() .AlignMiddle()
.AlignCenter() .AlignCenter()
.Padding(5)
.MaxHeight(32)
.Element(x => .Element(x =>
{ {
if (string.IsNullOrWhiteSpace(Text)) if (string.IsNullOrWhiteSpace(Text))
x.Image(ImageData, ImageScaling.FitArea); x.MaxHeight(32).Image(ImageData, ImageScaling.FitArea);
else else
x.Text(Text, TextStyle.Default.Size(14).SemiBold()); x.Text(Text, TextStyle.Default.Size(14));
}); });
} }
} }
+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);
}
}
}
+27 -11
View File
@@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using QuestPDF.Drawing.SpacePlan; using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
namespace QuestPDF.Elements namespace QuestPDF.Elements
@@ -11,9 +10,9 @@ namespace QuestPDF.Elements
{ {
public float Width { get; set; } = 1; 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 +37,14 @@ namespace QuestPDF.Elements
internal Element Left { get; set; } internal Element Left { get; set; }
internal Element Right { get; set; } internal Element Right { get; set; }
internal override void HandleVisitor(Action<Element?> visit)
{
Left.HandleVisitor(visit);
Right.HandleVisitor(visit);
base.HandleVisitor(visit);
}
internal override ISpacePlan Measure(Size availableSpace) internal override ISpacePlan Measure(Size availableSpace)
{ {
var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height)) as Size; var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height)) as Size;
@@ -61,16 +68,16 @@ namespace QuestPDF.Elements
return new FullRender(targetSize); 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 leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height));
var leftWidth = (leftMeasurement as Size)?.Width ?? 0; 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)); Canvas.Translate(new Position(leftWidth, 0));
Right.Draw(canvas, new Size(availableSpace.Width - leftWidth, availableSpace.Height)); Right.Draw(new Size(availableSpace.Width - leftWidth, availableSpace.Height));
canvas.Translate(new Position(-leftWidth, 0)); Canvas.Translate(new Position(-leftWidth, 0));
} }
} }
@@ -79,14 +86,20 @@ namespace QuestPDF.Elements
public ICollection<RowElement> Children { get; internal set; } = new List<RowElement>(); public ICollection<RowElement> Children { get; internal set; } = new List<RowElement>();
public float Spacing { get; set; } = 0; 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) internal override ISpacePlan Measure(Size availableSpace)
{ {
return Compose(availableSpace.Width).Measure(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 #region structure
@@ -95,7 +108,10 @@ namespace QuestPDF.Elements
{ {
var elements = AddSpacing(Children, Spacing); var elements = AddSpacing(Children, Spacing);
var rowElements = ReduceRows(elements, availableWidth); 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) 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);
}
}
}
+8 -3
View File
@@ -3,10 +3,15 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements namespace QuestPDF.Elements
{ {
internal class ShowOnce : ContainerElement internal class ShowOnce : ContainerElement, IStateResettable
{ {
private bool IsRendered { get; set; } private bool IsRendered { get; set; }
public void ResetState()
{
IsRendered = false;
}
internal override ISpacePlan Measure(Size availableSpace) internal override ISpacePlan Measure(Size availableSpace)
{ {
if (Child == null || IsRendered) if (Child == null || IsRendered)
@@ -15,7 +20,7 @@ namespace QuestPDF.Elements
return Child.Measure(availableSpace); return Child.Measure(availableSpace);
} }
internal override void Draw(ICanvas canvas, Size availableSpace) internal override void Draw(Size availableSpace)
{ {
if (Child == null || IsRendered) if (Child == null || IsRendered)
return; return;
@@ -23,7 +28,7 @@ namespace QuestPDF.Elements
if (Child.Measure(availableSpace) is FullRender) if (Child.Measure(availableSpace) is FullRender)
IsRendered = true; IsRendered = true;
Child.Draw(canvas, availableSpace); Child.Draw(availableSpace);
} }
} }
} }
+58
View File
@@ -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);
}
}
}
+20 -7
View File
@@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq; using System.Linq;
using QuestPDF.Drawing.SpacePlan; using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Fluent; using QuestPDF.Fluent;
@@ -10,13 +9,26 @@ using IContainer = QuestPDF.Infrastructure.IContainer;
namespace QuestPDF.Elements namespace QuestPDF.Elements
{ {
internal class SimpleStack : Element internal class SimpleStack : Element, IStateResettable
{ {
internal Element First { get; set; } = Empty.Instance; internal Element First { get; set; } = Empty.Instance;
internal Element Second { get; set; } = Empty.Instance; internal Element Second { get; set; } = Empty.Instance;
internal bool IsFirstRendered { get; set; } = false; 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) internal override ISpacePlan Measure(Size availableSpace)
{ {
var firstElement = IsFirstRendered ? Empty.Instance : First; var firstElement = IsFirstRendered ? Empty.Instance : First;
@@ -44,7 +56,7 @@ namespace QuestPDF.Elements
return new FullRender(targetSize); return new FullRender(targetSize);
} }
internal override void Draw(ICanvas canvas, Size availableSpace) internal override void Draw(Size availableSpace)
{ {
var firstElement = IsFirstRendered ? Empty.Instance : First; var firstElement = IsFirstRendered ? Empty.Instance : First;
@@ -56,7 +68,7 @@ namespace QuestPDF.Elements
var firstSize = firstMeasurement as Size; var firstSize = firstMeasurement as Size;
if (firstSize != null) 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) if (firstMeasurement is Wrap || firstMeasurement is PartialRender)
return; return;
@@ -68,9 +80,9 @@ namespace QuestPDF.Elements
if (secondMeasurement == null) if (secondMeasurement == null)
return; return;
canvas.Translate(new Position(0, firstHeight)); Canvas.Translate(new Position(0, firstHeight));
Second.Draw(canvas, new Size(availableSpace.Width, secondMeasurement.Height)); Second.Draw(new Size(availableSpace.Width, secondMeasurement.Height));
canvas.Translate(new Position(0, -firstHeight)); Canvas.Translate(new Position(0, -firstHeight));
if (secondMeasurement is FullRender) if (secondMeasurement is FullRender)
IsFirstRendered = false; IsFirstRendered = false;
@@ -97,6 +109,7 @@ namespace QuestPDF.Elements
return elements; return elements;
return elements return elements
.Where(x => !(x is Empty))
.Select(x => new Padding .Select(x => new Padding
{ {
Bottom = spacing, Bottom = spacing,
-117
View File
@@ -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);
}
}
}
}
+202
View File
@@ -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
};
}
}
}
}
+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);
}
}
}
+31
View File
@@ -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);
}
}
}
+1 -1
View File
@@ -56,7 +56,7 @@ namespace QuestPDF.Fluent
var descriptor = new ComponentDescriptor<T>(component); var descriptor = new ComponentDescriptor<T>(component);
handler?.Invoke(descriptor); 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() static void Component<T>(this IContainer element, Action<ComponentDescriptor<T>>? handler = null) where T : IComponent, new()
+9 -31
View File
@@ -14,7 +14,7 @@ namespace QuestPDF.Fluent
return container; 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) 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 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 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)
{
element.Element(new PageNumber
{
TextFormat = textFormat,
TextStyle = style ?? TextStyle.Default
});
} }
public static IContainer AspectRatio(this IContainer element, float ratio, AspectRatioOption option = AspectRatioOption.FitWidth) public static IContainer AspectRatio(this IContainer element, float ratio, AspectRatioOption option = AspectRatioOption.FitWidth)
@@ -93,24 +84,6 @@ namespace QuestPDF.Fluent
}); });
} }
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) public static void PageBreak(this IContainer element)
{ {
element.Element(new PageBreak()); element.Element(new PageBreak());
@@ -152,7 +125,7 @@ namespace QuestPDF.Fluent
public static void Canvas(this IContainer element, DrawOnCanvas handler) public static void Canvas(this IContainer element, DrawOnCanvas handler)
{ {
element.Element(new Elements.Canvas element.Element(new Canvas
{ {
Handler = handler Handler = handler
}); });
@@ -162,5 +135,10 @@ namespace QuestPDF.Fluent
{ {
return element.Element(new Box()); 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()) foreach (var imageData in document.GenerateImages())
{ {
var path = filePath(index); var path = filePath(index);
if (File.Exists(path))
File.Delete(path);
File.WriteAllBytes(path, imageData); File.WriteAllBytes(path, imageData);
index++; index++;
} }
+57 -2
View File
@@ -1,5 +1,7 @@
using System; using System;
using QuestPDF.Drawing;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
namespace QuestPDF.Fluent namespace QuestPDF.Fluent
@@ -8,6 +10,56 @@ namespace QuestPDF.Fluent
{ {
internal Page Page { get; } = new Page(); 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() public IContainer Header()
{ {
var container = new Container(); var container = new Container();
@@ -32,11 +84,14 @@ namespace QuestPDF.Fluent
public static class PageExtensions 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(); var descriptor = new PageDescriptor();
handler(descriptor); 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
});
}
}
}
-2
View File
@@ -1,6 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
+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();
}
}
}
-2
View File
@@ -1,6 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
+176
View File
@@ -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));
}
}
}
+9 -18
View File
@@ -18,6 +18,11 @@ namespace QuestPDF.Fluent
return style.Mutate(x => x.Color = value); 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) public static TextStyle FontType(this TextStyle style, string value)
{ {
return style.Mutate(x => x.FontType = value); return style.Mutate(x => x.FontType = value);
@@ -38,30 +43,16 @@ namespace QuestPDF.Fluent
return style.Mutate(x => x.IsItalic = value); return style.Mutate(x => x.IsItalic = value);
} }
#region Alignmnet public static TextStyle Stroked(this TextStyle style, bool value = true)
public static TextStyle Alignment(this TextStyle style, HorizontalAlignment value)
{ {
return style.Mutate(x => x.Alignment = value); return style.Mutate(x => x.IsStroked = value);
} }
public static TextStyle AlignLeft(this TextStyle style) public static TextStyle Underlined(this TextStyle style, bool value = true)
{ {
return style.Alignment(HorizontalAlignment.Left); return style.Mutate(x => x.IsUnderlined = value);
} }
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 #region Weight
public static TextStyle Weight(this TextStyle style, FontWeight weight) public static TextStyle Weight(this TextStyle style, FontWeight weight)
+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 const int PointsPerInch = 72;
public static Size A0 => new PageSize(2384.2f, 3370.8f); public static PageSize A0 => new PageSize(2384.2f, 3370.8f);
public static Size A1 => new PageSize(1684, 2384.2f); public static PageSize A1 => new PageSize(1684, 2384.2f);
public static Size A2 => new PageSize(1190.7f, 1684); public static PageSize A2 => new PageSize(1190.7f, 1684);
public static Size A3 => new PageSize(842, 1190.7f); public static PageSize A3 => new PageSize(842, 1190.7f);
public static Size A4 => new PageSize(595.4f, 842); public static PageSize A4 => new PageSize(595.4f, 842);
public static Size A5 => new PageSize(419.6f, 595.4f); public static PageSize A5 => new PageSize(419.6f, 595.4f);
public static Size A6 => new PageSize(297.7f, 419.6f); public static PageSize A6 => new PageSize(297.7f, 419.6f);
public static Size A7 => new PageSize(209.8f, 297.7f); public static PageSize A7 => new PageSize(209.8f, 297.7f);
public static Size A8 => new PageSize(147.4f, 209.8f); public static PageSize A8 => new PageSize(147.4f, 209.8f);
public static Size A9 => new PageSize(104.9f, 147.4f); public static PageSize A9 => new PageSize(104.9f, 147.4f);
public static Size A10 => new PageSize(73.7f, 104.9f); public static PageSize A10 => new PageSize(73.7f, 104.9f);
public static Size B0 => new PageSize(2835, 4008.7f); public static PageSize B0 => new PageSize(2835, 4008.7f);
public static Size B1 => new PageSize(2004.3f, 2835); public static PageSize B1 => new PageSize(2004.3f, 2835);
public static Size B2 => new PageSize(1417.5f, 2004.3f); public static PageSize B2 => new PageSize(1417.5f, 2004.3f);
public static Size B3 => new PageSize(1000.8f, 1417.5f); public static PageSize B3 => new PageSize(1000.8f, 1417.5f);
public static Size B4 => new PageSize(708.8f, 1000.8f); public static PageSize B4 => new PageSize(708.8f, 1000.8f);
public static Size B5 => new PageSize(499, 708.8f); public static PageSize B5 => new PageSize(499, 708.8f);
public static Size B6 => new PageSize(354.4f, 499); public static PageSize B6 => new PageSize(354.4f, 499);
public static Size B7 => new PageSize(249.5f, 354.4f); public static PageSize B7 => new PageSize(249.5f, 354.4f);
public static Size B8 => new PageSize(175.8f, 249.5f); public static PageSize B8 => new PageSize(175.8f, 249.5f);
public static Size B9 => new PageSize(124.7f, 175.8f); public static PageSize B9 => new PageSize(124.7f, 175.8f);
public static Size B10 => new PageSize(87.9f, 124.7f); public static PageSize B10 => new PageSize(87.9f, 124.7f);
public static Size Env10 => new PageSize(683.2f, 294.8f); public static PageSize Env10 => new PageSize(683.2f, 294.8f);
public static Size EnvC4 => new PageSize(649.2f, 918.5f); public static PageSize EnvC4 => new PageSize(649.2f, 918.5f);
public static Size EnvDL => new PageSize(311.9f, 623.7f); public static PageSize EnvDL => new PageSize(311.9f, 623.7f);
public static Size Executive => new PageSize(522, 756); public static PageSize Executive => new PageSize(522, 756);
public static Size Legal => new PageSize(612.4f, 1009.3f); public static PageSize Legal => new PageSize(612.4f, 1009.3f);
public static Size Letter => new PageSize(612.4f, 791); public static PageSize Letter => new PageSize(612.4f, 791);
public static Size ARCH_A => new PageSize(649.2f, 864.7f); public static PageSize ARCH_A => new PageSize(649.2f, 864.7f);
public static Size ARCH_B => new PageSize(864.7f, 1295.6f); public static PageSize ARCH_B => new PageSize(864.7f, 1295.6f);
public static Size ARCH_C => new PageSize(1295.6f, 1729.3f); public static PageSize ARCH_C => new PageSize(1295.6f, 1729.3f);
public static Size ARCH_D => new PageSize(1729.3f, 2591.2f); public static PageSize ARCH_D => new PageSize(1729.3f, 2591.2f);
public static Size ARCH_E => new PageSize(2591.2f, 3455.9f); public static PageSize ARCH_E => new PageSize(2591.2f, 3455.9f);
public static Size ARCH_E1 => new PageSize(2160.3f, 3024.9f); public static PageSize ARCH_E1 => new PageSize(2160.3f, 3024.9f);
public static Size ARCH_E2 => new PageSize(1871.1f, 2735.8f); public static PageSize ARCH_E2 => new PageSize(1871.1f, 2735.8f);
public static Size ARCH_E3 => new PageSize(1944.8f, 2809.5f); public static PageSize ARCH_E3 => new PageSize(1944.8f, 2809.5f);
} }
public static class PageSizeExtensions public static class PageSizeExtensions
+19 -19
View File
@@ -155,25 +155,25 @@ namespace QuestPDF.Helpers
private static readonly string[] BackgroundColors = private static readonly string[] BackgroundColors =
{ {
Colors.Red.Lighten2, Colors.Red.Lighten3,
Colors.Pink.Lighten2, Colors.Pink.Lighten3,
Colors.Purple.Lighten2, Colors.Purple.Lighten3,
Colors.DeepPurple.Lighten2, Colors.DeepPurple.Lighten3,
Colors.Indigo.Lighten2, Colors.Indigo.Lighten3,
Colors.Blue.Lighten2, Colors.Blue.Lighten3,
Colors.LightBlue.Lighten2, Colors.LightBlue.Lighten3,
Colors.Cyan.Lighten2, Colors.Cyan.Lighten3,
Colors.Teal.Lighten2, Colors.Teal.Lighten3,
Colors.Green.Lighten2, Colors.Green.Lighten3,
Colors.LightGreen.Lighten2, Colors.LightGreen.Lighten3,
Colors.Lime.Lighten2, Colors.Lime.Lighten3,
Colors.Yellow.Lighten2, Colors.Yellow.Lighten3,
Colors.Amber.Lighten2, Colors.Amber.Lighten3,
Colors.Orange.Lighten2, Colors.Orange.Lighten3,
Colors.DeepOrange.Lighten2, Colors.DeepOrange.Lighten3,
Colors.Brown.Lighten2, Colors.Brown.Lighten3,
Colors.Grey.Lighten2, Colors.Grey.Lighten3,
Colors.BlueGrey.Lighten2 Colors.BlueGrey.Lighten3
}; };
public static string BackgroundColor() public static string BackgroundColor()
+10 -3
View File
@@ -1,4 +1,5 @@
using QuestPDF.Drawing.SpacePlan; using System;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements; using QuestPDF.Elements;
namespace QuestPDF.Infrastructure namespace QuestPDF.Infrastructure
@@ -13,14 +14,20 @@ namespace QuestPDF.Infrastructure
set => Child = value as Element; set => Child = value as Element;
} }
internal override void HandleVisitor(Action<Element?> visit)
{
base.HandleVisitor(visit);
Child?.HandleVisitor(visit);
}
internal override ISpacePlan Measure(Size availableSpace) internal override ISpacePlan Measure(Size availableSpace)
{ {
return Child?.Measure(availableSpace) ?? new FullRender(Size.Zero); 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);
} }
} }
} }
+16 -1
View File
@@ -1,10 +1,25 @@
using System;
using QuestPDF.Drawing.SpacePlan; using QuestPDF.Drawing.SpacePlan;
namespace QuestPDF.Infrastructure namespace QuestPDF.Infrastructure
{ {
internal abstract class Element : IElement 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 ISpacePlan Measure(Size availableSpace);
internal abstract void Draw(ICanvas canvas, Size availableSpace); internal abstract void Draw(Size availableSpace);
} }
} }
+1 -1
View File
@@ -5,6 +5,6 @@ namespace QuestPDF.Infrastructure
public interface IDocument public interface IDocument
{ {
DocumentMetadata GetMetadata(); 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();
}
}

Some files were not shown because too many files have changed in this diff Show More