Files
QuestPDF/QuestPDF/Elements/Table/Table.cs
T
2021-12-24 14:40:54 +01:00

171 lines
6.0 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Table
{
internal class Table : Element, IStateResettable
{
public ICollection<TableColumnDefinition> Columns { get; } = new List<TableColumnDefinition>();
public ICollection<TableCell> Children { get; } = new List<TableCell>();
public float Spacing { get; set; }
public int CurrentRow { get; set; }
internal override void HandleVisitor(Action<Element?> visit)
{
Children.ToList().ForEach(x => x.HandleVisitor(visit));
base.HandleVisitor(visit);
}
public void ResetState()
{
CurrentRow = 1;
}
internal override SpacePlan Measure(Size availableSpace)
{
UpdateColumnsWidth(availableSpace.Width);
var layout = PlanLayout(availableSpace);
return layout.MaxRowRendered < GetRowsCount()
? SpacePlan.PartialRender(layout.Size)
: SpacePlan.FullRender(layout.Size);
}
internal override void Draw(Size availableSpace)
{
UpdateColumnsWidth(availableSpace.Width);
var layout = PlanLayout(availableSpace);
CurrentRow = layout.MaxRowRendered;
foreach (var command in layout.CellRenderingCommands)
{
Canvas.Translate(command.Offset);
command.Cell.Draw(command.Size);
Canvas.Translate(command.Offset.Reverse());
}
}
private void UpdateColumnsWidth(float availableWidth)
{
var constantWidth = Columns.Sum(x => x.ConstantSize);
var relativeWidth = Columns.Sum(x => x.RelativeSize);
var widthPerRelativeUnit = (relativeWidth > 0) ? (availableWidth - constantWidth) / relativeWidth : 0;
foreach (var column in Columns)
{
column.Width = column.ConstantSize + column.RelativeSize * widthPerRelativeUnit;
}
}
private TableRenderingPlan PlanLayout(Size availableSpace)
{
var cellRenderingCommands = new List<TableCellRenderingCommand>();
// update row heights
var rowsCount = GetRowsCount();
var rowBottomOffsets = new float[rowsCount];
var childrenToTry = Children.Where(x => x.Row >= CurrentRow).OrderBy(x => x.Row);
foreach (var child in childrenToTry)
{
var rowIndex = child.Row - 1;
var topOffset = 0f;
if (rowIndex > 0)
topOffset = rowBottomOffsets[rowIndex - 1]; // look at previous row
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
rowBottomOffsets[targetRowId] = Math.Max(rowBottomOffsets[targetRowId], cellBottomOffset);
}
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)
.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 (!IsCellVisible(cell, rowsToDisplay))
continue;
var leftOffset = GetCellWidthOffset(cell);
var topOffset = rowHeights.Take(cell.Row - 1).Sum();
var width = GetCellWidth(cell);
var height = rowHeights.Skip(cell.Row - 1).Take(cell.RowSpan).Sum();
cellRenderingCommands.Add(new TableCellRenderingCommand()
{
Cell = cell,
Size = new Size(width, height),
Offset = new Position(leftOffset, topOffset)
});
}
return new TableRenderingPlan
{
Size = new Size(totalWidth, totalHeight),
CellRenderingCommands = cellRenderingCommands,
MaxRowRendered = rowsToDisplay
};
float GetCellWidthOffset(TableCell cell)
{
return Columns.Take(cell.Column - 1).Select(x => x.Width).DefaultIfEmpty(0).Sum();
}
float GetCellWidth(TableCell cell)
{
return Columns.Skip(cell.Column - 1).Take(cell.ColumnSpan).Sum(x => x.Width);
}
Size GetCellSize(TableCell cell)
{
var width = GetCellWidth(cell);
var cellSize = new Size(width, Size.Max.Height);
var measurement = cell.Measure(cellSize);
if (measurement.Type == SpacePlanType.Wrap)
return new Size(width, Size.Infinity);
return measurement;
}
bool IsCellVisible(TableCell cell, int maxRow)
{
return cell.Row <= maxRow;
}
}
int GetRowsCount()
{
return Children.Max(x => x.Row + x.RowSpan);
}
}
}