Table: optimized rendering performance

This commit is contained in:
MarcinZiabek
2021-12-27 18:24:37 +01:00
committed by Marcin Ziąbek
parent 01a80032b0
commit 940616cd3e
4 changed files with 59 additions and 74 deletions
+6 -8
View File
@@ -103,7 +103,7 @@ namespace QuestPDF.Examples
.ProducePdf()
.PageSize(PageSizes.A4)
.ShowResults()
.Render(container => GeneratePerformanceStructure(container, 100));
.Render(container => GeneratePerformanceStructure(container, 2));
}
[Test]
@@ -117,7 +117,7 @@ namespace QuestPDF.Examples
.EnableCaching()
.EnableDebugging(false)
.ShowResults()
.Render(container => GeneratePerformanceStructure(container, 1000));
.Render(container => GeneratePerformanceStructure(container, 100));
}
public static void GeneratePerformanceStructure(IContainer container, int repeats)
@@ -136,16 +136,16 @@ namespace QuestPDF.Examples
columns.RelativeColumn();
});
foreach (var _ in Enumerable.Range(0, repeats))
foreach (var i in Enumerable.Range(0, repeats))
{
table.Cell().RowSpan(3).LabelCell("Project");
table.Cell().RowSpan(3).ValueCell(Placeholders.Sentence());
table.Cell().LabelCell("Report number");
table.Cell().ValueCell(i.ToString());
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");
@@ -179,8 +179,6 @@ namespace QuestPDF.Examples
table.Cell().LabelCell("Remarks");
table.Cell().ColumnSpan(3).ValueCell(Placeholders.Paragraph());
table.Cell().ColumnSpan(4).BorderBottom(2);
}
});
}
@@ -1,25 +0,0 @@
using System;
using System.Collections.Generic;
namespace QuestPDF.Elements.Table
{
internal static class EnumerableExtensions
{
public static IEnumerable<T> Scan<T>(this IEnumerable<T> input, Func<T, T, T> accumulate)
{
using var enumerator = input.GetEnumerator();
if (!enumerator.MoveNext())
yield break;
var state = enumerator.Current;
yield return state;
while (enumerator.MoveNext())
{
state = accumulate(state, enumerator.Current);
yield return state;
}
}
}
}
+52 -40
View File
@@ -2,11 +2,38 @@
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Table
{
/// <summary>
/// This dictionary allows to access key that does not exist.
/// Instead of throwing an exception, it returns a default value.
/// </summary>
internal class DynamicDictionary<TKey, TValue>
{
private TValue Default { get; }
private IDictionary<TKey, TValue> Dictionary { get; } = new Dictionary<TKey, TValue>();
public DynamicDictionary()
{
}
public DynamicDictionary(TValue defaultValue)
{
Default = defaultValue;
}
public TValue this[TKey key]
{
get => Dictionary.TryGetValue(key, out var value) ? value : Default;
set => Dictionary[key] = value;
}
public List<KeyValuePair<TKey, TValue>> Items => Dictionary.ToList();
}
internal class Table : Element, IStateResettable
{
public List<TableColumnDefinition> Columns { get; } = new List<TableColumnDefinition>();
@@ -26,7 +53,7 @@ namespace QuestPDF.Elements.Table
public void ResetState()
{
if (RowsCount == default)
RowsCount = Children.Max(x => x.Row + x.RowSpan);
RowsCount = Children.Max(x => x.Row + x.RowSpan - 1);
if (OrderedChildren == default)
{
@@ -35,7 +62,7 @@ namespace QuestPDF.Elements.Table
.ToDictionary(x => x.Key, x => x.OrderBy(y => y.Column).ToArray());
OrderedChildren = Enumerable
.Range(0, RowsCount)
.Range(0, RowsCount + 1)
.Select(x => groups.TryGetValue(x, out var output) ? output : Array.Empty<TableCell>())
.ToArray();
}
@@ -93,12 +120,9 @@ namespace QuestPDF.Elements.Table
.ToList()
.ForEach(x => cellOffsets[x] = Columns[x - 1].Width + cellOffsets[x - 1]);
// update row heights
var rowsCount = RowsCount;
var rowBottomOffsets = new float[rowsCount];
var childrenToTry = Enumerable.Range(CurrentRow - 1, RowsCount - CurrentRow).SelectMany(x => OrderedChildren[x]);
var rowBottomOffsets = new DynamicDictionary<int, float>();
var childrenToTry = Enumerable.Range(CurrentRow, RowsCount - CurrentRow + 1).SelectMany(x => OrderedChildren[x]);
var currentRow = CurrentRow;
@@ -106,59 +130,47 @@ namespace QuestPDF.Elements.Table
{
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)
topOffset = rowBottomOffsets[rowIndex - 1]; // look at previous row
var topOffset = rowBottomOffsets[child.Row - 1];
var height = GetCellSize(child).Height;
var cellBottomOffset = height + topOffset;
var targetRowId = child.Row + child.RowSpan - 2; // -1 because indexing starts at 0, -1 because rowSpan starts at 1
var targetRowId = child.Row + child.RowSpan - 1; // -1 because rowSpan starts at 1
rowBottomOffsets[targetRowId] = Math.Max(rowBottomOffsets[targetRowId], cellBottomOffset);
}
var rowHeights = new DynamicDictionary<int, float>();
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];
Enumerable
.Range(1, rowsCount - 1)
.Range(CurrentRow, rowBottomOffsets.Items.Count)
.ToList()
.ForEach(x => rowHeights[x] = rowBottomOffsets[x] - rowBottomOffsets[x-1]);
// find rows count to render in this pass
var rowsToDisplay = rowHeights.Scan((x, y) => x + y).Count(x => x <= availableSpace.Height + Size.Epsilon);
rowHeights = rowHeights.Take(rowsToDisplay).ToArray();
var totalHeight = rowHeights.Sum();
var totalWidth = Columns.Sum(x => x.Width);
foreach (var cell in Children)
{
if (cell.Row >= CurrentRow && cell.Row > rowsToDisplay)
continue;
var maxRowToDisplay = rowBottomOffsets.Items.Where(x => x.Value <= availableSpace.Height + Size.Epsilon).Max(x => x.Key);
var totalHeight = rowHeights.Items.Where(x => x.Key <= maxRowToDisplay).Sum(x => x.Value);
var totalWidth = Columns.Sum(x => x.Width);
var childrenToDraw = Enumerable
.Range(CurrentRow, maxRowToDisplay - CurrentRow + 1)
.SelectMany(x => OrderedChildren[x]);
foreach (var cell in childrenToDraw)
{
var leftOffset = cellOffsets[cell.Column - 1];
var topOffset = cell.Row == 1 ? 0 : rowBottomOffsets[cell.Row - 2];
var topOffset = cell.Row == 1 ? 0 : rowBottomOffsets[cell.Row - 1];
var width = GetCellWidth(cell);
var height = Enumerable.Range(cell.Row - 1, cell.RowSpan).TakeWhile(x => x < rowHeights.Length).Select(x => rowHeights[x]).Sum();
var height = Enumerable.Range(cell.Row, cell.RowSpan).Select(x => rowHeights[x]).Sum();
cellRenderingCommands.Add(new TableCellRenderingCommand()
{
@@ -172,7 +184,7 @@ namespace QuestPDF.Elements.Table
{
Size = new Size(totalWidth, totalHeight),
CellRenderingCommands = cellRenderingCommands,
MaxRowRendered = rowsToDisplay
MaxRowRendered = maxRowToDisplay
};
float GetCellWidth(TableCell cell)
+1 -1
View File
@@ -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>10</LangVersion>
<LangVersion>9</LangVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageIcon>Logo.png</PackageIcon>
<PackageIconUrl>https://www.questpdf.com/images/package-logo.png</PackageIconUrl>