VincentDontKnow wrote:
Any hint is welcome.
Hello.
I wrote a fix for my project this weekend. Feel free to add my helper class to your project.
Code:
using System;
using System.Collections.Generic;
using MigraDoc.DocumentObjectModel;
using MigraDoc.DocumentObjectModel.Tables;
using MigraDoc.Rendering;
namespace YourProjectNamespace;
static class MigraDocHelper {
/// <summary>
/// Method for fixing the MergeDown bug.
/// The method overrides the RowHeightRule of the last row, increasing the row height by the missing height value.
/// </summary>
/// <remarks>
/// This fix internally clones the part of the table that contains the rows affected by the MergeDown.
/// So, if there is a Cell inside this part of the table that has a MergeDown that continues further than this part of table, then an exception will be thrown.
/// </remarks>
/// <param name="pageAndStylesConfig">Configure the document according to how your page is configured and what styles are used in the document.</param>
/// <param name="cell">Cell that has MergeDown</param>
public static void MergedCellHeightFix(Action<Document> pageAndStylesConfig, Cell cell)
{
if (cell.Table == null || cell.Row == null)
throw new Exception("Cell must be added to the table.");
Table table = cell.Table;
Row row = cell.Row;
//Most likely bug occurs if MergeDown greater then 1
if (cell.MergeDown > 1)
{
Table partiallyClonedTable = CreatePartiallyСlonedTable(table, row.Index, cell.MergeDown + 1);
Unit partiallyClonedTableHeight = CalculateTablesHeight(pageAndStylesConfig, [partiallyClonedTable], BordersRenderOption.WithoutTopAndBottom);
Unit cellHeight = CalculateCellHeight(pageAndStylesConfig, cell);
if (cellHeight > partiallyClonedTableHeight)
{
Unit diffHeight = cellHeight - partiallyClonedTableHeight;
Row lastRowOfMerge = table.Rows[row.Index + cell.MergeDown];
Unit lastRowOfMergeHeight = CalculateRowHeight(pageAndStylesConfig, lastRowOfMerge);
lastRowOfMerge.HeightRule = RowHeightRule.Exactly;
lastRowOfMerge.Height = Unit.FromPoint(lastRowOfMergeHeight.Point + diffHeight.Point);
}
}
}
/// <summary>
/// The method clones elements from Cell.Elements into the renderer to calculate the height.
/// </summary>
private static Unit CalculateCellHeight(Action<Document> pageAndStylesConfig, Cell cellFromInputTable)
{
Unit occupiedSpaceHeight = Unit.Zero;
var document = new Document();
var section = document.AddSection();
pageAndStylesConfig(document);
var table = new Table
{
Borders =
{
Width = 0
},
};
Column? columnFromInputTable = cellFromInputTable.Column;
if (columnFromInputTable == null) throw new Exception("Cell must be added to the table.");
Column column = table.AddColumn();
column.Width = columnFromInputTable.Width;
column.Format = columnFromInputTable.Format.Clone();
column.LeftPadding = columnFromInputTable.LeftPadding;
column.RightPadding = columnFromInputTable.RightPadding;
Row row = table.AddRow();
Cell cell = row.Cells[0];
foreach (var _element in cellFromInputTable.Elements)
{
switch (_element)
{
case MigraDoc.DocumentObjectModel.Paragraph _paragraph:
cell.Add(_paragraph.Clone());
break;
case MigraDoc.DocumentObjectModel.Shapes.Image _image:
cell.Add(_image.Clone());
break;
case MigraDoc.DocumentObjectModel.Shapes.Charts.Chart _chart:
cell.Add(_chart.Clone());
break;
case MigraDoc.DocumentObjectModel.Shapes.TextFrame _textFrame:
cell.Add(_textFrame.Clone());
break;
default:
throw new NotImplementedException();
}
}
section.Add(table);
Unit occupiedHeight = CalculateTablesHeightFromRender(document);
return occupiedHeight;
}
public enum BordersRenderOption
{
All,
WithoutTopAndBottom
}
private static Unit CalculateRowHeight(Action<Document> pageAndStylesConfig, Row row)
{
if (row.Table == null)
throw new Exception("Row must be added to the table.");
Table table = row.Table;
Table partiallyClonedTable = CreatePartiallyСlonedTable(table, row.Index, 1);
Unit partiallyClonedTableHeight = CalculateTablesHeight(pageAndStylesConfig, [partiallyClonedTable], BordersRenderOption.WithoutTopAndBottom);
return partiallyClonedTableHeight;
}
public static Unit CalculateTablesHeight(Action<Document> pageAndStylesConfig, IEnumerable<Table> tables, BordersRenderOption bordersRenderOption)
{
var document = new Document();
var section = document.AddSection();
pageAndStylesConfig(document);
foreach (var table in tables)
{
Table newTable = table.Clone();
if (bordersRenderOption == BordersRenderOption.WithoutTopAndBottom)
{
newTable.Borders.Top.Clear();
newTable.Borders.Bottom.Clear();
}
section.Add(newTable);
}
Unit occupiedHeight = CalculateTablesHeightFromRender(document);
return occupiedHeight;
}
private static Unit CalculateTablesHeightFromRender(Document document)
{
Unit occupiedHeight = Unit.Zero;
var renderer = new DocumentRenderer(document);
renderer.PrepareDocument();
DocumentObject[] documentobjects = renderer.GetDocumentObjectsFromPage(1);
RenderInfo[]? info = renderer.GetRenderInfoFromPage(1);
if (info == null) throw new NullReferenceException();
for (int i = 0; i < documentobjects.Length; i++)
{
if (documentobjects[i] is Table)
{
var tableinfo = (MigraDoc.Rendering.TableRenderInfo)info[i];
occupiedHeight += Unit.FromPoint(tableinfo.LayoutInfo.ContentArea.Height.Point);
}
}
return occupiedHeight;
}
private static Table CreatePartiallyСlonedTable(Table inputTable, int startingRowIndex, int numberOfRowsToClone)
{
Table outputTable = new Table();
outputTable.Columns = inputTable.Columns.Clone();
if (numberOfRowsToClone < 0)
throw new ArgumentOutOfRangeException("The number of rows to clone must be greater than 0.");
if (startingRowIndex < 0)
throw new ArgumentOutOfRangeException("The row index must not be negative.");
int currentRowIndex = startingRowIndex;
int remainingRowsNumber = numberOfRowsToClone;
while (remainingRowsNumber > 0)
{
var row = outputTable.AddRow();
row.Cells = inputTable.Rows[currentRowIndex].Cells.Clone();
remainingRowsNumber --;
currentRowIndex ++;
}
return outputTable;
}
}
Here is an example of usage:
Code:
// table creation code somewhere...
row3.Cells[1].MergeDown = 3;
// after all the table creation code
Cell cellB3 = row3.Cells[1];
MigraDocHelper.MergedCellHeightFix(document => PageAndStyleConfig(document), cellB3);
// Your document must be configured with exactly the same config that you pass to the fix.
private void PageAndStyleConfig(Document document)
{
var section = document.LastSection;
var pageSetup = new PageSetup
{
PageFormat = PageFormat.A4,
SectionStart = BreakType.BreakNextPage,
Orientation = Orientation.Landscape,
PageWidth = "29.7cm",
PageHeight = "21cm",
TopMargin = Unit.FromMillimeter(6),
BottomMargin = Unit.FromMillimeter(6),
LeftMargin = Unit.FromMillimeter(10),
RightMargin = Unit.FromMillimeter(10),
HeaderDistance = Unit.FromMillimeter(0),
FooterDistance = Unit.FromMillimeter(0),
OddAndEvenPagesHeaderFooter = false,
DifferentFirstPageHeaderFooter = true,
MirrorMargins = false,
HorizontalPageBreak = false,
StartingNumber = 1
};
section.PageSetup = pageSetup;
var style = document.Styles[StyleNames.Normal]!;
style.Font.Name = "Times New Roman";
style.Font.Size = 10;
style = document.Styles.AddStyle(MyCellStyle, StyleNames.Normal);
style.Font.Name = "Times New Roman";
style.Font.Size = 12;
style.ParagraphFormat.Alignment = ParagraphAlignment.Center;
}
I tested my fix on a table with only one affected cell. I can't guarantee that the fix will work with multiple affected cells. There's also a limitation: the cell we're fixing can't contain rows with other cells with MergeDown that extend beyond those rows.