diff --git a/QuestPDF.Examples/TableBenchmark.cs b/QuestPDF.Examples/TableBenchmark.cs new file mode 100644 index 0000000..2d61355 --- /dev/null +++ b/QuestPDF.Examples/TableBenchmark.cs @@ -0,0 +1,46 @@ +using System.Linq; +using NUnit.Framework; +using QuestPDF.Examples.Engine; +using QuestPDF.Fluent; +using QuestPDF.Helpers; + +namespace QuestPDF.Examples +{ + public class TableBenchmark + { + [Test] + public void Benchmark() + { + RenderingTest + .Create() + .ProducePdf() + .PageSize(PageSizes.A4) + .ShowResults() + .MaxPages(20_000) + .EnableCaching(true) + .EnableDebugging(false) + .Render(container => + { + container + .Padding(10) + .MinimalBox() + .Border(1) + .Table(table => + { + const int numberOfRows = 250_000; + const int numberOfColumns = 10; + + table.ColumnsDefinition(columns => + { + foreach (var _ in Enumerable.Range(0, numberOfColumns)) + columns.RelativeColumn(); + }); + + foreach (var row in Enumerable.Range(0, numberOfRows)) + foreach (var column in Enumerable.Range(0, numberOfColumns)) + table.Cell().Background(Placeholders.BackgroundColor()).Padding(5).Text($"{row}_{column}"); + }); + }); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Table/Table.cs b/QuestPDF/Elements/Table/Table.cs index f508576..4a33c25 100644 --- a/QuestPDF/Elements/Table/Table.cs +++ b/QuestPDF/Elements/Table/Table.cs @@ -19,15 +19,22 @@ namespace QuestPDF.Elements.Table private int RowsCount { get; set; } private int CurrentRow { get; set; } + // cache that stores all cells + // first index: row number + // inner table: list of all cells that ends at the corresponding row + private TableCell[][] CellsCache { get; set; } + private int MaxRow { get; set; } + internal override void Initialize(IPageContext pageContext, ICanvas canvas) { StartingRowsCount = Cells.Select(x => x.Row).DefaultIfEmpty(0).Max(); RowsCount = Cells.Select(x => x.Row + x.RowSpan - 1).DefaultIfEmpty(0).Max(); Cells = Cells.OrderBy(x => x.Row).ThenBy(x => x.Column).ToList(); + BuildCache(); base.Initialize(pageContext, canvas); } - + internal override IEnumerable GetChildren() { return Cells; @@ -38,6 +45,31 @@ namespace QuestPDF.Elements.Table Cells.ForEach(x => x.IsRendered = false); CurrentRow = 1; } + + private void BuildCache() + { + if (CellsCache != null) + return; + + if (Cells.Count == 0) + { + MaxRow = 0; + CellsCache = Array.Empty(); + + return; + } + + var groups = Cells + .GroupBy(x => x.Row + x.RowSpan - 1) + .ToDictionary(x => x.Key, x => x.OrderBy(x => x.Column).ToArray()); + + MaxRow = groups.Max(x => x.Key); + + CellsCache = Enumerable + .Range(0, MaxRow + 1) + .Select(x => groups.ContainsKey(x) ? groups[x] : Array.Empty()) + .ToArray(); + } internal override SpacePlan Measure(Size availableSpace) { @@ -143,7 +175,10 @@ namespace QuestPDF.Elements.Table var rowBottomOffsets = new DynamicDictionary(); var commands = new List(); - var cellsToTry = Cells.Where(x => x.Row + x.RowSpan - 1 >= CurrentRow); + var cellsToTry = Enumerable + .Range(CurrentRow, MaxRow - CurrentRow + 1) + .SelectMany(x => CellsCache[x]); + var currentRow = CurrentRow; var maxRenderingRow = RowsCount;