Table: refactorization, cell placement algorithm

This commit is contained in:
MarcinZiabek
2021-12-24 14:40:54 +01:00
committed by Marcin Ziąbek
parent 82e4dd36f0
commit d3911d5687
10 changed files with 259 additions and 97 deletions
+87 -31
View File
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
@@ -9,7 +10,7 @@ namespace QuestPDF.Examples
{
public class TableExamples
{
public static Random Random { get; } = new Random(0);
public static Random Random { get; } = new Random();
[Test]
public void Example()
@@ -24,8 +25,7 @@ namespace QuestPDF.Examples
container
.Padding(25)
.Box()
.Border(2)
.MaxHeight(500)
.Border(2)
.Table(table =>
{
table.ColumnsDefinition(columns =>
@@ -36,40 +36,96 @@ namespace QuestPDF.Examples
columns.ConstantColumn(200);
});
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().ColumnSpan(2).Element(CreateBox("A"));
table.Cell().Element(CreateBox("B"));
table.Cell().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().Element(CreateBox("D"));
table.Cell().RowSpan(2).Element(CreateBox("E"));
table.Cell().RowSpan(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"));
table.Cell().RowSpan(2).Element(CreateBox("G"));
table.Cell().RowSpan(2).Element(CreateBox("H"));
table.Cell().Element(CreateBox("I"));
table.Cell().Element(CreateBox("J"));
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"));
});
});
Action<IContainer> CreateBox(string label)
{
return container =>
}
[Test]
public void PerformanceTest()
{
RenderingTest
.Create()
.ProducePdf()
.PageSize(1000, 2000)
.MaxPages(1000)
.ShowResults()
.Render(container =>
{
var height = Random.Next(2, 7) * 25;
container
.Border(1)
.Background(Placeholders.BackgroundColor())
.AlignCenter()
.AlignMiddle()
.Border(1)
.MinHeight(height)
.Text($"{label}: {height}");
};
}
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
foreach (var size in Enumerable.Range(0, 10))
columns.ConstantColumn(100);
});
foreach (var i in Enumerable.Range(1, 10000))
{
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, 7) * 25;
container
.Border(1)
.Background(Placeholders.BackgroundColor())
.Layers(layers =>
{
layers
.PrimaryLayer()
.ExtendHorizontal()
.Height(height);
layers
.Layer()
.AlignCenter()
.AlignMiddle()
.Text($"{label}: {height}px");
});
};
}
}
}
@@ -0,0 +1,25 @@
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;
}
}
}
}
@@ -0,0 +1,9 @@
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Table
{
public interface ITableCellContainer : IContainer
{
}
}
@@ -1,55 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using QuestPDF.Drawing;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
namespace QuestPDF.Elements.Table
{
internal class TableColumnDefinition
{
public float ConstantSize { get; }
public float RelativeSize { get; }
internal float Width { get; set; }
public TableColumnDefinition(float constantSize, float relativeSize)
{
ConstantSize = constantSize;
RelativeSize = relativeSize;
}
}
public interface ITableCellContainer : IContainer
{
}
internal class TableCell : Container, ITableCellContainer
{
public int Row { get; set; } = 1;
public int RowSpan { get; set; } = 1;
public int Column { get; set; } = 1;
public int ColumnSpan { get; set; } = 1;
}
internal class TableRenderingPlan
{
public Size Size { get; set; }
public List<TableCellRenderingCommand> CellRenderingCommands { get; set; }
public int MaxRowRendered { get; set; }
}
internal class TableCellRenderingCommand
{
public TableCell Cell { get; set; }
public Size Size { get; set; }
public Position Offset { get; set; }
}
internal class Table : Element, IStateResettable
{
public ICollection<TableColumnDefinition> Columns { get; } = new List<TableColumnDefinition>();
@@ -212,24 +168,4 @@ namespace QuestPDF.Elements
return Children.Max(x => x.Row + x.RowSpan);
}
}
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;
}
}
}
}
+11
View File
@@ -0,0 +1,11 @@
namespace QuestPDF.Elements.Table
{
internal class TableCell : Container, ITableCellContainer
{
public int Row { get; set; } = 1;
public int RowSpan { get; set; } = 1;
public int Column { get; set; } = 1;
public int ColumnSpan { get; set; } = 1;
}
}
@@ -0,0 +1,11 @@
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Table
{
internal class TableCellRenderingCommand
{
public TableCell Cell { get; set; }
public Size Size { get; set; }
public Position Offset { get; set; }
}
}
@@ -0,0 +1,16 @@
namespace QuestPDF.Elements.Table
{
internal class TableColumnDefinition
{
public float ConstantSize { get; }
public float RelativeSize { get; }
internal float Width { get; set; }
public TableColumnDefinition(float constantSize, float relativeSize)
{
ConstantSize = constantSize;
RelativeSize = relativeSize;
}
}
}
@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Fluent;
namespace QuestPDF.Elements.Table
{
static class TableLayoutPlanner
{
public static void PlanCellPositions(this Table table)
{
PlanCellPositions(table.Columns.Count, table.Children);
}
private static void PlanCellPositions(int columnsCount, ICollection<TableCell> cells)
{
var cellsWindow = new List<TableCell>();
(int x, int y) currentLocation = (1, 1);
foreach (var cell in cells)
{
if (cellsWindow.Count > Math.Max(columnsCount, 16))
{
cellsWindow = cellsWindow
.Where(x => x.Row + x.RowSpan > currentLocation.y)
.ToList();
}
if (cell.HasLocation())
{
cellsWindow.Add(cell);
currentLocation = (cell.Column, cell.Row);
continue;
}
foreach (var location in GenerateCoordinates(columnsCount, currentLocation))
{
cell.Column = location.x;
cell.Row = location.y;
if (cell.CollidesWithAnyOf(cellsWindow))
continue;
cellsWindow.Add(cell);
currentLocation = (cell.Column, cell.Row);
break;
}
}
}
private static IEnumerable<(int x, int y)> GenerateCoordinates(int columnsCount, (int x, int y) startPosition)
{
if (startPosition.x > columnsCount)
throw new ArgumentException();
foreach (var x in Enumerable.Range(startPosition.x, columnsCount - startPosition.x + 1))
yield return (x, startPosition.y);
foreach (var y in Enumerable.Range(startPosition.y + 1, 1_000_000))
foreach (var x in Enumerable.Range(1, columnsCount))
yield return (x, y);
}
private static bool CollidesWith(this TableCell cell, TableCell neighbour)
{
return cell.Column < neighbour.Column + neighbour.ColumnSpan &&
cell.Column + cell.ColumnSpan > neighbour.Column &&
cell.Row < neighbour.Row + neighbour.RowSpan &&
cell.RowSpan + cell.Row > neighbour.Row;
}
private static bool CollidesWithAnyOf(this TableCell cell, ICollection<TableCell> neighbours)
{
return neighbours.Any(cell.CollidesWith);
}
private static bool HasLocation(this TableCell cell)
{
return cell.Row != 1 && cell.Column != 1;
}
}
}
@@ -0,0 +1,12 @@
using System.Collections.Generic;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Table
{
internal class TableRenderingPlan
{
public Size Size { get; set; }
public List<TableCellRenderingCommand> CellRenderingCommands { get; set; }
public int MaxRowRendered { get; set; }
}
}
+5 -1
View File
@@ -1,5 +1,7 @@
using System;
using System.Diagnostics;
using QuestPDF.Elements;
using QuestPDF.Elements.Table;
using QuestPDF.Infrastructure;
namespace QuestPDF.Fluent
@@ -50,7 +52,7 @@ namespace QuestPDF.Fluent
Table.Spacing = value;
}
public ITableCellContainer Cell()
public ITableCellContainer Cell(int row = 1, int column = 1, int rowSpan = 1, int columnsSpan = 1)
{
var cell = new TableCell();
Table.Children.Add(cell);
@@ -66,6 +68,8 @@ namespace QuestPDF.Fluent
var descriptor = new TableDescriptor(table);
handler(descriptor);
table.PlanCellPositions();
element.Element(table);
}
}