Compare commits

...

14 Commits

Author SHA1 Message Date
Marcin Ziąbek feab280a27 Fixed detecting infinite layout exception in specific cases 2021-09-13 01:09:45 +02:00
Marcin Ziąbek 4152a0f701 Improved row performance and layout structure stability 2021-09-02 00:53:50 +02:00
Marcin Ziąbek 6c80c59996 Fixed misspelling in release notes 2021-09-01 19:19:16 +02:00
Marcin Ziąbek 08a37666fe 2021.9.1 2021-09-01 19:14:52 +02:00
Marcin Ziąbek c2fb68562d 2021.9.0 2021-08-30 23:37:44 +02:00
Marcin Ziąbek 683ab362d8 Added support for registering font types based on the suggestion of brendonparker 2021-08-29 20:15:12 +02:00
Marcin Ziąbek 04af33fc31 Included examples for DSL and complex layouts 2021-08-23 23:51:42 +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
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 c05c5e1597 Update readme.md
Updated code, added the support section
2021-05-15 16:09:01 +02:00
29 changed files with 604 additions and 222 deletions
+32
View File
@@ -0,0 +1,32 @@
using System.IO;
using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class BarCode
{
[Test]
public void Example()
{
FontManager.RegisterFontType("LibreBarcode39", File.OpenRead("LibreBarcode39-Regular.ttf"));
RenderingTest
.Create()
.PageSize(400, 100)
.FileName()
.Render(container =>
{
container
.Background(Colors.White)
.AlignCenter()
.AlignMiddle()
.Text("*QuestPDF*", TextStyle.Default.FontType("LibreBarcode39").Size(64));
});
}
}
}
+46
View File
@@ -0,0 +1,46 @@
using System.Diagnostics;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class ContinuousPageDocument : IDocument
{
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public void Compose(IDocumentContainer container)
{
container.Page(page =>
{
page.Margin(20);
page.ContinuousSize(150);
page.Header().Text("Header");
page.Content().PaddingVertical(10).Border(1).Padding(10).Stack(stack =>
{
foreach (var index in Enumerable.Range(1, 100))
stack.Item().Text($"Line {index}", TextStyle.Default.Color(Placeholders.Color()));
});
page.Footer().Text("Footer");
});
}
}
public class ContinuousPageExamples
{
[Test]
public void ContinuousPage()
{
var path = "example.pdf";
new ContinuousPageDocument().GeneratePdf(path);
Process.Start("explorer", path);
}
}
}
+237 -67
View File
@@ -18,6 +18,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(200, 150)
.FileName()
.Render(container =>
{
container
@@ -33,6 +34,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(300, 300)
.FileName()
.Render(container =>
{
container
@@ -55,49 +57,14 @@ namespace QuestPDF.Examples
});
});
}
// [Test]
// public void Page()
// {
// RenderingTest
// .Create()
// .PageSize(298, 421)
// .Render(container =>
// {
// container
// .Background("#FFF")
// .Padding(15)
// .Page(page =>
// {
// page.Header()
// .Height(60)
// .Background(Colors.Grey.Lighten1)
// .AlignCenter()
// .AlignMiddle()
// .Text("Header");
//
// page.Content()
// .Background(Colors.Grey.Lighten2)
// .AlignCenter()
// .AlignMiddle()
// .Text("Content");
//
// page.Footer()
// .Height(30)
// .Background(Colors.Grey.Lighten1)
// .AlignCenter()
// .AlignMiddle()
// .Text("Footer");
// });
// });
// }
[Test]
public void Row()
{
RenderingTest
.Create()
.PageSize(740, 200)
.FileName()
.Render(container =>
{
container
@@ -138,6 +105,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(740, 200)
.FileName()
.Render(container =>
{
container
@@ -159,6 +127,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(500, 360)
.FileName()
.Render(container =>
{
container
@@ -181,6 +150,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(210, 210)
.FileName()
.Render(container =>
{
container
@@ -203,6 +173,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(300, 200)
.FileName()
.Render(container =>
{
var text = "";
@@ -225,6 +196,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(400, 230)
.FileName()
.Render(container =>
{
var textStyle = TextStyle.Default.Size(14);
@@ -259,6 +231,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(300, 200)
.FileName()
.Render(container =>
{
container
@@ -288,6 +261,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(400, 250)
.FileName()
.Render(container =>
{
container
@@ -363,6 +337,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(300, 300)
.FileName()
.Render(container =>
{
container
@@ -407,6 +382,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(450, 150)
.FileName()
.Render(container =>
{
container
@@ -439,6 +415,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(500, 175)
.FileName()
.Render(container =>
{
container
@@ -465,6 +442,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(300, 300)
.FileName()
.Render(container =>
{
container
@@ -504,6 +482,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(300, 150)
.FileName()
.Render(container =>
{
container
@@ -523,21 +502,25 @@ namespace QuestPDF.Examples
{
RenderingTest
.Create()
.PageSize(400, 400)
.PageSize(400, 250)
.FileName()
.Render(container =>
{
var style = TextStyle.Default.Size(20);
container
.Background("#FFF")
.Padding(25)
.Stack(stack =>
{
stack.Spacing(5);
stack.Item().Background(Placeholders.BackgroundColor()).Scale(0.5f).Text("Smaller text", style);
stack.Item().Background(Placeholders.BackgroundColor()).Text("Normal text", style);
stack.Item().Background(Placeholders.BackgroundColor()).Scale(2f).Text("Bigger text", style);
stack.Item().Background(Placeholders.BackgroundColor()).Scale(-1.5f).Text("Flipped text", style);
var scales = new[] { 0.75f, 1f, 1.25f, 1.5f };
foreach (var scale in scales)
{
stack
.Item()
.Border(1)
.Scale(scale)
.Padding(10)
.Text($"Content with {scale} scale.", TextStyle.Default.Size(20));
}
});
});
}
@@ -547,31 +530,39 @@ namespace QuestPDF.Examples
{
RenderingTest
.Create()
.PageSize(500, 300)
.PageSize(300, 200)
.FileName()
.Render(container =>
{
container
.Background("#FFF")
.Padding(25)
.Box()
.Background(Colors.Green.Lighten4)
.Padding(5)
.Padding(25)
.Background(Colors.Green.Lighten3)
.TranslateX(15)
.TranslateY(15)
.Text("Text outside of bounds");
.Border(2)
.BorderColor(Colors.Green.Darken1)
.Padding(50)
.Text("Moved text", TextStyle.Default.Size(25));
});
}
[Test]
public void Rotate()
public void ConstrainedRotate()
{
RenderingTest
.Create()
.PageSize(450, 450)
.PageSize(350, 350)
.FileName()
.Render(container =>
{
container
.Background("#FFF")
.Padding(20)
.Grid(grid =>
{
@@ -581,11 +572,10 @@ namespace QuestPDF.Examples
foreach (var turns in Enumerable.Range(0, 4))
{
grid.Item()
.Width(200)
.Height(200)
.Background(Colors.Grey.Lighten3)
.Width(150)
.Height(150)
.Background(Colors.Grey.Lighten2)
.Padding(10)
//.Box()
.Element(element =>
{
foreach (var x in Enumerable.Range(0, turns))
@@ -594,23 +584,83 @@ namespace QuestPDF.Examples
return element;
})
.Box()
.Background(Colors.Grey.Lighten1)
.Text($"Rotated {turns * 90} degrees.", TextStyle.Default.Size(14));
.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(450, 450)
.PageSize(350, 350)
.FileName()
.Render(container =>
{
container
.Background("#FFF")
.Padding(20)
.Grid(grid =>
{
@@ -620,21 +670,24 @@ namespace QuestPDF.Examples
foreach (var turns in Enumerable.Range(0, 4))
{
grid.Item()
.Width(200)
.Height(200)
.Width(150)
.Height(150)
.Background(Colors.Grey.Lighten3)
.Padding(10)
.Element(element =>
{
if (turns == 1 || turns == 2)
element = element.FlipX();
element = element.FlipHorizontal();
if (turns == 2 || turns == 3)
element = element.FlipY();
element = element.FlipVertical();
return element;
})
.Text($"Flipped.", TextStyle.Default.Size(14));
.Box()
.Background(Colors.White)
.Padding(10)
.Text($"Flipped {turns}", TextStyle.Default.Size(16));
}
});
});
@@ -646,9 +699,11 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(200, 200)
.FileName()
.Render(container =>
{
container
.Padding(10)
.Border(2)
.Row(row =>
{
@@ -663,5 +718,120 @@ namespace QuestPDF.Examples
});
});
}
[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);
});
});
}
[Test]
public void ComplexLayout()
{
RenderingTest
.Create()
.PageSize(500, 225)
.FileName()
.Render(container =>
{
container
.Padding(25)
.Stack(stack =>
{
stack.Item().Row(row =>
{
row.RelativeColumn().LabelCell("Label 1");
row.RelativeColumn(3).Grid(grid =>
{
grid.Columns(3);
grid.Item(2).LabelCell("Label 2");
grid.Item().LabelCell("Label 3");
grid.Item(2).ValueCell().Text("Value 2");
grid.Item().ValueCell().Text("Value 3");
});
});
stack.Item().Row(row =>
{
row.RelativeColumn().ValueCell().Text("Value 1");
row.RelativeColumn(3).Grid(grid =>
{
grid.Columns(3);
grid.Item().LabelCell("Label 4");
grid.Item(2).LabelCell("Label 5");
grid.Item().ValueCell().Text("Value 4");
grid.Item(2).ValueCell().Text("Value 5");
});
});
stack.Item().Row(row =>
{
row.RelativeColumn().LabelCell("Label 6");
row.RelativeColumn().ValueCell().Text("Value 6");
});
});
});
}
[Test]
public void DomainSpecificLanguage()
{
RenderingTest
.Create()
.PageSize(600, 310)
.FileName()
.Render(container =>
{
container
.Padding(25)
.Grid(grid =>
{
grid.Columns(10);
for(var i=1; i<=4; i++)
{
grid.Item(2).LabelCell(Placeholders.Label());
grid.Item(3).ValueCell().Image(Placeholders.Image(200, 150));
}
});
});
}
}
}
+2 -1
View File
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using QuestPDF.Drawing;
using QuestPDF.Elements;
using QuestPDF.Fluent;
@@ -23,7 +24,7 @@ namespace QuestPDF.Examples.Engine
return new RenderingTest();
}
public RenderingTest FileName(string fileName)
public RenderingTest FileName([CallerMemberName] string fileName = "test")
{
FileNamePrefix = fileName;
return this;
+1 -1
View File
@@ -33,7 +33,7 @@ namespace QuestPDF.Examples.Engine
container.Page(page =>
{
page.Size(new PageSize(Size.Width, Size.Height));
page.Content().Container().Element(Container as Container);
page.Content().Container().Background(Colors.White).Element(Container as Container);
});
}
}
+4 -3
View File
@@ -12,11 +12,11 @@ namespace QuestPDF.Examples
{
return container
.Border(1)
.Background(dark ? "#EEE" : "#FFF")
.Background(dark ? Colors.Grey.Lighten2 : Colors.White)
.Padding(10);
}
public static IContainer LabelCell(this IContainer container) => container.Cell(true);
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text, TextStyle.Default.Medium());
public static IContainer ValueCell(this IContainer container) => container.Cell(false);
}
@@ -28,6 +28,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(550, 400)
.FileName()
.Render(container =>
{
container
@@ -39,7 +40,7 @@ namespace QuestPDF.Examples
{
stack.Item().Row(row =>
{
row.RelativeColumn(2).LabelCell().Text(Placeholders.Label());
row.RelativeColumn(2).LabelCell(Placeholders.Label());
row.RelativeColumn(3).ValueCell().Text(Placeholders.Paragraph());
});
}
Binary file not shown.
+1
View File
@@ -36,6 +36,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(350, 280)
.FileName()
.Render(container =>
{
container
+4
View File
@@ -13,6 +13,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(200, 150)
.FileName()
.Render(container =>
{
container
@@ -35,6 +36,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(200, 150)
.FileName()
.Render(container =>
{
container
@@ -58,6 +60,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(200, 150)
.FileName()
.Render(container =>
{
container
@@ -108,6 +111,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(200, 150)
.FileName()
.Render(container =>
{
container
@@ -16,4 +16,10 @@
<ProjectReference Include="..\QuestPDF\QuestPDF.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="LibreBarcode39-Regular.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
@@ -25,17 +25,6 @@ namespace QuestPDF.ReportSample.Layouts
public void Compose(IDocumentContainer container)
{
container
// .Page(page =>
// {
// page.MarginVertical(40);
// page.MarginHorizontal(50);
//
// page.ContinuousSize(PageSizes.A4.Width);
//
// page.Header().Element(ComposeHeader);
// page.Content().Element(ComposeContent);
// page.Footer().AlignCenter().PageNumber("Page A {number}");
// })
.Page(page =>
{
page.MarginVertical(40);
+10 -2
View File
@@ -60,7 +60,10 @@ namespace QuestPDF.Drawing
var spacePlan = content.Measure(Size.Max) as Size;
if (spacePlan == null)
break;
{
canvas.EndDocument();
ThrowLayoutException();
}
try
{
@@ -78,7 +81,7 @@ namespace QuestPDF.Drawing
if (currentPage >= documentMetadata.DocumentLayoutExceptionThreshold)
{
canvas.EndDocument();
throw new DocumentLayoutException("Composed layout generates infinite document.");
ThrowLayoutException();
}
if (spacePlan is FullRender)
@@ -88,6 +91,11 @@ namespace QuestPDF.Drawing
}
canvas.EndDocument();
void ThrowLayoutException()
{
throw new DocumentLayoutException("Composed layout generates infinite document.");
}
}
}
}
@@ -1,15 +1,23 @@
using System.Collections.Concurrent;
using System;
using System.Collections.Concurrent;
using System.IO;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Drawing
{
internal static class CanvasCache
public static class FontManager
{
private static ConcurrentDictionary<string, SKTypeface> Typefaces = new ConcurrentDictionary<string, SKTypeface>();
private static ConcurrentDictionary<string, SKPaint> Paints = new ConcurrentDictionary<string, SKPaint>();
private static ConcurrentDictionary<string, SKPaint> ColorPaint = new ConcurrentDictionary<string, SKPaint>();
public static void RegisterFontType(string fontName, Stream stream)
{
Typefaces.TryAdd(fontName, SKTypeface.FromStream(stream));
}
internal static SKPaint ColorToPaint(this string color)
{
return ColorPaint.GetOrAdd(color, Convert);
@@ -29,12 +37,10 @@ namespace QuestPDF.Drawing
static SKPaint Convert(TextStyle style)
{
var slant = style.IsItalic ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
return new SKPaint
{
Color = SKColor.Parse(style.Color),
Typeface = SKTypeface.FromFamilyName(style.FontType, (int)style.FontWeight, (int)SKFontStyleWidth.Normal, slant),
Typeface = GetTypeface(style),
TextSize = style.Size,
TextEncoding = SKTextEncoding.Utf32,
@@ -47,6 +53,17 @@ namespace QuestPDF.Drawing
}
};
}
static SKTypeface GetTypeface(TextStyle style)
{
if (Typefaces.TryGetValue(style.FontType, out var result))
return result;
var slant = style.IsItalic ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
return SKTypeface.FromFamilyName(style.FontType, (int)style.FontWeight, (int)SKFontStyleWidth.Normal, slant)
?? throw new ArgumentException($"The typeface {style.FontType} could not be found.");
}
}
internal static TextMeasurement BreakText(this TextStyle style, string text, float availableWidth)
-1
View File
@@ -31,7 +31,6 @@ namespace QuestPDF.Drawing
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);
+13 -1
View File
@@ -1,3 +1,4 @@
using System;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
@@ -36,9 +37,20 @@ namespace QuestPDF.Elements
.Decoration(decoration =>
{
decoration.Header().Element(Header);
decoration.Content().Extend().Element(Content);
decoration
.Content()
.Element(x => IsClose(MinSize.Width, MaxSize.Width) ? x.ExtendHorizontal() : x)
.Element(x => IsClose(MinSize.Height, MaxSize.Height) ? x.ExtendVertical() : x)
.Element(Content);
decoration.Footer().Element(Footer);
});
bool IsClose(float x, float y)
{
return Math.Abs(x - y) < Size.Epsilon;
}
}
}
}
+4 -40
View File
@@ -1,57 +1,21 @@
using System;
using System.Linq;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class Rotate : 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;
public float Angle { get; set; } = 0;
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.RotateRadians(NormalizedTurnCount * (float) Math.PI / 2f);
if (NormalizedTurnCount == 1 || NormalizedTurnCount == 3)
availableSpace = new Size(availableSpace.Height, availableSpace.Width);
skiaCanvas.RotateDegrees(Angle);
Child?.Draw(availableSpace);
skiaCanvas.SetMatrix(currentMatrix);
}
+37 -44
View File
@@ -7,29 +7,31 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal abstract class RowElement : ContainerElement
internal abstract class RowElement : Constrained
{
public float Width { get; set; } = 1;
public float Size { get; protected set; }
internal override void Draw(Size availableSpace)
public void SetWidth(float width)
{
Child?.Draw(availableSpace);
MinWidth = width;
MaxWidth = width;
}
}
internal class ConstantRowElement : RowElement
{
public ConstantRowElement(float width)
public ConstantRowElement(float size)
{
Width = width;
Size = size;
SetWidth(size);
}
}
internal class RelativeRowElement : RowElement
{
public RelativeRowElement(float width)
public RelativeRowElement(float size)
{
Width = width;
Size = size;
}
}
@@ -84,70 +86,61 @@ namespace QuestPDF.Elements
internal class Row : Element
{
public ICollection<RowElement> Children { get; internal set; } = new List<RowElement>();
public float Spacing { get; set; } = 0;
public ICollection<RowElement> Children { get; internal set; } = new List<RowElement>();
private Element? RootElement { get; set; }
internal override void HandleVisitor(Action<Element?> visit)
{
Children.ToList().ForEach(x => x.HandleVisitor(visit));
if (RootElement == null)
ComposeTree();
RootElement.HandleVisitor(visit);
base.HandleVisitor(visit);
}
internal override ISpacePlan Measure(Size availableSpace)
{
return Compose(availableSpace.Width).Measure(availableSpace);
UpdateElementsWidth(availableSpace.Width);
return RootElement.Measure(availableSpace);
}
internal override void Draw(Size availableSpace)
{
Compose(availableSpace.Width).Draw(availableSpace);
UpdateElementsWidth(availableSpace.Width);
RootElement.Draw(availableSpace);
}
#region structure
private Element Compose(float availableWidth)
private void ComposeTree()
{
var elements = AddSpacing(Children, Spacing);
var rowElements = ReduceRows(elements, availableWidth);
var tree = BuildTree(rowElements.ToArray());
tree.HandleVisitor(x => x.Initialize(PageContext, Canvas));
Children = AddSpacing(Children, Spacing);
return tree;
var elements = Children.Cast<Element>().ToArray();
RootElement = BuildTree(elements);
}
private static ICollection<Element> ReduceRows(ICollection<RowElement> elements, float availableWidth)
private void UpdateElementsWidth(float availableWidth)
{
var constantWidth = elements
var constantWidth = Children
.Where(x => x is ConstantRowElement)
.Cast<ConstantRowElement>()
.Sum(x => x.Width);
.Sum(x => x.Size);
var relativeWidth = elements
var relativeWidth = Children
.Where(x => x is RelativeRowElement)
.Cast<RelativeRowElement>()
.Sum(x => x.Width);
.Sum(x => x.Size);
var widthPerRelativeUnit = (availableWidth - constantWidth) / relativeWidth;
return elements
.Select(x =>
{
if (x is RelativeRowElement r)
return new ConstantRowElement(r.Width * widthPerRelativeUnit)
{
Child = x.Child
};
return x;
})
.Select(x => new Constrained
{
MinWidth = x.Width,
MaxWidth = x.Width,
Child = x.Child
})
.Cast<Element>()
.ToList();
Children
.Where(x => x is RelativeRowElement)
.Cast<RelativeRowElement>()
.ToList()
.ForEach(x => x.SetWidth(x.Size * widthPerRelativeUnit));
}
private static ICollection<RowElement> AddSpacing(ICollection<RowElement> elements, float spacing)
+59
View File
@@ -0,0 +1,59 @@
using System;
using System.Linq;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class SimpleRotate : ContainerElement
{
public int TurnCount { get; set; }
public int NormalizedTurnCount => (TurnCount % 4 + 4) % 4;
internal override ISpacePlan Measure(Size availableSpace)
{
if (NormalizedTurnCount == 0 || NormalizedTurnCount == 2)
return base.Measure(availableSpace);
availableSpace = new Size(availableSpace.Height, availableSpace.Width);
var childSpace = base.Measure(availableSpace) as Size;
if (childSpace == null)
return new Wrap();
var targetSpace = new Size(childSpace.Height, childSpace.Width);
if (childSpace is FullRender)
return new FullRender(targetSpace);
if (childSpace is PartialRender)
return new PartialRender(targetSpace);
throw new ArgumentException();
}
internal override void Draw(Size availableSpace)
{
var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
if (skiaCanvas == null)
return;
var currentMatrix = skiaCanvas.TotalMatrix;
if (NormalizedTurnCount == 1 || NormalizedTurnCount == 2)
skiaCanvas.Translate(availableSpace.Width, 0);
if (NormalizedTurnCount == 2 || NormalizedTurnCount == 3)
skiaCanvas.Translate(0, availableSpace.Height);
skiaCanvas.RotateDegrees(NormalizedTurnCount * 90);
if (NormalizedTurnCount == 1 || NormalizedTurnCount == 3)
availableSpace = new Size(availableSpace.Height, availableSpace.Width);
Child?.Draw(availableSpace);
skiaCanvas.SetMatrix(currentMatrix);
}
}
}
+2 -2
View File
@@ -5,8 +5,8 @@ namespace QuestPDF.Elements
{
internal class Translate : ContainerElement
{
public float TranslateX { get; set; } = 1;
public float TranslateY { get; set; } = 1;
public float TranslateX { get; set; } = 0;
public float TranslateY { get; set; } = 0;
internal override void Draw(Size availableSpace)
{
+32
View File
@@ -0,0 +1,32 @@
using System;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class Unconstrained : ContainerElement
{
internal override ISpacePlan Measure(Size availableSpace)
{
var childSize = Child?.Measure(Size.Max) ?? new FullRender(Size.Zero);
if (childSize is PartialRender)
return new PartialRender(Size.Zero);
if (childSize is FullRender)
return new FullRender(Size.Zero);
return childSize;
}
internal override void Draw(Size availableSpace)
{
var measurement = Child?.Measure(Size.Max) as Size;
if (measurement == null)
return;
Child?.Draw(measurement);
}
}
}
+10 -5
View File
@@ -18,10 +18,10 @@ namespace QuestPDF.Fluent
{
if (element?.Child != null && element.Child is Empty == false)
{
var message = $"Container {element.GetType().Name} already contains an {element.Child.GetType().Name} child. " +
$"Tried to assign {child?.GetType()?.Name}." +
$"You should not assign multiple elements to single-child containers.";
var message = "You should not assign multiple child elements to a single-child container. " +
"This may happen when a container variable is used outside of its scope/closure OR the container is used in multiple fluent API chains OR the container is used incorrectly in a loop. " +
"This exception is thrown to help you detect that some part of the code is overriding fragments of the document layout with a new content - essentially destroying existing content.";
throw new DocumentComposeException(message);
}
@@ -104,7 +104,7 @@ namespace QuestPDF.Fluent
style.Alignment = alignment.Horizontal;
}
element.Element(new Text()
element.Element(new Text
{
Value = text.ToString(),
Style = style
@@ -162,5 +162,10 @@ namespace QuestPDF.Fluent
{
return element.Element(new Box());
}
public static IContainer Unconstrained(this IContainer element)
{
return element.Element(new Unconstrained());
}
}
}
+4
View File
@@ -39,6 +39,10 @@ namespace QuestPDF.Fluent
foreach (var imageData in document.GenerateImages())
{
var path = filePath(index);
if (File.Exists(path))
File.Delete(path);
File.WriteAllBytes(path, imageData);
index++;
}
+15 -5
View File
@@ -12,14 +12,24 @@ namespace QuestPDF.Fluent
public void Size(PageSize pageSize)
{
Page.MinSize = pageSize;
Page.MaxSize = pageSize;
MinSize(pageSize);
MaxSize(pageSize);
}
public void ContinuousSize(float width)
{
Page.MinSize = new PageSize(width, 0);
Page.MaxSize = new PageSize(width, Infrastructure.Size.Max.Height);
MinSize(new PageSize(width, 0));
MaxSize(new PageSize(width, Infrastructure.Size.Max.Height));
}
public void MinSize(PageSize pageSize)
{
Page.MinSize = pageSize;
}
public void MaxSize(PageSize pageSize)
{
Page.MaxSize = pageSize;
}
public void MarginLeft(float value)
+13 -4
View File
@@ -6,9 +6,9 @@ namespace QuestPDF.Fluent
{
public static class RotateExtensions
{
private static IContainer Rotate(this IContainer element, Action<Rotate> handler)
private static IContainer SimpleRotate(this IContainer element, Action<SimpleRotate> handler)
{
var scale = element as Rotate ?? new Rotate();
var scale = element as SimpleRotate ?? new SimpleRotate();
handler(scale);
return element.Element(scale);
@@ -16,12 +16,21 @@ namespace QuestPDF.Fluent
public static IContainer RotateLeft(this IContainer element)
{
return element.Rotate(x => x.TurnCount--);
return element.SimpleRotate(x => x.TurnCount--);
}
public static IContainer RotateRight(this IContainer element)
{
return element.Rotate(x => x.TurnCount++);
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
});
}
}
}
+8 -8
View File
@@ -16,32 +16,32 @@ namespace QuestPDF.Fluent
public static IContainer Scale(this IContainer element, float value)
{
return element.ScaleX(value).ScaleY(value);
return element.ScaleHorizontal(value).ScaleVertical(value);
}
public static IContainer ScaleX(this IContainer element, float value)
public static IContainer ScaleHorizontal(this IContainer element, float value)
{
return element.Scale(x => x.ScaleX = value);
}
public static IContainer FlipX(this IContainer element)
public static IContainer FlipHorizontal(this IContainer element)
{
return element.ScaleX(-1);
return element.ScaleHorizontal(-1);
}
public static IContainer ScaleY(this IContainer element, float value)
public static IContainer ScaleVertical(this IContainer element, float value)
{
return element.Scale(x => x.ScaleY = value);
}
public static IContainer FlipY(this IContainer element)
public static IContainer FlipVertical(this IContainer element)
{
return element.ScaleY(-1);
return element.ScaleVertical(-1);
}
public static IContainer FlipOver(this IContainer element)
{
return element.FlipX().FlipY();
return element.FlipHorizontal().FlipVertical();
}
}
}
+1 -6
View File
@@ -13,12 +13,7 @@ namespace QuestPDF.Fluent
return element.Element(translate);
}
public static IContainer Translate(this IContainer element, float value)
{
return element.TranslateX(value).TranslateY(value);
}
public static IContainer TranslateX(this IContainer element, float value)
{
return element.Translate(x => x.TranslateX = value);
+4 -4
View File
@@ -4,9 +4,9 @@
<Authors>MarcinZiabek</Authors>
<Company>CodeFlint</Company>
<PackageId>QuestPDF</PackageId>
<Version>2021.5.2</Version>
<Version>2021.9.3</Version>
<PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
<PackageReleaseNotes>New elements: Box, Grid, Canvas, EnsureSpance and Layers. Redesigned the Debug element. Added material design colors. Added list of basic fonts. Added spacing property to the Row element. Fluent API improvements and increased stability.</PackageReleaseNotes>
<PackageReleaseNotes>Added support for registering custom fonts from a stream. Fixed continuous page setting. Improved exception messages.</PackageReleaseNotes>
<LangVersion>8</LangVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageIcon>Logo.png</PackageIcon>
@@ -14,8 +14,8 @@
<PackageProjectUrl>https://www.questpdf.com/</PackageProjectUrl>
<RepositoryUrl>https://github.com/QuestPDF/library.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Copyright>QuestPDF contributors</Copyright>
<PackageTags>PDF file export generate create render portable document format quest free</PackageTags>
<Copyright>Marcin Ziąbek, QuestPDF contributors</Copyright>
<PackageTags>pdf file export generate generation tool create creation render portable document format quest html library converter free</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Nullable>enable</Nullable>
<TargetFrameworks>net462;netstandard2.0;netcoreapp2.0;netcoreapp3.0</TargetFrameworks>
+14
View File
@@ -0,0 +1,14 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 2021.5.2 | :white_check_mark: |
| Older | :x: |
## Reporting a Vulnerability
Please report any code vulnerabilities using GitHub Issues. We are trying to respond within 1 day and analyze reported situation as soon as possible.
Please collaborate with us to find best solutation available.
Once the vulnerability is recognized and fixed, the issue is closed with appropraite message.
+23 -12
View File
@@ -16,6 +16,15 @@
6) **Enjoy fast PDF generation** - QuestPDF is created upon SkiaSharp, a well-known graphical library, and converts your data into PDF documents. It offers a highly optimized layouting engine capable of generating over 1000 PDF files per minute per core. The entire process is thread-safe.
## Support QuestPDF
All great frameworks and libraries started from zero. Please help us to make QuestPDF a commonly known library and an obvious choice in case of generating PDF documents. It can be as easy as:
- Giving this repository a star ⭐ so more people will know about it,
- Observing 🤩 the library to know about each new realease,
- Trying our sample project to see how easy it is to create an invoice 📊,
- Sharing your thoughts 💬 with us and your colleagues,
- Simply using it 👨‍💻 and suggesting new features,
- Creating new features 🆕 for everybody.
## Installation
@@ -25,7 +34,6 @@ The library is available as a nuget package. You can install it as any other nug
<img src="https://github.com/QuestPDF/example-invoice/raw/main/images/nuget.svg" width="200px">
</a>
## Documentation
1. [Release notes and roadmap](https://www.questpdf.com/documentation/releases.html) - everything that is planned for future library iterations, description of new features and information about potential breaking changes.
@@ -46,16 +54,19 @@ For tutorial, documentation and API reference, please visit [the QuestPDF docume
Here you can find an example code showing how easy is to write and understand the fluent API:
```csharp
public void Compose(IContainer container)
{
public void Compose(IDocumentContainer container)
{
container
.PaddingHorizontal(50)
.PaddingVertical(50)
.Page(page =>
{
page.Header(ComposeHeader);
page.Content(ComposeContent);
page.Footer().AlignCenter().PageNumber("Page {number}");
page.MarginVertical(60);
page.MarginHorizontal(40);
page.Size(PageSizes.A4);
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().PageNumber();
});
}
@@ -63,11 +74,11 @@ void ComposeHeader(IContainer container)
{
container.Row(row =>
{
row.RelativeColumn().Stack(column =>
row.RelativeColumn().Stack(stack =>
{
column.Element().Text($"Invoice #{Model.InvoiceNumber}", TextStyle.Default.Size(20).Bold());
column.Element().Text($"Issue date: {Model.IssueDate:d}");
column.Element().Text($"Due date: {Model.DueDate:d}");
stack.Item().Text($"Invoice #{Model.InvoiceNumber}", TextStyle.Default.Size(20).Bold());
stack.Item().Text($"Issue date: {Model.IssueDate:d}");
stack.Item().Text($"Due date: {Model.DueDate:d}");
});
row.ConstantColumn(100).Height(50).Placeholder();