Table: row height fixes for overlapping cells. Added code examples

This commit is contained in:
Marcin Ziąbek
2022-01-06 00:06:24 +01:00
parent 6e59fa6e3d
commit 01980e5635
8 changed files with 214 additions and 165 deletions
+21 -10
View File
@@ -15,30 +15,41 @@ namespace QuestPDF.Examples
{
RenderingTest
.Create()
.PageSize(175, 100)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Background(Colors.White)
.Background(Colors.Grey.Lighten2)
.Padding(25)
.Box()
.Layers(layers =>
{
layers.PrimaryLayer().Padding(10).Text("Sample text");
layers.Layer().Canvas((canvas, size) =>
{
using var paint = new SKPaint
DrawRoundedRectangle(Colors.White, false);
DrawRoundedRectangle(Colors.Blue.Darken2, true);
void DrawRoundedRectangle(string color, bool isStroke)
{
Color = SKColor.Parse(Colors.Black),
IsStroke = true,
StrokeWidth = 1,
IsAntialias = true
};
using var paint = new SKPaint
{
Color = SKColor.Parse(color),
IsStroke = isStroke,
StrokeWidth = 2,
IsAntialias = true
};
canvas.DrawRoundRect(0, 0, size.Width, size.Height, 20, 20, paint);
canvas.DrawRoundRect(0, 0, size.Width, size.Height, 20, 20, paint);
}
});
layers
.PrimaryLayer()
.PaddingVertical(10)
.PaddingHorizontal(20)
.Text("Sample text", TextStyle.Default.Size(16).Color(Colors.Blue.Darken2).SemiBold());
});
});
}
@@ -0,0 +1,55 @@
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class DifferentHeaderOnFirstPageExample
{
[Test]
public void Placeholder()
{
RenderingTest
.Create()
.PageSize(PageSizes.A6)
.ProduceImages()
.ShowResults()
.EnableDebugging()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A6);
page.Margin(30);
page.Background(Colors.White);
page.Header().Stack(stack =>
{
stack.Item().ShowOnce().Background(Colors.Blue.Lighten2).Height(60);
stack.Item().SkipOnce().Background(Colors.Green.Lighten2).Height(40);
});
page.Content().PaddingVertical(10).Stack(stack =>
{
stack.Spacing(10);
foreach (var _ in Enumerable.Range(0, 13))
stack.Item().Background(Colors.Grey.Lighten2).Height(40);
});
page.Footer().AlignCenter().Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.Size(16));
text.CurrentPageNumber();
text.Span(" / ");
text.TotalPages();
});
});
});
}
}
}
+104 -125
View File
@@ -40,23 +40,11 @@ namespace QuestPDF.Examples
columns.RelativeColumn();
});
// by using the custom element, we can reuse the same style
table.Cell().Row(1).Column(4).Element(x => TextContent(x, "A"));
table.Cell().Row(2).Column(2).Element(x => TextContent(x, "A"));
table.Cell().Row(3).Column(3).Element(x => TextContent(x, "A"));
table.Cell().Row(4).Column(1).Element(x => TextContent(x, "A"));
static void TextContent(IContainer container, string text)
{
container
.Border(1)
.Background(Colors.Grey.Lighten3)
.MinWidth(50)
.MinHeight(50)
.AlignCenter()
.AlignMiddle()
.Text(text);
}
// by using custom 'Element' method, we can reuse visual configuration
table.Cell().Row(1).Column(4).Element(Block).Text("A");
table.Cell().Row(2).Column(2).Element(Block).Text("B");
table.Cell().Row(3).Column(3).Element(Block).Text("C");
table.Cell().Row(4).Column(1).Element(Block).Text("D");
});
});
}
@@ -86,21 +74,10 @@ namespace QuestPDF.Examples
columns.RelativeColumn();
});
table.DefaultCellStyle(cell =>
{
return cell
.Border(1)
.Background(Colors.Grey.Lighten3)
.MinWidth(50)
.MinHeight(50)
.AlignCenter()
.AlignMiddle();
});
table.Cell().Row(1).Column(1).Text("A");
table.Cell().Row(2).Column(2).Text("B");
table.Cell().Row(1).Column(3).Text("C");
table.Cell().Row(2).Column(4).Text("D");
table.Cell().Row(1).Column(1).Element(Block).Text("A");
table.Cell().Row(2).Column(2).Element(Block).Text("B");
table.Cell().Row(1).Column(3).Element(Block).Text("C");
table.Cell().Row(2).Column(4).Element(Block).Text("D");
});
});
}
@@ -160,17 +137,52 @@ namespace QuestPDF.Examples
columns.RelativeColumn();
});
table.Cell().TextBox("A");
table.Cell().Row(2).Column(2).TextBox("B");
table.Cell().TextBox("C");
table.Cell().Row(3).Column(3).TextBox("D");
table.Cell().ColumnSpan(2).TextBox("E");
table.Cell().Element(Block).Text("A");
table.Cell().Row(2).Column(2).Element(Block).Text("B");
table.Cell().Element(Block).Text("C");
table.Cell().Row(3).Column(3).Element(Block).Text("D");
table.Cell().ColumnSpan(2).Element(Block).Text("E");
});
});
}
[Test]
public void ExtendLastCellsToTableBottom()
{
RenderingTest
.Create()
.ProduceImages()
.PageSize(220, 170)
.ShowResults()
.Render(container =>
{
container
.Padding(10)
.Box()
.Border(1)
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
});
table.ExtendLastCellsToTableBottom();
table.Cell().Row(1).Column(1).Element(Block).Text("A");
table.Cell().Row(3).Column(1).Element(Block).Text("B");
table.Cell().Row(2).Column(2).Element(Block).Text("C");
table.Cell().Row(3).Column(3).Element(Block).Text("D");
table.Cell().Row(2).RowSpan(2).Column(4).Element(Block).Text("E");
});
});
}
[Test]
public void Overlapping()
{
RenderingTest
.Create()
@@ -191,43 +203,10 @@ namespace QuestPDF.Examples
columns.RelativeColumn();
columns.RelativeColumn();
});
table.ExtendLastCellsToTableBottom();
table.Cell().Row(1).Column(1).TextBox("A");
table.Cell().Row(3).Column(1).TextBox("B");
table.Cell().Row(2).Column(2).TextBox("C");
table.Cell().Row(3).Column(3).TextBox("D");
});
});
}
[Test]
public void Overlapping()
{
RenderingTest
.Create()
.ProduceImages()
.PageSize(170, 120)
.ShowResults()
.Render(container =>
{
container
.Padding(10)
.Box()
.Border(1)
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
});
table.Cell().RowSpan(2).ColumnSpan(2).Background(Colors.Green.Lighten3);
table.Cell().Background(Colors.Blue.Lighten3).MinHeight(50);
table.Cell().Row(2).Column(2).ColumnSpan(2).Background(Colors.Red.Lighten3).MinHeight(50);
table.Cell().Row(1).RowSpan(3).Column(1).ColumnSpan(3).Background(Colors.Grey.Lighten3).MinHeight(150);
table.Cell().Row(1).RowSpan(2).Column(1).ColumnSpan(2).Background(Colors.Grey.Lighten1).MinHeight(100);
table.Cell().Row(3).Column(3).Background(Colors.Grey.Darken1).MinHeight(50);
});
});
}
@@ -254,15 +233,15 @@ namespace QuestPDF.Examples
columns.RelativeColumn();
});
table.Cell().RowSpan(2).ColumnSpan(2).TextBox("1");
table.Cell().ColumnSpan(2).TextBox("2");
table.Cell().TextBox("3");
table.Cell().TextBox("4");
table.Cell().RowSpan(2).TextBox("5");
table.Cell().ColumnSpan(2).TextBox("6");
table.Cell().RowSpan(2).TextBox("7");
table.Cell().TextBox("8");
table.Cell().TextBox("9");
table.Cell().RowSpan(2).ColumnSpan(2).Element(Block).Text("1");
table.Cell().ColumnSpan(2).Element(Block).Text("2");
table.Cell().Element(Block).Text("3");
table.Cell().Element(Block).Text("4");
table.Cell().RowSpan(2).Element(Block).Text("5");
table.Cell().ColumnSpan(2).Element(Block).Text("6");
table.Cell().RowSpan(2).Element(Block).Text("7");
table.Cell().Element(Block).Text("8");
table.Cell().Element(Block).Text("9");
});
});
}
@@ -289,15 +268,15 @@ namespace QuestPDF.Examples
columns.RelativeColumn();
});
table.Cell().RowSpan(4).TextBox("1");
table.Cell().RowSpan(4).Element(Block).Text("1");
table.Cell().RowSpan(2).TextBox("2");
table.Cell().RowSpan(1).TextBox("3");
table.Cell().RowSpan(1).TextBox("4");
table.Cell().RowSpan(2).Element(Block).Text("2");
table.Cell().RowSpan(1).Element(Block).Text("3");
table.Cell().RowSpan(1).Element(Block).Text("4");
table.Cell().RowSpan(2).TextBox("5");
table.Cell().RowSpan(1).TextBox("6");
table.Cell().RowSpan(1).TextBox("7");
table.Cell().RowSpan(2).Element(Block).Text("5");
table.Cell().RowSpan(1).Element(Block).Text("6");
table.Cell().RowSpan(1).Element(Block).Text("7");
});
});
}
@@ -332,24 +311,38 @@ namespace QuestPDF.Examples
.Border(1)
.Decoration(decoration =>
{
IContainer DefaultCellStyle(IContainer container, string backgroundColor)
{
return container
.Border(1)
.BorderColor(Colors.Grey.Lighten1)
.Background(backgroundColor)
.PaddingVertical(5)
.PaddingHorizontal(10)
.AlignCenter()
.AlignMiddle();
}
decoration
.Header()
.DefaultTextStyle(TextStyle.Default.SemiBold())
.Table(table =>
{
table.ColumnsDefinition(DefineTableColumns);
table.DefaultCellStyle(cell => DefineDefaultCellStyle(cell, Colors.Grey.Lighten3));
table.Cell().RowSpan(2).ExtendHorizontal().AlignLeft().Text("Document type");
table.Cell().RowSpan(2).Element(CellStyle).ExtendHorizontal().AlignLeft().Text("Document type");
table.Cell().ColumnSpan(2).Text("Inches");
table.Cell().ColumnSpan(2).Text("Points");
table.Cell().ColumnSpan(2).Element(CellStyle).Text("Inches");
table.Cell().ColumnSpan(2).Element(CellStyle).Text("Points");
table.Cell().Text("Width");
table.Cell().Text("Height");
table.Cell().Element(CellStyle).Text("Width");
table.Cell().Element(CellStyle).Text("Height");
table.Cell().Text("Width");
table.Cell().Text("Height");
table.Cell().Element(CellStyle).Text("Width");
table.Cell().Element(CellStyle).Text("Height");
// you can extend already existing styles by creating additional methods
IContainer CellStyle(IContainer container) => DefaultCellStyle(container, Colors.Grey.Lighten3);
});
decoration
@@ -357,19 +350,20 @@ namespace QuestPDF.Examples
.Table(table =>
{
table.ColumnsDefinition(DefineTableColumns);
table.DefaultCellStyle(cell => DefineDefaultCellStyle(cell, Colors.White));
foreach (var page in pageSizes)
{
table.Cell().ExtendHorizontal().AlignLeft().Text(page.name);
table.Cell().Element(CellStyle).ExtendHorizontal().AlignLeft().Text(page.name);
// inches
table.Cell().Text(page.width);
table.Cell().Text(page.height);
table.Cell().Element(CellStyle).Text(page.width);
table.Cell().Element(CellStyle).Text(page.height);
// points
table.Cell().Text(page.width * inchesToPoints);
table.Cell().Text(page.height * inchesToPoints);
table.Cell().Element(CellStyle).Text(page.width * inchesToPoints);
table.Cell().Element(CellStyle).Text(page.height * inchesToPoints);
IContainer CellStyle(IContainer container) => DefaultCellStyle(container, Colors.White);
}
});
@@ -383,18 +377,6 @@ namespace QuestPDF.Examples
columns.ConstantColumn(75);
columns.ConstantColumn(75);
}
IContainer DefineDefaultCellStyle(IContainer container, string backgroundColor)
{
return container
.Border(1)
.BorderColor(Colors.Grey.Lighten1)
.Background(backgroundColor)
.PaddingVertical(5)
.PaddingHorizontal(10)
.AlignCenter()
.AlignMiddle();
}
});
});
}
@@ -404,13 +386,13 @@ namespace QuestPDF.Examples
{
RenderingTest
.Create()
.ProduceImages()
.ProducePdf()
.PageSize(PageSizes.A4)
.MaxPages(10_000)
.EnableCaching()
.EnableDebugging(false)
.ShowResults()
.Render(container => GeneratePerformanceStructure(container, 1));
.Render(container => GeneratePerformanceStructure(container, 1000));
}
public static void GeneratePerformanceStructure(IContainer container, int repeats)
@@ -479,20 +461,17 @@ namespace QuestPDF.Examples
}
});
}
}
public static class TableTestsExtensions
{
public static void TextBox(this IContainer container, string text)
// this method uses a higher order function to define a custom and dynamic style
static IContainer Block(IContainer container)
{
container
return container
.Border(1)
.Background(Colors.Grey.Lighten3)
.MinWidth(50)
.MinHeight(50)
.AlignCenter()
.AlignMiddle()
.Text(text, TextStyle.Default.Size(16));
.AlignMiddle();
}
}
}
+5 -6
View File
@@ -173,7 +173,7 @@ namespace QuestPDF.Elements.Table
break;
foreach (var row in Enumerable.Range(cell.Row, cell.Row - currentRow))
rowBottomOffsets[row] = rowBottomOffsets[row - 1];
rowBottomOffsets[row] = Math.Max(rowBottomOffsets[row - 1], rowBottomOffsets[row]);
currentRow = cell.Row;
}
@@ -226,11 +226,8 @@ namespace QuestPDF.Elements.Table
var maxRow = commands.Select(x => x.Cell).Max(x => x.Row + x.RowSpan);
if (maxRow > currentRow)
{
foreach (var row in Enumerable.Range(currentRow + 1, maxRow - currentRow))
rowBottomOffsets[row] = rowBottomOffsets[row - 1];
}
foreach (var row in Enumerable.Range(CurrentRow, maxRow - CurrentRow))
rowBottomOffsets[row] = Math.Max(rowBottomOffsets[row - 1], rowBottomOffsets[row]);
AdjustCellSizes(commands, rowBottomOffsets);
@@ -246,7 +243,9 @@ namespace QuestPDF.Elements.Table
{
var lastRow = command.Cell.Row + command.Cell.RowSpan - 1;
var height = rowBottomOffsets[lastRow] - command.Offset.Y;
command.Size = new Size(command.Size.Width, height);
command.Offset = new Position(command.Offset.X, rowBottomOffsets[command.Cell.Row - 1]);
}
}
+2 -2
View File
@@ -2,10 +2,10 @@ namespace QuestPDF.Elements.Table
{
internal class TableCell : Container, ITableCellContainer
{
public int Row { get; set; } = 1;
public int Row { get; set; } = 0;
public int RowSpan { get; set; } = 1;
public int Column { get; set; } = 1;
public int Column { get; set; } = 0;
public int ColumnSpan { get; set; } = 1;
public bool IsRendered { get; set; }
+15 -1
View File
@@ -25,6 +25,8 @@ namespace QuestPDF.Elements.Table
.Where(x => x.Row + x.RowSpan > currentLocation.y)
.ToList();
}
SetPartialLocation(cell);
if (cell.HasLocation())
{
@@ -77,9 +79,21 @@ namespace QuestPDF.Elements.Table
return neighbours.Any(cell.CollidesWith);
}
private static void SetPartialLocation(this TableCell cell)
{
if (cell.Row == default && cell.Column == default)
return;
if (cell.Row == default)
cell.Row = 1;
if (cell.Column == default)
cell.Column = 1;
}
private static bool HasLocation(this TableCell cell)
{
return cell.Row != 1 || cell.Column != 1;
return cell.Row != 0 && cell.Column != 0;
}
}
}
@@ -15,13 +15,21 @@ namespace QuestPDF.Elements.Table
private static void ValidateCellPositions(int columnsCount, ICollection<TableCell> cells)
{
const string prefix = "Detected issue in table cells configuration.";
foreach (var cell in cells)
{
if (cell.Column < 1)
throw new DocumentComposeException($"{prefix} A cell column position should be greater or equal to 1. Got {cell.Column}.");
if (cell.Row < 1)
throw new DocumentComposeException($"{prefix} A cell row position should be greater or equal to 1. Got {cell.Row}.");
if (cell.Column > columnsCount)
throw new DocumentLayoutException($"Cell location is incorrect. Cell starts at column that does not exist. Cell details: {GetCellDetails(cell)}.");
throw new DocumentComposeException($"{prefix} Cell starts at column that does not exist. Cell details: {GetCellDetails(cell)}.");
if (cell.Column + cell.ColumnSpan - 1 > columnsCount)
throw new DocumentLayoutException($"Cell location is incorrect. Cell spans over columns that do not exist. Cell details: {GetCellDetails(cell)}.");
throw new DocumentComposeException($"{prefix} Table cell location is incorrect. Cell spans over columns that do not exist. Cell details: {GetCellDetails(cell)}.");
}
string GetCellDetails(TableCell cell)
+2 -19
View File
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Elements;
using QuestPDF.Elements.Table;
using QuestPDF.Infrastructure;
@@ -35,7 +36,6 @@ namespace QuestPDF.Fluent
public class TableDescriptor
{
private Table Table { get; }
private Func<IContainer, IContainer> DefaultCellStyleFunc { get; set; } = x => x;
internal TableDescriptor(Table table)
{
@@ -52,29 +52,13 @@ namespace QuestPDF.Fluent
{
Table.ExtendLastCellsToTableBottom = true;
}
public void DefaultCellStyle(Func<IContainer, IContainer> mapper)
{
DefaultCellStyleFunc = mapper;
}
public ITableCellContainer Cell()
{
var cell = new TableCell();
Table.Cells.Add(cell);
return cell;
}
internal void ApplyDefaultCellStyle()
{
foreach (var cell in Table.Cells)
{
var container = new Container();
DefaultCellStyleFunc(container).Element(cell.Child);
cell.Child = container;
}
}
}
public static class TableExtensions
@@ -85,7 +69,6 @@ namespace QuestPDF.Fluent
var descriptor = new TableDescriptor(table);
handler(descriptor);
descriptor.ApplyDefaultCellStyle();
table.PlanCellPositions();
table.ValidateCellPositions();