Compare commits

...

4 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
7 changed files with 127 additions and 58 deletions
+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);
}
}
}
+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.");
}
}
}
}
+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;
}
}
}
}
+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)
+4 -4
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);
}
+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)
+2 -2
View File
@@ -4,9 +4,9 @@
<Authors>MarcinZiabek</Authors>
<Company>CodeFlint</Company>
<PackageId>QuestPDF</PackageId>
<Version>2021.9.0</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>Added support for registering custom fonts from a stream.</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>