PDFsharp & MigraDoc Foundation
https://forum.pdfsharp.net/

Migradoc Table help
https://forum.pdfsharp.net/viewtopic.php?f=2&t=1492
Page 1 of 1

Author:  jeffhare [ Wed Jan 05, 2011 1:24 am ]
Post subject:  Migradoc Table help

Hello,

So, now I could really use a hand understanding some parts of MigraDoc... :roll:

The stock MigraDoc distribution also doesn't support directly adding a new Table as the content of an existing table Cell. I found that some minor changes made this not only possible but has it sizing and rendering very nicely. I'd be happy to share that if it helps anyone.

So I know MigraDoc out of the box isn't currently able to split a cell across pages. I'm working very hard to try to understand both the formatting and rendering passes that draw tables in PDF.

TableRenderer.Format(Area area, ...) is provided an area constraint to fit content into:
TableRenderer.InitFormat() didn't make any use of it however. (other formatters do use the area)

By sending that area down to TableRenderer.FormatCells(area), makes it possible to stop rendering the cell content based on the available area left on the page. Normally a cell was assumed to have infinite height.

In fact, this worked really well and tables displayed all the content that would fit and then stopped and drew the bottom cell border. Nice. Content clipped instead of running off the bottom of the page.

The real problem I'm having is trying to understand what would be necessary to effectively insert a new row into the table on the next page in order to continue formatting what didn't fit. It doesn't seem that hard to isolate the content that didn't fit in each cell, but I don't seem to be able to grasp how to get the formatter to page break and allow me to format a new row with content that didn't fit.

Could I add code similar to how Table headers are repeated on subsequent pages as the "continued row" for this row that didn't fit? I feel like I'm really close here but don't understand quite how the formatter triggers the start of a new page and stores the Infos.

When a row doesn't fit on the current page, it is nicely pushed to the next page. I'm missing how the "new page" part is accomplished such that the table continues formatting.

Does anyone have any tips or clarifications on the table cell formatting / new row process?

Thanks in advance!
-Jeff

Author:  jeffhare [ Thu Jan 06, 2011 6:41 pm ]
Post subject:  Re: Migradoc Table help

So I did find one less-than-perfect strategy that was easy to implement that seems to work when dealing with table cells (rows) that have content that would occupy more height than a single page.

This strategy is when storing the render infos and creating a new page, check to see if the InnerHeight of the content assigned to this page is taller than the page height. If so, increase the page height to accomodate. While this results in an occasional oddly tall page, at least no content is clipped.

Author:  SwiftNano [ Mon Jan 31, 2011 10:43 am ]
Post subject:  Re: Migradoc Table help

Hi Jeff,

I would very much like to be able to nest tables within table cells.

If you would be kind enough to share the changes to allow this I would be very grateful.

Thanks for your Help.

Author:  jeffhare [ Mon Feb 07, 2011 11:14 pm ]
Post subject:  Re: Migradoc Table help

