Browse Source

Fix: row-span calculation logic in Table may cause infinite document generation

Marcin Ziąbek 6 days ago
parent
commit
612c62ac37
2 changed files with 113 additions and 6 deletions
  1. 101 0
      Source/QuestPDF.LayoutTests/TableTests.cs
  2. 12 6
      Source/QuestPDF/Elements/Table/Table.cs

+ 101 - 0
Source/QuestPDF.LayoutTests/TableTests.cs

@@ -0,0 +1,101 @@
+namespace QuestPDF.LayoutTests;
+
+public class TableTests
+{
+    [Test]
+    public void RowSpan_CornerCase1()
+    {
+        LayoutTest
+            .HavingSpaceOfSize(200, 400)
+            .ForContent(content =>
+            {
+                content
+                    .Table(table =>
+                    {
+                        table.ColumnsDefinition(columns =>
+                        {
+                            columns.RelativeColumn();
+                        });
+                        
+                        table.Cell()
+                            .RowSpan(2)
+                            .Mock("a")
+                            .SolidBlock(100, 100);
+                        
+                        table.Cell()
+                            .Mock("b")
+                            .SolidBlock(100, 350);
+                    });
+            })
+            .ExpectDrawResult(document =>
+            {
+                document
+                    .Page()
+                    .RequiredAreaSize(200, 100)
+                    .Content(page =>
+                    {
+                        page.Mock("a").Position(0, 0).Size(200, 100);
+                    });
+                
+                document
+                    .Page()
+                    .RequiredAreaSize(200, 350)
+                    .Content(page =>
+                    {
+                        page.Mock("b").Position(0, 0).Size(200, 350);
+                    });
+            });
+    }
+    
+    [Test]
+    public void RowSpan_CornerCase2()
+    {
+        LayoutTest
+            .HavingSpaceOfSize(200, 400)
+            .ForContent(content =>
+            {
+                content
+                    .Table(table =>
+                    {
+                        table.ColumnsDefinition(columns =>
+                        {
+                            columns.RelativeColumn();
+                            columns.RelativeColumn();
+                        });
+                        
+                        table.Cell()
+                            .Column(1)
+                            .Row(1)
+                            .RowSpan(3)
+                            .Mock("a")
+                            .SolidBlock(100, 100);
+                        
+                        table.Cell()
+                            .Column(2)
+                            .Row(2)
+                            .Mock("b")
+                            .ContinuousBlock(100, 600);
+                    });
+            })
+            .ExpectDrawResult(document =>
+            {
+                document
+                    .Page()
+                    .RequiredAreaSize(200, 400)
+                    .Content(page =>
+                    {
+                        page.Mock("a").Position(0, 0).Size(100, 400);
+                        page.Mock("b").Position(100, 0).Size(100, 400);
+                    });
+                
+                document
+                    .Page()
+                    .RequiredAreaSize(200, 200)
+                    .Content(page =>
+                    {
+                        page.Mock("a").Position(0, 0).Size(100, 200);
+                        page.Mock("b").Position(100, 0).Size(100, 200);
+                    });
+            });
+    }
+}

+ 12 - 6
Source/QuestPDF/Elements/Table/Table.cs

@@ -141,12 +141,18 @@ namespace QuestPDF.Elements.Table
 
         private int CalculateCurrentRow(ICollection<TableCellRenderingCommand> commands)
         {
-            return commands
-                .GroupBy(x => x.Cell.Row)
-                .Where(x => x.All(y => y.Cell.IsRendered || y.Measurement.Type is SpacePlanType.Empty or SpacePlanType.FullRender))
-                .Select(x => x.Key + 1)
-                .DefaultIfEmpty(CurrentRow)
-                .Max();
+            if (!commands.Any())
+                return CurrentRow;
+
+            var notRenderedCells = commands
+                .Where(x => !x.Cell.IsRendered)
+                .Where(x => x.Measurement.Type is SpacePlanType.Wrap or SpacePlanType.PartialRender)
+                .ToList();
+            
+            if (notRenderedCells.Any())
+                return notRenderedCells.Min(x => x.Cell.Row + x.Cell.RowSpan - 1);
+                
+            return commands.Max(x => x.Cell.Row + x.Cell.RowSpan - 1) + 1;
         }
         
         private void UpdateColumnsWidth(float availableWidth)