Ronald van den Broek
Ronald van den Broek

Reputation: 13

Adding a link with iText7 to the header or footer of a PDF

I am currently trying to add a link to the header of footer of a pdf document however the library gives the following error System.IndexOutOfRangeException: 'Requested page number 0 is out of bounds.' when adding the link to the header using the IText7 library.

Adding the same object to the body of the page works fine. Surrounding the code with a try catch results in the following: Image of pdf output

I couldn't find any code examples online regarding this problem in IText7, the solutions in ITextSharp are not applicable anymore.

My question is how do I add a link to an external website to the header of the pdf? Is the current behavior a bug in the library or intended?

I am using the following code:

The main method, loading the html, initializing the document and adding the object to the header and the main page.

public void Convert()
{
    // Initialize template
    IList<IElement> templateElements = HtmlConverter.ConvertToElements(File.ReadAllText("FooterTest.html"));

    // Initialize document
    PdfWriter pdfWriter = new PdfWriter("Output.pdf");
    PdfDocument pdfDocument = new PdfDocument(pdfWriter);
    Document document = new Document(pdfDocument);
    document.SetTopMargin(100);

    // Adding the header object to the header and the main body
    pdfDocument.AddEventHandler(PdfDocumentEvent.START_PAGE, new PdfHeader((IBlockElement)templateElements[0], document));
    document.Add((IBlockElement)templateElements[0]);

    document.Close();
}

Event handler class responsible for adding the object to the header. The code gives the error mentioned above within the try-catch

public class PdfHeader : IEventHandler
{
    private readonly IBlockElement footer;
    private readonly Document doc;

    public PdfHeader(IBlockElement footer, Document doc)
    {
        this.doc = doc;
        this.footer = footer;
    }

    public void HandleEvent(Event headerEvent)
    {
        PdfDocumentEvent docEvent = (PdfDocumentEvent)headerEvent;
        PdfDocument pdf = docEvent.GetDocument();
        PdfPage page = docEvent.GetPage();
        Rectangle pageSize = page.GetPageSize();
        PdfCanvas pdfCanvas = new PdfCanvas(page.GetLastContentStream(), page.GetResources(), pdf);
        Rectangle rectangle = new Rectangle(
            pdf.GetDefaultPageSize().GetX() + doc.GetLeftMargin(),
            pdf.GetDefaultPageSize().GetTop() - 80,
            page.GetPageSize().GetWidth() - doc.GetLeftMargin() - doc.GetRightMargin(),
            50);

        //Below is the code where the error is produced.
        try
        {
            new Canvas(pdfCanvas, pdf, rectangle).Add(footer);

        }
        catch { }
    }
}

The html file containing the header object (FooterTest.html loaded in the Convert() method)

<html>
    <body>
        <table>
            <tr>
                <td>
                This is a some text not containing a link.
                </td>
            </tr>
            <tr>
                <td>
                This text contains a link to <a href="https://www.google.com">Google</a> to demonstrate the issue.
                </td>
            </tr>
        </table>
    </body>
</html>

This is my first question on stack overflow so any feedback on the question itself is also appreciated.

Upvotes: 1

Views: 2632

Answers (1)

Yulian Gaponenko
Yulian Gaponenko

Reputation: 588

What you've encountered is not entirely a bug, however iText should definitely fail more gracefully and explanatory in this case.

The problem here, is that for the Canvas class it's actually not known what is the page on which the drawing performs. In general case Canvas is just a high level wrapper for the content drawing operations, which can be placed on any content stream (e.g. in form XObject, page content stream etc). However the link is something that is defined specifically on a page level (via link annotation).

It's reasonable easy to work around the issue. I can suggest you two approaches.

First approach is to fix the issue by overriding the CanvasRenderer:

// set the custom renderer:
Canvas canvas = new Canvas(pdfCanvas, pdf, rectangle);
canvas.setRenderer(new PageCanvasRenderer(canvas, page));
canvas.add(footer);

...

private static class PageCanvasRenderer extends CanvasRenderer {
    private final PdfPage page;

    public PageCanvasRenderer(Canvas canvas, PdfPage page) {
        super(canvas);
        this.page = page;
    }

    @Override
    protected LayoutArea updateCurrentArea(LayoutResult overflowResult) {
        if (currentArea == null) {
            currentArea = new RootLayoutArea(canvas.getPdfDocument().getPageNumber(page), canvas.getRootArea().clone());
        }
        return currentArea;
    }
}

Second approach is to use Document instance instead of the Canvas. The Document works with the pages content always, so the explained issue doesn't exist here. You can use fixed positioning to place the content in PdfHeader:

Replace the

new Canvas(pdfCanvas, pdf, rectangle).Add(footer);

with

Document document = new Document(pdf);

Div canvas = new Div().setFixedPosition(pdf.getPageNumber(page), rectangle.getLeft(), rectangle.getBottom(), rectangle.getWidth());
canvas.add(footer);

document.add(canvas);
// Don't close document itself! It would close the PdfDocument!
document.getRenderer().close();

Upvotes: 2

Related Questions