171 lines
6.0 KiB
C#
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);
|
|
}
|
|
}
|
|
} |