Table: minor optimizations + performance test
This commit is contained in:
committed by
Marcin Ziąbek
parent
3e5e3843d2
commit
01a80032b0
@@ -13,11 +13,12 @@ namespace QuestPDF.Examples
|
||||
return container
|
||||
.Border(1)
|
||||
.Background(dark ? Colors.Grey.Lighten2 : Colors.White)
|
||||
.Padding(10);
|
||||
.Padding(5);
|
||||
}
|
||||
|
||||
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text, TextStyle.Default.Medium());
|
||||
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text, TextStyle.Default.SemiBold());
|
||||
public static IContainer ValueCell(this IContainer container) => container.Cell(false);
|
||||
public static void ValueCell(this IContainer container, string text) => container.ValueCell().Text(text, TextStyle.Default);
|
||||
}
|
||||
|
||||
public class FrameExample
|
||||
@@ -36,7 +37,7 @@ namespace QuestPDF.Examples
|
||||
.Padding(25)
|
||||
.Stack(stack =>
|
||||
{
|
||||
for(var i=1; i<=4; i++)
|
||||
for(var i = 1; i <= 4; i++)
|
||||
{
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
|
||||
<PackageReference Include="microcharts" Version="0.9.5.9" />
|
||||
<PackageReference Include="nunit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
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;
|
||||
using IContainer = QuestPDF.Infrastructure.IContainer;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class TableExamples
|
||||
{
|
||||
public static Random Random { get; } = new Random();
|
||||
|
||||
|
||||
[Test]
|
||||
public void Example()
|
||||
{
|
||||
@@ -51,86 +55,148 @@ namespace QuestPDF.Examples
|
||||
table.Cell().RowSpan(2).Element(CreateBox("K"));
|
||||
table.Cell().ColumnSpan(2).Element(CreateBox("L"));
|
||||
table.Cell().Element(CreateBox("M"));
|
||||
|
||||
// table.Cell().Row(1).Column(1).ColumnSpan(2).Element(CreateBox("A"));
|
||||
// table.Cell().Row(1).Column(3).Element(CreateBox("B"));
|
||||
// table.Cell().Row(1).Column(4).Element(CreateBox("C"));
|
||||
//
|
||||
// table.Cell().Row(2).Column(1).Element(CreateBox("D"));
|
||||
// table.Cell().Row(2).RowSpan(2).Column(2).Element(CreateBox("E"));
|
||||
// table.Cell().Row(2).RowSpan(3).Column(3).ColumnSpan(2).Element(CreateBox("F"));
|
||||
//
|
||||
// table.Cell().Row(3).RowSpan(2).Column(1).Element(CreateBox("G"));
|
||||
// table.Cell().Row(4).RowSpan(2).Column(2).Element(CreateBox("H"));
|
||||
// table.Cell().Row(5).Column(3).Element(CreateBox("I"));
|
||||
// table.Cell().Row(5).Column(4).Element(CreateBox("J"));
|
||||
// table.Cell().Row(5).RowSpan(2).Column(1).Element(CreateBox("K"));
|
||||
// table.Cell().Row(6).Column(2).ColumnSpan(2).Element(CreateBox("L"));
|
||||
// table.Cell().Row(6).Column(4).Element(CreateBox("M"));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PerformanceTest()
|
||||
public void TreeTable()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProducePdf()
|
||||
.PageSize(1002, 2002)
|
||||
.MaxPages(1000)
|
||||
.EnableCaching()
|
||||
.EnableDebugging(false)
|
||||
.PageSize(PageSizes.A4)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(1)
|
||||
.Padding(25)
|
||||
.Box()
|
||||
.Border(2)
|
||||
.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
foreach (var size in Enumerable.Range(0, 10))
|
||||
columns.ConstantColumn(100);
|
||||
columns.RelativeColumn(100);
|
||||
columns.RelativeColumn(100);
|
||||
columns.RelativeColumn(100);
|
||||
});
|
||||
|
||||
foreach (var i in Enumerable.Range(1, 10_000))
|
||||
{
|
||||
table
|
||||
.Cell()
|
||||
.RowSpan((uint)Random.Next(1, 5))
|
||||
.ColumnSpan((uint)Random.Next(1, 5))
|
||||
.Element(CreateBox(i.ToString()));
|
||||
}
|
||||
table.Cell().RowSpan(4).Element(CreateBox("A"));
|
||||
|
||||
table.Cell().RowSpan(2).Element(CreateBox("B"));
|
||||
table.Cell().Element(CreateBox("C"));
|
||||
table.Cell().Element(CreateBox("D"));
|
||||
|
||||
table.Cell().RowSpan(2).Element(CreateBox("E"));
|
||||
table.Cell().Element(CreateBox("F"));
|
||||
table.Cell().Element(CreateBox("G"));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TemperatureReport()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProducePdf()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ShowResults()
|
||||
.Render(container => GeneratePerformanceStructure(container, 100));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TemperatureReport_PerformanceTest()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProducePdf()
|
||||
.PageSize(PageSizes.A4)
|
||||
.MaxPages(10000)
|
||||
.EnableCaching()
|
||||
.EnableDebugging(false)
|
||||
.ShowResults()
|
||||
.Render(container => GeneratePerformanceStructure(container, 1000));
|
||||
}
|
||||
|
||||
public static void GeneratePerformanceStructure(IContainer container, int repeats)
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.Box()
|
||||
.Border(2)
|
||||
.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.ConstantColumn(100);
|
||||
columns.RelativeColumn();
|
||||
columns.ConstantColumn(100);
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
foreach (var _ in Enumerable.Range(0, repeats))
|
||||
{
|
||||
table.Cell().RowSpan(3).LabelCell("Project");
|
||||
table.Cell().RowSpan(3).ValueCell(Placeholders.Sentence());
|
||||
|
||||
table.Cell().LabelCell("Date");
|
||||
table.Cell().ValueCell(Placeholders.ShortDate());
|
||||
|
||||
table.Cell().LabelCell("Report number");
|
||||
table.Cell().ValueCell(Placeholders.Integer());
|
||||
|
||||
table.Cell().LabelCell("Inspector");
|
||||
table.Cell().ValueCell("Marcin Ziąbek");
|
||||
|
||||
table.Cell().ColumnSpan(2).LabelCell("Morning weather");
|
||||
table.Cell().ColumnSpan(2).LabelCell("Evening weather");
|
||||
|
||||
table.Cell().ValueCell("Time");
|
||||
table.Cell().ValueCell("7:13");
|
||||
|
||||
table.Cell().ValueCell("Time");
|
||||
table.Cell().ValueCell("18:25");
|
||||
|
||||
table.Cell().ValueCell("Description");
|
||||
table.Cell().ValueCell("Sunny");
|
||||
|
||||
table.Cell().ValueCell("Description");
|
||||
table.Cell().ValueCell("Windy");
|
||||
|
||||
table.Cell().ValueCell("Wind");
|
||||
table.Cell().ValueCell("Mild");
|
||||
|
||||
table.Cell().ValueCell("Wind");
|
||||
table.Cell().ValueCell("Strong");
|
||||
|
||||
table.Cell().ValueCell("Temperature");
|
||||
table.Cell().ValueCell("17°C");
|
||||
|
||||
table.Cell().ValueCell("Temperature");
|
||||
table.Cell().ValueCell("32°C");
|
||||
|
||||
table.Cell().LabelCell("Remarks");
|
||||
table.Cell().ColumnSpan(3).ValueCell(Placeholders.Paragraph());
|
||||
|
||||
table.Cell().ColumnSpan(4).BorderBottom(2);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Action<IContainer> CreateBox(string label)
|
||||
{
|
||||
return container =>
|
||||
{
|
||||
var height = Random.Next(2, 7) * 25;
|
||||
var height = Random.Next(2, 6) * 10;
|
||||
|
||||
container
|
||||
.Border(2)
|
||||
.Background(Placeholders.BackgroundColor())
|
||||
.Layers(layers =>
|
||||
{
|
||||
layers
|
||||
.PrimaryLayer()
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.Height(height)
|
||||
.Width(80)
|
||||
.Border(1);
|
||||
|
||||
layers
|
||||
.Layer()
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.Text($"{label}: {height}px");
|
||||
});
|
||||
// .AlignCenter()
|
||||
// .AlignMiddle()
|
||||
.Height(height);
|
||||
// .Text($"{label}: {height}px");
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Engines;
|
||||
using BenchmarkDotNet.Running;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Drawing.Proxy;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
[SimpleJob(RunStrategy.Monitoring, launchCount: 0, warmupCount: 1, targetCount: 16)]
|
||||
[MinColumn, MaxColumn, MeanColumn, MedianColumn]
|
||||
public class TablePerformanceTest
|
||||
{
|
||||
private static Random Random { get; } = new Random();
|
||||
|
||||
private PageContext PageContext { get; set; }
|
||||
private DocumentMetadata Metadata { get; set; }
|
||||
private Container Content { get; set; }
|
||||
|
||||
[Test]
|
||||
public void Run()
|
||||
{
|
||||
var configuration = ManualConfig
|
||||
.Create(DefaultConfig.Instance)
|
||||
.WithOptions(ConfigOptions.DisableOptimizationsValidator);
|
||||
|
||||
BenchmarkRunner.Run<TablePerformanceTest>(configuration);
|
||||
}
|
||||
|
||||
[IterationSetup]
|
||||
[SetUp]
|
||||
public void GenerateReportData()
|
||||
{
|
||||
Metadata = new DocumentMetadata()
|
||||
{
|
||||
DocumentLayoutExceptionThreshold = 1000
|
||||
};
|
||||
|
||||
var documentContainer = new DocumentContainer();
|
||||
|
||||
documentContainer.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A3);
|
||||
GeneratePerformanceStructure(page.Content(), 10_000);
|
||||
});
|
||||
|
||||
Content = documentContainer.Compose();
|
||||
|
||||
PageContext = new PageContext();
|
||||
DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, Metadata, null);
|
||||
|
||||
Content.HandleVisitor(x =>
|
||||
{
|
||||
if (x is ICacheable)
|
||||
x.CreateProxy(y => new CacheProxy(y));
|
||||
});
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[Test]
|
||||
public void GenerationTest()
|
||||
{
|
||||
DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, Metadata, null);
|
||||
}
|
||||
|
||||
void GeneratePerformanceStructure(IContainer container, int itemsCount)
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
foreach (var size in Enumerable.Range(0, 10))
|
||||
columns.ConstantColumn(100);
|
||||
});
|
||||
|
||||
foreach (var i in Enumerable.Range(1, itemsCount))
|
||||
{
|
||||
table
|
||||
.Cell()
|
||||
.RowSpan((uint)Random.Next(1, 5))
|
||||
.ColumnSpan((uint)Random.Next(1, 5))
|
||||
.Element(CreateBox(i.ToString()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Action<IContainer> CreateBox(string label)
|
||||
{
|
||||
return container =>
|
||||
{
|
||||
var height = Random.Next(2, 6) * 20;
|
||||
|
||||
container
|
||||
.Border(2)
|
||||
.Background(Placeholders.BackgroundColor())
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.Height(height)
|
||||
.Text($"{label}: {height}px");
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,8 @@ namespace QuestPDF.ReportSample
|
||||
[Test]
|
||||
public void GenerateAndShowPdf()
|
||||
{
|
||||
ImagePlaceholder.Solid = true;
|
||||
|
||||
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.pdf");
|
||||
Report.GeneratePdf(path);
|
||||
Process.Start("explorer.exe", path);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Table
|
||||
@@ -12,7 +13,9 @@ namespace QuestPDF.Elements.Table
|
||||
public List<TableCell> Children { get; } = new List<TableCell>();
|
||||
public float Spacing { get; set; }
|
||||
|
||||
public int CurrentRow { get; set; }
|
||||
private TableCell[][] OrderedChildren { get; set; }
|
||||
private int RowsCount { get; set; }
|
||||
private int CurrentRow { get; set; }
|
||||
|
||||
internal override void HandleVisitor(Action<Element?> visit)
|
||||
{
|
||||
@@ -22,6 +25,21 @@ namespace QuestPDF.Elements.Table
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
if (RowsCount == default)
|
||||
RowsCount = Children.Max(x => x.Row + x.RowSpan);
|
||||
|
||||
if (OrderedChildren == default)
|
||||
{
|
||||
var groups = Children
|
||||
.GroupBy(x => x.Row)
|
||||
.ToDictionary(x => x.Key, x => x.OrderBy(y => y.Column).ToArray());
|
||||
|
||||
OrderedChildren = Enumerable
|
||||
.Range(0, RowsCount)
|
||||
.Select(x => groups.TryGetValue(x, out var output) ? output : Array.Empty<TableCell>())
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
CurrentRow = 1;
|
||||
}
|
||||
|
||||
@@ -31,7 +49,7 @@ namespace QuestPDF.Elements.Table
|
||||
|
||||
var layout = PlanLayout(availableSpace);
|
||||
|
||||
return layout.MaxRowRendered < GetRowsCount()
|
||||
return layout.MaxRowRendered < RowsCount
|
||||
? SpacePlan.PartialRender(layout.Size)
|
||||
: SpacePlan.FullRender(layout.Size);
|
||||
}
|
||||
@@ -75,23 +93,30 @@ namespace QuestPDF.Elements.Table
|
||||
.ToList()
|
||||
.ForEach(x => cellOffsets[x] = Columns[x - 1].Width + cellOffsets[x - 1]);
|
||||
|
||||
|
||||
|
||||
// update row heights
|
||||
var rowsCount = GetRowsCount();
|
||||
var rowsCount = RowsCount;
|
||||
var rowBottomOffsets = new float[rowsCount];
|
||||
var childrenToTry = Children.Where(x => x.Row >= CurrentRow).OrderBy(x => x.Row);
|
||||
var childrenToTry = Enumerable.Range(CurrentRow - 1, RowsCount - CurrentRow).SelectMany(x => OrderedChildren[x]);
|
||||
|
||||
var currentRow = CurrentRow;
|
||||
|
||||
foreach (var child in childrenToTry)
|
||||
{
|
||||
if (currentRow < child.Row)
|
||||
if (child.Row > currentRow)
|
||||
{
|
||||
rowBottomOffsets[currentRow] = Math.Max(rowBottomOffsets[currentRow], rowBottomOffsets[currentRow-1]);
|
||||
if (rowBottomOffsets[currentRow - 1] > availableSpace.Height + Single.Epsilon)
|
||||
break;
|
||||
|
||||
currentRow = child.Row;
|
||||
}
|
||||
|
||||
|
||||
var rowIndex = child.Row - 1;
|
||||
|
||||
if (rowIndex > 1)
|
||||
rowBottomOffsets[rowIndex] = Math.Max(rowBottomOffsets[rowIndex], rowBottomOffsets[rowIndex-1]);
|
||||
|
||||
var topOffset = 0f;
|
||||
|
||||
if (rowIndex > 0)
|
||||
@@ -102,11 +127,13 @@ namespace QuestPDF.Elements.Table
|
||||
|
||||
var targetRowId = child.Row + child.RowSpan - 2; // -1 because indexing starts at 0, -1 because rowSpan starts at 1
|
||||
rowBottomOffsets[targetRowId] = Math.Max(rowBottomOffsets[targetRowId], cellBottomOffset);
|
||||
|
||||
//if (targetRowId > 1 && rowBottomOffsets[targetRowId - 1] > availableSpace.Height)
|
||||
// break;
|
||||
}
|
||||
|
||||
Enumerable
|
||||
.Range(1, rowsCount - 1)
|
||||
.ToList()
|
||||
.ForEach(x => rowBottomOffsets[x] = Math.Max(rowBottomOffsets[x], rowBottomOffsets[x-1]));
|
||||
|
||||
var rowHeights = new float[rowsCount];
|
||||
rowHeights[0] = rowBottomOffsets[0];
|
||||
|
||||
@@ -166,10 +193,5 @@ namespace QuestPDF.Elements.Table
|
||||
return measurement;
|
||||
}
|
||||
}
|
||||
|
||||
int GetRowsCount()
|
||||
{
|
||||
return Children.Max(x => x.Row + x.RowSpan);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
<Version>2021.12.0</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>$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt"))</PackageReleaseNotes>
|
||||
<LangVersion>8</LangVersion>
|
||||
<LangVersion>10</LangVersion>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageIcon>Logo.png</PackageIcon>
|
||||
<PackageIconUrl>https://www.questpdf.com/images/package-logo.png</PackageIconUrl>
|
||||
|
||||
Reference in New Issue
Block a user