Hello, (Sorry for the delay, didn't see this post).

I could share the changes, but I've made too many other customizations to just patch in. Instead, I think it's pretty easy to share how I did it. You will have to start modifying MigraDoc. There may be other ways to do this, but I couldn't seem to get any response to the problem, so I did the best I could. One of the changes was to make properties out of lots of member variables, so I'll try to help out with the tweaks if some of those found their way into this code.

In class: MigraDoc.DocumentObjectModel.Table.Cell
Add a new Cell method that permits adding a table. This makes it possible to say cell.AddTable( myNestedTable) to get the table added into the cell.
Code:
        public void AddTable(Table table)
        {
            Elements.Add(table);
        }

So, now we have the table in the Cell's elements collection.

See how that works... Before doing anything else.... However, you will see that tall cells will run off the bottom of the page and lose the content.

I added a CalcPageSize overload to the FormattedDocument class to handle the case where a single row is too tall to fit on a single page. This will resize that page height to ensure that the entire table row fits... Poor solution, but no content is lost anyway.
Code:
        XSize CalcPageSize(PageSetup pageSetup, ArrayList renderInfos)
        {
            double ContentHeight = 0.0;

            // renderInfos holds the objects being rendered on this page
            foreach (var renInfo in renderInfos)
            {
                // This computation only needs to be done for Tables where
                // only a single row is being rendered on *this* page.  This
                // is likely a very tall row. (rows don't span pages yet...)
                // If a row is too tall to fit on the remainder of the page it was
               //  pushed to the next page which might be what we have here.
                // It's also possible that this is the last row in the table and was
                // just pushed to a new page, or that we have a table with 1 row.
                // ------------------------------------------------------------------------------
                TableRenderInfo tri = renInfo as TableRenderInfo;
                if (tri != null)
                {
                    TableFormatInfo tfi = tri.FormatInfo as TableFormatInfo;

                    // Only rendering a single ROW here, make sure it fits on the page.
                    if (tfi != null && (int)tfi.ConnectedRowsMap[tfi.StartRow] == tfi.EndRow)
                    {
                        Dictionary<int, double> MCH = new Dictionary<int,double>();

                        foreach (FormattedCell cell in tfi.FormattedCells.Values)
                        {
                            if (cell != null)
                            {
                                int row = cell.Cell.Row.Index;
                                int col = cell.Cell.Column.Index;

                                // This indicates that we're only drawing a single row
                                if (row == tfi.StartRow && row == (int)tfi.ConnectedRowsMap[tfi.StartRow])
                                {
                                    //Debug.WriteLine("CRM["+ tfi.StartRow + "]="+(int)tfi.ConnectedRowsMap [tfi.StartRow]+"  Cell: " + row + "," + col + " -> h:" + cell.ContentHeight);
                                    double CH = cell.ContentHeight + pageSetup.TopMargin.Point + pageSetup.BottomMargin.Point;
                                    ContentHeight = Math.Max(CH, ContentHeight);
                                }
                                else if ((int)tfi.ConnectedRowsMap [tfi.StartRow] > tfi.StartRow)
                                {
                                    double v = 0.0;
                                    if (MCH.TryGetValue(col, out v))
                                    {
                                        MCH [col] += cell.ContentHeight;
                                    }
                                    else
                                    {
                                        MCH.Add(col,cell.ContentHeight);
                                    }
                                    //Debug.WriteLine("CRM["+ tfi.StartRow + "]="+(int)tfi.ConnectedRowsMap [tfi.StartRow]+"  Cell: " + row + "," + col + " -> h:" + cell.ContentHeight + " MCH: " + MCH [col]);
                                    ContentHeight = Math.Max(MCH[col] + pageSetup.TopMargin.Point + pageSetup.BottomMargin.Point, ContentHeight);
                                }
                                //else Debug.WriteLine("CRM["+ tfi.StartRow + "]="+(int)tfi.ConnectedRowsMap [tfi.StartRow]+"  Cell: " + row + "," + col + " -> h:" + cell.ContentHeight);
                            }
                        }
                    }
                }
            }

            XSize pgSize = new XSize(pageSetup.PageWidth.Point, Math.Max(pageSetup.PageHeight.Point, ContentHeight));

            return pgSize;
        }


The above is called in the IareaProvider.StoreRenderInfos method in the same class... I only needed to call this from this one spot.
Code:
        void IAreaProvider.StoreRenderInfos(ArrayList renderInfos)
        {
            pageRenderInfos.Add(CurrentPage, renderInfos);
            XSize pageSize = CalcPageSize(CurrentSection.PageSetup, renderInfos);   // <<<== note the renderinfos being passed.
            PageOrientation pageOrientation = CalcPageOrientation(CurrentSection.PageSetup);
            PageInfo pageInfo = new PageInfo(pageSize.Width, pageSize.Height, pageOrientation);
            PageInfos.Add(CurrentPage, pageInfo);
            PageFieldInfos.Add(CurrentPage, CurrentFieldInfos);
        }




Now, The following stuff I added while attempting to find a way to cause a row to split across pages. (didn't succeed here the way I wanted, since the rendering process for tables isn't done in a way I can understand. Just Can't wrap my head around it.). I made the following changes to help pass along the constraining area into the cell formatter in hopes of being able to constrain a table row to a page or inject another row on the following page if and continue what didn't fit.

In class: MigraDoc.Rendering.TableRenderer we need to make a few changes. First, FormatCells() needs to be handed it's constraining area.
Code:
        void FormatCells(Area constrainingArea)   // <<== added Area parameter.
        {
            // Added Cell Comparer here to allow traversing the
            // Formatted Cells in a natural order.
            formattedCells = new SortedList(new CellComparer());
            foreach (Cell cell in mergedCells)
            {
                /// Here we need to detect that all the cell content doesn't fit.
                FormattedCell formattedCell = new FormattedCell(cell, DocRenderer, mergedCells.GetEffectiveBorders(cell), FieldInfos, 0, 0) { ConstrainingArea = constrainingArea };
                formattedCell.Format(Gfx);
                try
                {
                    formattedCells.Add(cell, formattedCell);
                }
                catch (Exception)
                {
                    Debug.WriteLine("Tried to add same cell twice.");
                }
            }
        }


And in InitFormat, we need to actually USE the constraining area we're handed, by passing that along to FormatCells we edited above.
Code:
        /// <summary>
        /// InitFormat here didn't use the area it is passed. -JAH
        /// </summary>
        /// <param name="area"></param>
        /// <param name="previousFormatInfo"></param>
        void InitFormat(Area area, FormatInfo previousFormatInfo)
        {
            TableFormatInfo prevTableFormatInfo = (TableFormatInfo)previousFormatInfo;
            TableRenderInfo tblRenderInfo = new TableRenderInfo { TableObject = table };

            RenderInfo = tblRenderInfo;

            if (prevTableFormatInfo != null)
            {
                mergedCells = prevTableFormatInfo.MergedCells;
                formattedCells = prevTableFormatInfo.FormattedCells;
                bottomBorderMap = prevTableFormatInfo.BottomBorderMap;
                lastHeaderRow = prevTableFormatInfo.LastHeaderRow;
                connectedRowsMap = prevTableFormatInfo.ConnectedRowsMap;
                startRow = prevTableFormatInfo.EndRow + 1;
            }
            else
            {
                mergedCells = new MergedCellList(table);
                FormatCells(area);  // <<=== Pass along the constraining Area passed into this method.
                CalcLastHeaderRow();
                CreateConnectedRows();
                CreateBottomBorderMap();
                if (doHorizontalBreak)
                {
                    CalcLastHeaderColumn();
                    CreateConnectedColumns();
                }
                startRow = lastHeaderRow + 1;
            }
            ((TableFormatInfo)tblRenderInfo.FormatInfo).MergedCells = mergedCells;
            ((TableFormatInfo)tblRenderInfo.FormatInfo).FormattedCells = formattedCells;
            ((TableFormatInfo)tblRenderInfo.FormatInfo).BottomBorderMap = bottomBorderMap;
            ((TableFormatInfo)tblRenderInfo.FormatInfo).ConnectedRowsMap = connectedRowsMap;
            ((TableFormatInfo)tblRenderInfo.FormatInfo).LastHeaderRow = lastHeaderRow;
        }



In the FormattedCell class, I added the ConstrainingArea property and a new Enum to the RowHeightRule called RowHeightRule.Constrain.

Code:
        public Area ConstrainingArea { get; set; }  //<== in FormattedCell class


Code:
namespace MigraDoc.DocumentObjectModel.Tables
{
    /// <summary>
    /// Specifies the calculation rule of the row height.
    /// </summary>
    public enum RowHeightRule
    {
        AtLeast,  // Minimum row height
        Auto,     // Calculate row height
        Exactly,  // Specific Row Height
        Constrain // Fit within this area or split across pages **NEW**!!!
    }
}


I updated the FormattedCell.InnerHeight property as follows:
Code:
        internal XUnit InnerHeight
        {
            get
            {
                Row row = Cell.Row;
                XUnit verticalPadding = row.TopPadding.Point;
                verticalPadding += row.BottomPadding.Point;

                switch (row.HeightRule)
                {
                    case RowHeightRule.Exactly:
                        return row.Height.Point;

                    case RowHeightRule.Auto:
                        return verticalPadding + contentHeight;

                    case RowHeightRule.Constrain:  // <<==  Added this case...
                        return (contentHeight < 1) ? ConstrainingArea.Height : (XUnit)Math.Min(ConstrainingArea.Height, (verticalPadding + contentHeight));

                    case RowHeightRule.AtLeast:
                    default:
                        return Math.Max(row.Height, verticalPadding + contentHeight);
                }
            }
        }

Author:  jeffhare [ Mon Feb 07, 2011 11:22 pm ]
Post subject:  Re: Migradoc Table help

Part II.

Something I didn't mention above is that this assumes that you will SIZE your table column widths appropriately ahead of time. So, you have the parent table sized to the page width. If you want to nest a table within a cell, you will probably want to size the nested table to fit within it's parent cell width. This results in the best looking nested tables, and then the paragraph/content flowing works well.

I have tried this with very deeply nested tables and works fine, assuming your generation algorithm has the ability to pre-compute this.

If not, you may be able to take advantage of this with the last block of changes I listed in my previous post to proportionally scale a nested table's column widths to fit the available area.

Cheers, and hope this helps a bit...
-Jeff

Author:  SwiftNano [ Mon Feb 14, 2011 1:27 pm ]
Post subject:  Re: Migradoc Table help

Thanks Jeff,

This is excellent!

Author:  TNTPOP [ Thu Nov 22, 2012 4:24 pm ]
Post subject:  Re: Migradoc Table help

Hi,

I'm currently working with MigraDoc to build a template system and encounter the same "Cell Split" issue.
I'd like to know if you got any further in finding a solution to split cells across pages.

Just in case it can help, I built a library that takes takes a MigraDoc document format as a template and generates a PDF document including the data. I managed to go recursively into tables using the first row as a template for other rows but that is where I get the cell split issue.

Any help to get those cells split is welcome.

TNT

Author:  TNTPOP [ Mon Nov 26, 2012 1:21 pm ]
Post subject:  Re: Migradoc Table help

Hi,

Does anyone have a work-around for splitting rows on pages? Would it work with a row span (merge)?

Author:  Thomas Hoevel [ Mon Nov 26, 2012 3:34 pm ]
Post subject:  Re: Migradoc Table help

TNTPOP wrote:
Does anyone have a work-around for splitting rows on pages? Would it work with a row span (merge)?
Row span is the opposite of cell split.

A table that has rows which are higher than a full page looks ugly IMHO. Using wider columns or smaller fonts may allow to prevent this.
Maybe you can display you data without a table. Or split your data into multiple rows instead of a single row.
Or use a short text in the table and link to the full text later in the document.

Author:  TNTPOP [ Tue Nov 27, 2012 7:44 am ]
Post subject:  Re: Migradoc Table help

Ok, I think I'll open a different thread where I can explain what I'm doing with MigraDoc

Author:  stvoidmain [ Wed Jul 30, 2014 5:19 pm ]
Post subject:  Re: Migradoc Table help

Thanks Jeff, you idea gave me what i needed to get started on row splitting, while i agree that a "SIMPLE" row that don't fit its ugly, that is not the case when you use nested tables for layout wich is my case, in case someone else needs this, i made a repo in github http://github.com/stvoidmain/PDFsharp so others can benefit from it.

Author:  snali [ Tue Dec 22, 2015 3:13 pm ]
Post subject:  Re: Migradoc Table help

Hello Thomas Hoevel,

Are you going to integrate table row splitting functionality into new version of Migradoc and PDFSharp released? It would help us to have both row splitting functionality and other functionalities as part of new PDFsharp and migradoc release. Appreciate your response.

Thanks

Author:  njdotnetdev [ Mon Aug 22, 2016 7:04 pm ]
Post subject:  Re: Migradoc Table help

Hi,

Was there a change made for splitting a cell across multiple pages? I am also looking for nested tables to have charts and tables nested in cells of a table.

Thanks.

Author:  () => true [ Mon Aug 22, 2016 7:14 pm ]
Post subject:  Re: Migradoc Table help

With version 1.50 MigraDoc still does not split table cells.
So use tables in tables with care.
Maybe you can use a smart combination of MergeRight and MergeDown instead of nested tables.

Page 1 of 1 All times are UTC
Powered by phpBB® Forum Software © phpBB Group
https://www.phpbb.com/