PDFsharp & MigraDoc Foundation

PDFsharp - A .NET library for processing PDF & MigraDoc Foundation - Creating documents on the fly
It is currently Tue Apr 23, 2024 4:34 pm

All times are UTC


Forum rules


Please read this before posting on this forum: Forum Rules



Post new topic Reply to topic  [ 14 posts ] 
Author Message
 Post subject: Migradoc Table help
PostPosted: Wed Jan 05, 2011 1:24 am 
Offline
Supporter
User avatar

Joined: Thu May 27, 2010 7:40 pm
Posts: 59
Location: New Hampshire, USA
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


Top
 Profile  
Reply with quote  
 Post subject: Re: Migradoc Table help
PostPosted: Thu Jan 06, 2011 6:41 pm 
Offline
Supporter
User avatar

Joined: Thu May 27, 2010 7:40 pm
Posts: 59
Location: New Hampshire, USA
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.


Top
 Profile  
Reply with quote  
 Post subject: Re: Migradoc Table help
PostPosted: Mon Jan 31, 2011 10:43 am 
Offline

Joined: Mon Jan 31, 2011 10:40 am
Posts: 2
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.


Top
 Profile  
Reply with quote  
 Post subject: Re: Migradoc Table help
PostPosted: Mon Feb 07, 2011 11:14 pm 
Offline
Supporter
User avatar

Joined: Thu May 27, 2010 7:40 pm
Posts: 59
Location: New Hampshire, USA
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);
                }
            }
        }


Top
 Profile  
Reply with quote  
 Post subject: Re: Migradoc Table help
PostPosted: Mon Feb 07, 2011 11:22 pm 
Offline
Supporter
User avatar

Joined: Thu May 27, 2010 7:40 pm
Posts: 59
Location: New Hampshire, USA
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


Top
 Profile  
Reply with quote  
 Post subject: Re: Migradoc Table help
PostPosted: Mon Feb 14, 2011 1:27 pm 
Offline

Joined: Mon Jan 31, 2011 10:40 am
Posts: 2
Thanks Jeff,

This is excellent!


Top
 Profile  
Reply with quote  
 Post subject: Re: Migradoc Table help
PostPosted: Thu Nov 22, 2012 4:24 pm 
Offline

Joined: Fri Oct 19, 2012 7:58 am
Posts: 8
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


Top
 Profile  
Reply with quote  
 Post subject: Re: Migradoc Table help
PostPosted: Mon Nov 26, 2012 1:21 pm 
Offline

Joined: Fri Oct 19, 2012 7:58 am
Posts: 8
Hi,

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


Top
 Profile  
Reply with quote  
 Post subject: Re: Migradoc Table help
PostPosted: Mon Nov 26, 2012 3:34 pm 
Offline
PDFsharp Guru
User avatar

Joined: Mon Oct 16, 2006 8:16 am
Posts: 3096
Location: Cologne, Germany
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.

_________________
Regards
Thomas Hoevel
PDFsharp Team


Top
 Profile  
Reply with quote  
 Post subject: Re: Migradoc Table help
PostPosted: Tue Nov 27, 2012 7:44 am 
Offline

Joined: Fri Oct 19, 2012 7:58 am
Posts: 8
Ok, I think I'll open a different thread where I can explain what I'm doing with MigraDoc


Top
 Profile  
Reply with quote  
 Post subject: Re: Migradoc Table help
PostPosted: Wed Jul 30, 2014 5:19 pm 
Offline

Joined: Wed Jul 30, 2014 5:16 pm
Posts: 1
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.


Top
 Profile  
Reply with quote  
 Post subject: Re: Migradoc Table help
PostPosted: Tue Dec 22, 2015 3:13 pm 
Offline

Joined: Tue Dec 22, 2015 3:06 pm
Posts: 1
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


Top
 Profile  
Reply with quote  
 Post subject: Re: Migradoc Table help
PostPosted: Mon Aug 22, 2016 7:04 pm 
Offline

Joined: Mon Oct 12, 2015 7:33 pm
Posts: 9
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.


Top
 Profile  
Reply with quote  
 Post subject: Re: Migradoc Table help
PostPosted: Mon Aug 22, 2016 7:14 pm 
Offline
PDFsharp Expert
User avatar

Joined: Wed Dec 09, 2009 8:59 am
Posts: 340
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.

_________________
Öhmesh Volta ("() => true")
PDFsharp Team Holiday Substitute


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 14 posts ] 

All times are UTC


Who is online

Users browsing this forum: Google [Bot] and 216 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Privacy Policy, Data Protection Declaration, Impressum
Powered by phpBB® Forum Software © phpBB Group