BoarOfGold
BoarOfGold

Reputation: 53

Add a page number and text aligned as footer to every pdf page

I added a sample project to reproduce the problem to the GitHub repo, I hope this helps: Click to go to the Github repo

I need to add text and a number of page to a PDF, this PDF is from an invoice.

I already tried using events (but the examples are for java, and are incomplete, or I think they are), and it's not working, I'm getting the same error, and in that way text cannot be aligned.

As I said, I've trying to achieve this using a table and a canvas, the code works fine if the PDF has only one page.

But with more than one page I get this error:

This exception was originally thrown at this call stack:  KernelExtensions.Get<TKey,

TValue>(System.Collections.Generic.IDictionary, TKey) iText.Kernel.Pdf.PdfDictionary.Get(iText.Kernel.Pdf.PdfName, bool) iText.Kernel.Pdf.PdfDictionary.GetAsNumber(iText.Kernel.Pdf.PdfName) iText.Kernel.Pdf.PdfPage.GetRotation() iText.Kernel.Pdf.Canvas.PdfCanvas.PdfCanvas(iText.Kernel.Pdf.PdfPage) BoarGiveMeMoar.Invoices.InvoiceToPdf.AddFooter(iText.Layout.Document) in InvoiceToPdf.cs BoarGiveMeMoar.Invoices.InvoiceToPdf.FillPdf.AnonymousMethod__4() in InvoiceToPdf.cs System.Threading.Tasks.Task.InnerInvoke() in Task.cs System.Threading.Tasks.Task..cctor.AnonymousMethod__274_0(object) in Task.cs System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(System.Threading.Thread, System.Threading.ExecutionContext, System.Threading.ContextCallback, object) in ExecutionContext.cs ... [Call Stack Truncated]

This is my code, but I get the error from above:

private readonly int smallFontSize = 6;
private readonly int stdFontSize = 7;

public void FillPdf(string filename)
{
    using var pdfWriter = new PdfWriter(filename);
    using var pdfDoc = new PdfDocument(pdfWriter);
    using var doc = new Document(pdfDoc, PageSize.LETTER);

    doc.SetMargins(12, 12, 36, 12);

    AddData(doc);
    AddFooter(doc);

    doc.Close();
}

private void AddData(Document doc)
{
    Table table = new Table(UnitValue.CreatePercentArray(5)).UseAllAvailableWidth();
    PdfFont bold = PdfFontFactory.CreateFont(StandardFonts.HELVETICA_BOLD);


    for (int i = 0; i < 250; i++)
    {
        var cell = new Cell(1, 5)
            .Add(new Paragraph($"My Favorite animals are boars and hippos")
            .SetFontSize(stdFontSize).SetFont(bold));

        cell.SetBorder(Border.NO_BORDER);
        cell.SetPadding(0);
        table.AddCell(cell);
    }

    doc.Add(table);
}

private void AddFooter(Document doc)
{
    if (doc is null)
        return;

    Table table = new Table(UnitValue.CreatePercentArray(60)).UseAllAvailableWidth();

    int numberOfPages = doc.GetPdfDocument().GetNumberOfPages();
    for (int i = 1; i <= numberOfPages; i++)
    {
        PdfPage page = doc.GetPdfDocument().GetPage(i);
        PdfCanvas pdfCanvas = new PdfCanvas(page);
        Rectangle rectangle = new Rectangle(
            0,
            0,
            page.GetPageSize().GetWidth(),
            15);

        Canvas canvas = new Canvas(pdfCanvas, doc.GetPdfDocument(), rectangle);

        var cell = new Cell(1, 20).SetFontSize(smallFontSize);
        cell.SetBorder(Border.NO_BORDER);
        cell.SetPadding(0);                
        table.AddCell(cell);

        cell = new Cell(1, 20).Add(new Paragraph("This document is an invoice")
            .SetTextAlignment(TextAlignment.CENTER)).SetFontSize(smallFontSize);
        cell.SetBorder(Border.NO_BORDER);
        cell.SetPadding(0);                
        table.AddCell(cell);

        cell = new Cell(1, 10).SetFontSize(smallFontSize);
        cell.SetBorder(Border.NO_BORDER);
        cell.SetPadding(0);                
        table.AddCell(cell);

        cell = new Cell(1, 7)
            .Add(new Paragraph($"Page {string.Format(CultureInfo.InvariantCulture, "{0:#,0}", i)} of {string.Format(CultureInfo.InvariantCulture, "{0:#,0}", numberOfPages)}   ")
            .SetTextAlignment(TextAlignment.RIGHT)).SetFontSize(smallFontSize);
        cell.SetBorder(Border.NO_BORDER);
        cell.SetPadding(0);                
        table.AddCell(cell);

        cell = new Cell(1, 3).SetFontSize(smallFontSize);
        cell.SetBorder(Border.NO_BORDER);
        cell.SetPadding(0);                
        table.AddCell(cell);

        canvas.Add(table).SetFontSize(smallFontSize);
        canvas.Close();
    }

}

Example way to call the code:

new InvoiceToPdf()
            .FillPdf(@$"{Environment.GetFolderPath(Environment.SpecialFolder.Desktop)}\StackOverflow Invoice Test.pdf");

I get an error on this line that creates a new PdfCanvas

PdfCanvas pdfCanvas = new PdfCanvas(page);

So, anyone of you has a solution?

Upvotes: 2

Views: 673

Answers (1)

Alexey Subach
Alexey Subach

Reputation: 12312

By default to decrease memory consumption a bit the pages of the PDF document you are creating are flushed as they fill up.

It means that when the content is written e.g. on page 3 of the document, the content of the page 1 is already written to the disk and it's too late to add more content there.

One option is to first create a document and close it, then open it with reader and writer, i.e.: new PdfDocument(PdfReader, PdfWriter), create Document around PdfDocument again and append content to the document as you do now:

public void FillPdf(string filename)
{
    {
        using var pdfWriter = new PdfWriter(tempFilename);
        using var pdfDoc = new PdfDocument(pdfWriter);
        using var doc = new Document(pdfDoc, PageSize.LETTER);

        doc.SetMargins(12, 12, 36, 12);

        AddData(doc);
        doc.Close();
    }

    {
        using var pdfWriter = new PdfWriter(filename);
        using var pdfReader = new PdfReader(tempFilename);
        using var pdfDoc = new PdfDocument(pdfReader, pdfWriter);
        using var doc = new Document(pdfDoc, PageSize.LETTER);

        doc.SetMargins(12, 12, 36, 12);

        AddFooter(doc);
        doc.Close();
    }
}

Second option is not to flush the content as soon as possible and keep it in memory instead. You can do that by only making a slight modification in your code: pass third parameter to Document constructor

using var doc = new Document(pdfDoc, PageSize.LETTER, false);

Please keep in mind that second option might result in additional empty page being created at the end of your document in some corner cases (e.g. when you add such a large image to your document that it doesn't even fit into a full page), so use it carefully. It should not create any problems with you are dealing with regular simple content though

Upvotes: 1

Related Questions