Search code examples
c#itextitext7

How to get a long table drawed starting from the top of page using iText7 in C#?


PDF Page Example

The PDF is composed by serveral paragraphs added to the document, and there is a long table which need to set started from top of one page(not the first page) which means a paragraph will be splitted to two parts.

If the table is short(length not exceed the page height), it can be done gracefully by handling Page Event of start page, just added the table to the canvas with the fixed position and size, and set the document's top margin.

PdfDocumentEvent docEvent = (PdfDocumentEvent)currentEvent;
PdfDocument pdfDoc = docEvent.GetDocument();
PdfPage page = docEvent.GetPage();

var currentPageNumber = pdfDoc.GetPageNumber(page);

var addingTablesOfPage = addingTables.Where(t => t.PageNumber == currentPageNumber && t.Prepared).ToList();
if (addingTablesOfPage != null && addingTablesOfPage.Count > 0)
{
    PdfCanvas canvas = new PdfCanvas(page.NewContentStreamBefore(), page.GetResources(), pdfDoc);
    PageSize pageSize = pdfDoc.GetDefaultPageSize();

    var pageHeight = pageSize.GetHeight() - Constants.DocumentMargins[0] - Constants.DocumentMargins[2];
    var pageWidth = pageSize.GetWidth() - Constants.DocumentMargins[1] - Constants.DocumentMargins[3];

    var totalHeight = 0f;
    var alignRight = false;

    // add tables to the top of page
    foreach (var table in addingTablesOfPage.OrderBy(t => t.Type).ToList())
    {
        var tableWidth = 0f;

        float coordX = 0;
        tableWidth = pageWidth;
        coordX = pageSize.GetX() + doc.GetLeftMargin();
        totalHeight += table.TableHeight;
        float coordY = pageSize.GetTop() - Constants.DocumentMargins[0] - totalHeight;
        Rectangle rect = new Rectangle(coordX, coordY, tableWidth, table.TableHeight);

        var tableCanvas = new Canvas(canvas, rect);
        tableCanvas.Add(table.Table);
        tableCanvas.Close();
    }

    float topMargin = Constants.DocumentMargins[0] + totalHeight;

    doc.SetTopMargin(topMargin);
}
else
{
    doc.SetTopMargin(Constants.DocumentMargins[0]);
}

But here the table is too long which will be splitted to multi pages. As far as I known, Canvas class is primarily aimed at cases when you need to add elements to a specific predefined area on a page / XObject and it is not aimed at overflowing your content to next areas. So how can I achieve the behavior?

Thank you!


Solution

  • The description of what you are trying to achieve is quite vague (not clear if it's paragraph wrapping around table, or if it's a table in between paragraph; not clear if the table is bound to any text in paragraph or not etc etc), but my answer should guide you to the right direction.

    I will show you how to add the paragraph part that fits till the end of the current page to the document, then add your table spanning across more than one page, and then add the leftover part of your paragraph.

    First off, we need a custom renderer for our paragraph that will only render the part that currently fits in the page and will remember the rest to be added later.

    private static class CustomParagraphRenderer extends ParagraphRenderer {
        public CustomParagraphRenderer leftover;
        private CustomParagraphRenderer toDraw;
    
        public CustomParagraphRenderer(Paragraph modelElement) {
            super(modelElement);
        }
    
        @Override
        public LayoutResult layout(LayoutContext layoutContext) {
            LayoutResult result = super.layout(layoutContext);
            if (result.getStatus() == LayoutResult.PARTIAL) {
                // Expected result here is that paragraph splits across pages
                leftover = (CustomParagraphRenderer) result.getOverflowRenderer();
                toDraw = (CustomParagraphRenderer) result.getSplitRenderer();
                return new LayoutResult(LayoutResult.FULL, result.getSplitRenderer().getOccupiedArea(), null, null);
            }
            return result;
        }
    
        @Override
        public void draw(DrawContext drawContext) {
            if (toDraw != null) {
                toDraw.draw(drawContext);
            } else {
                super.draw(drawContext);
            }
        }
    
        @Override
        public IRenderer getNextRenderer() {
            return new CustomParagraphRenderer((Paragraph) modelElement);
        }
    }
    

    Now, we are adding some placeholder rectangle to the document just to occupy some space for the testing purposes, then we add our paragraph with our custom renderer (note that the expectation is that the paragraph will be rendered as much as possible on the current page, and the leftover part will be remembered), then we add our table spanning across multiple pages, and finally, we add our leftover paragraph part.

    PdfDocument pdfDocument = new PdfDocument(new PdfWriter(outFileName));
    Document document = new Document(pdfDocument);
    
    document.setFontSize(20);
    
    Div emptyArea = new Div().setHeight(400).setBackgroundColor(ColorConstants.RED);
    document.add(emptyArea);
    
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < 500; i++) {
        sb.append(i).append(" ");
    }
    Paragraph p = new Paragraph(sb.toString());
    p.setNextRenderer(new CustomParagraphRenderer(p).setParent(document.getRenderer()));
    CustomParagraphRenderer paragraphRenderer = (CustomParagraphRenderer) p.createRendererSubTree();
    // Add first part of the paragraph
    document.getRenderer().addChild(paragraphRenderer);
    
    Table table = new Table(3);
    for (int i = 0; i < 25; i++) {
        for (int j = 0; j < 3; j++) {
            table.addCell(new Cell().add(new Paragraph("Cell (" + i + ", " + j + ")")));
        }
    }
    // Add big table
    document.add(table);
    
    // Add leftover paragraph part
    document.getRenderer().addChild(paragraphRenderer.leftover.setParent(document.getRenderer()));
    
    document.close();
    

    Result looks as follows. Note that I deliberately added consequent numbers as paragraph content so that it's clear that it's a continuous paragraph that is interrupted with a table:

    result