Adding "Page X of Y" footers to PDF documents seems like a straightforward task, yet iTextSharp developers routinely spend hours wrestling with the PdfPageEventHelper class just to display page numbers. The complexity stems from a fundamental problem: when iTextSharp renders the first page, it has no way of knowing how many total pages the document will contain.
The Problem
iTextSharp requires developers to implement a custom class extending PdfPageEventHelper to add headers and footers. For simple static text, this works reasonably well. However, the moment you need dynamic content like "Page 1 of 5", you encounter a chicken-and-egg problem: the total page count is only known after the entire document has been rendered.
The solution iTextSharp provides involves creating a PdfTemplate placeholder during document creation, writing it to each page's footer area, and then filling in the actual total page count in the OnCloseDocument event after the document finishes rendering. This requires understanding several interconnected concepts:
-
PdfContentBytefor direct content manipulation -
PdfTemplatefor deferred content rendering -
BaseFontconfiguration for text rendering - Coordinate calculations for positioning
- The subtle timing differences between
OnStartPage,OnEndPage, andOnCloseDocument
Error Messages and Symptoms
Developers commonly encounter these issues when implementing page footers:
DocumentException: The document is not open
IOException: The document has no pages
NullReferenceException: Object reference not set to an instance of an object
System.InvalidOperationException: The document is already closed
Stack traces frequently point to attempts to add content in the wrong event handler or to access the PdfWriter before the document is properly initialized.
Who Is Affected
This complexity affects any developer who needs to:
- Generate reports with page numbering
- Create invoices with footer information
- Produce legal documents requiring page counts
- Build batch processing systems that generate paginated PDFs
- Implement print-ready PDF export from web applications
The 88,000+ views on the original Stack Overflow question demonstrate the scale of developers encountering this issue across .NET Framework and .NET Core applications.
Evidence from the Developer Community
The difficulty of implementing page footers in iTextSharp has generated extensive discussion across developer forums over more than a decade.
Timeline
| Date | Event | Source |
|---|---|---|
| 2009-06-23 | Original question posted | Stack Overflow |
| 2009-2017 | Multiple answers and variations posted | Stack Overflow |
| 2017-09-15 | Last significant activity on thread | Stack Overflow |
| Ongoing | 88,647 views accumulated | Stack Overflow |
Community Reports
"Though I keep getting an exception on cb"
— Original poster, Stack Overflow, 2009
The accepted answer on Stack Overflow spans over 100 lines of code, implementing a TwoColumnHeaderFooter class with multiple properties and event handlers. Alternative solutions posted by other developers range from 50 to 200 lines, each with different trade-offs.
Forum threads on C# Corner, CodeProject, and Microsoft Q&A show developers asking the same question repeatedly, often with slightly different requirements that require re-implementing the page event handler from scratch.
Root Cause Analysis
The complexity arises from iTextSharp's low-level document model. Unlike libraries that abstract page rendering, iTextSharp exposes the PDF content stream directly. This provides flexibility but requires developers to understand PDF internals.
The page numbering problem specifically occurs because:
Streaming Architecture: iTextSharp writes pages to the output stream sequentially. Once a page is written, its content cannot be modified.
Unknown Total: The total page count depends on content flow, table sizes, and page breaks that are calculated during rendering.
Template Workaround: The
PdfTemplateobject provides a way to reserve space on each page and fill it later, but this requires coordinating multiple event handlers.Coordinate System: Developers must calculate exact X/Y coordinates for footer placement, accounting for page margins, font metrics, and text alignment.
The iText documentation explicitly states: "It is forbidden to add content to the document object in the onEndPage(). You should add your header and your footer in the onEndPage() method using PdfWriter, NOT document."
Attempted Workarounds
Workaround 1: The PdfTemplate Approach
The standard solution involves creating a template placeholder and filling it when the document closes.
Approach: Create a PdfTemplate in OnOpenDocument, add it to each page in OnEndPage, and populate the total in OnCloseDocument.
public class PageFooterHandler : PdfPageEventHelper
{
private PdfContentByte contentByte;
private PdfTemplate totalPagesTemplate;
private BaseFont baseFont;
public override void OnOpenDocument(PdfWriter writer, Document document)
{
base.OnOpenDocument(writer, document);
baseFont = BaseFont.CreateFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
contentByte = writer.DirectContent;
totalPagesTemplate = contentByte.CreateTemplate(50, 50);
}
public override void OnEndPage(PdfWriter writer, Document document)
{
base.OnEndPage(writer, document);
int currentPage = writer.PageNumber;
string pageText = "Page " + currentPage + " of ";
float textWidth = baseFont.GetWidthPoint(pageText, 8);
Rectangle pageSize = document.PageSize;
contentByte.BeginText();
contentByte.SetFontAndSize(baseFont, 8);
contentByte.SetTextMatrix(pageSize.GetLeft(40), pageSize.GetBottom(30));
contentByte.ShowText(pageText);
contentByte.EndText();
contentByte.AddTemplate(totalPagesTemplate, pageSize.GetLeft(40) + textWidth, pageSize.GetBottom(30));
}
public override void OnCloseDocument(PdfWriter writer, Document document)
{
base.OnCloseDocument(writer, document);
totalPagesTemplate.BeginText();
totalPagesTemplate.SetFontAndSize(baseFont, 8);
totalPagesTemplate.SetTextMatrix(0, 0);
// Subtract 1 because OnCloseDocument is called with page number incremented
totalPagesTemplate.ShowText((writer.PageNumber - 1).ToString());
totalPagesTemplate.EndText();
}
}
Usage:
Document document = new Document(PageSize.A4, 36, 36, 54, 54);
PdfWriter writer = PdfWriter.GetInstance(document, new FileStream("output.pdf", FileMode.Create));
writer.PageEvent = new PageFooterHandler();
document.Open();
// Add content...
document.Close();
Limitations:
- Requires approximately 60+ lines of boilerplate code
- Must manually calculate positions using coordinate system
- Font metrics must be considered for proper alignment
- Different page sizes require different calculations
- Centering text requires additional width calculations
- No built-in support for styling or HTML formatting
Workaround 2: Using PdfPTable for Footer Layout
Some developers use PdfPTable within the page event for more complex footer layouts.
Approach: Create a table in OnEndPage and use WriteSelectedRows to position it.
public override void OnEndPage(PdfWriter writer, Document document)
{
PdfPTable footerTable = new PdfPTable(3);
footerTable.TotalWidth = document.PageSize.Width - document.LeftMargin - document.RightMargin;
footerTable.DefaultCell.Border = Rectangle.NO_BORDER;
footerTable.AddCell(new Phrase("Left text", FontFactory.GetFont(FontFactory.HELVETICA, 8)));
footerTable.AddCell(new Phrase("Page " + writer.PageNumber, FontFactory.GetFont(FontFactory.HELVETICA, 8)));
footerTable.AddCell(new Phrase("Right text", FontFactory.GetFont(FontFactory.HELVETICA, 8)));
footerTable.WriteSelectedRows(0, -1, document.LeftMargin, document.BottomMargin - 10, writer.DirectContent);
}
Limitations:
- Still cannot display total page count without template workaround
- Additional complexity layered on top of existing complexity
- Performance overhead from creating table on every page
- Table cell alignment requires additional configuration
Workaround 3: Two-Pass Rendering
Some applications render the document twice: once to count pages, then again with the correct total.
Limitations:
- Doubles processing time
- Doubles memory usage
- Not practical for dynamic or large documents
- Requires identical rendering conditions between passes
A Different Approach: IronPDF
Rather than working around the limitations of a low-level PDF API, IronPDF provides a declarative approach to headers and footers. The library handles the complexity of page numbering internally, exposing simple placeholder tokens that get replaced during rendering.
Why IronPDF Handles This Differently
IronPDF uses a Chrome-based rendering engine that processes the entire document before finalizing output. This architecture allows the library to:
- Calculate total page count before writing any pages
- Replace placeholder tokens with actual values
- Support HTML and CSS styling in headers and footers
- Handle positioning automatically based on margins
The footer is defined as an HTML fragment with placeholder tokens. The rendering engine substitutes {page} and {total-pages} with the actual values during PDF generation.
Code Example
using IronPdf;
public class PdfFooterGenerator
{
public void GeneratePdfWithPageFooter()
{
// Initialize the Chrome-based PDF renderer
var renderer = new ChromePdfRenderer();
// Configure footer with page numbering using placeholder tokens
renderer.RenderingOptions.HtmlFooter = new HtmlHeaderFooter
{
HtmlFragment = "<div style='text-align: center; font-size: 10px; font-family: Arial, sans-serif;'>" +
"Page {page} of {total-pages}" +
"</div>",
MaxHeight = 20,
DrawDividerLine = false
};
// Set bottom margin to accommodate the footer
renderer.RenderingOptions.MarginBottom = 25;
// Render HTML content to PDF
string htmlContent = @"
<html>
<body>
<h1>Sample Report</h1>
<p>This document demonstrates automatic page numbering.</p>
<!-- Additional content that may span multiple pages -->
</body>
</html>";
var pdf = renderer.RenderHtmlAsPdf(htmlContent);
// Save the generated PDF
pdf.SaveAs("report-with-footer.pdf");
}
}
Key points about this code:
- The
{page}placeholder is replaced with the current page number - The
{total-pages}placeholder is replaced with the total page count - HTML and inline CSS control the footer appearance
- No event handlers, templates, or coordinate calculations required
- The
MaxHeightproperty reserves space for the footer content
Extended Example with Styled Footer
using IronPdf;
public class StyledFooterGenerator
{
public void GeneratePdfWithStyledFooter()
{
var renderer = new ChromePdfRenderer();
// Create a footer with company branding and page numbers
renderer.RenderingOptions.HtmlFooter = new HtmlHeaderFooter
{
HtmlFragment = @"
<div style='width: 100%; font-family: Arial, sans-serif; font-size: 9px;
display: flex; justify-content: space-between; padding: 0 20px;'>
<span style='color: #666;'>Confidential Document</span>
<span style='color: #333;'>Page {page} of {total-pages}</span>
<span style='color: #666;'>Generated: {date}</span>
</div>",
MaxHeight = 25,
DrawDividerLine = true
};
// Configure header as well
renderer.RenderingOptions.HtmlHeader = new HtmlHeaderFooter
{
HtmlFragment = @"
<div style='text-align: right; font-size: 8px; color: #999; padding-right: 20px;'>
{html-title}
</div>",
MaxHeight = 20
};
// Set margins to accommodate header and footer
renderer.RenderingOptions.MarginTop = 30;
renderer.RenderingOptions.MarginBottom = 30;
// Render content
var pdf = renderer.RenderHtmlAsPdf("<html><head><title>Quarterly Report</title></head><body>...</body></html>");
pdf.SaveAs("styled-report.pdf");
}
}
Available Placeholder Tokens
IronPDF supports these merge fields in headers and footers:
| Placeholder | Description |
|---|---|
{page} |
Current page number |
{total-pages} |
Total page count in document |
{date} |
Current date |
{time} |
Current time |
{url} |
Source URL (for URL-to-PDF conversion) |
{html-title} |
Title from the HTML <title> tag |
{pdf-title} |
PDF document title metadata |
API Reference
For more details on the methods and classes used:
Migration Considerations
Licensing
IronPDF is commercial software. A free trial is available for evaluation, and licenses are available for individual developers, teams, and enterprises. The licensing model differs from iTextSharp's AGPL license, which requires either open-sourcing your application or purchasing a commercial license.
API Differences
The migration from iTextSharp to IronPDF involves conceptual differences:
| iTextSharp | IronPDF |
|---|---|
| Programmatic document construction | HTML/CSS-based rendering |
PdfPageEventHelper for headers/footers |
HtmlHeaderFooter with placeholders |
| Manual coordinate positioning | Automatic layout with margins |
PdfPTable for complex layouts |
HTML tables with CSS |
BaseFont configuration |
Standard web fonts or embedded fonts |
For existing iTextSharp projects, migration involves translating document construction logic to HTML templates. Simple reports can often be converted quickly; complex programmatic layouts may require more significant restructuring.
What You Gain
- Significantly reduced code complexity for headers and footers
- HTML and CSS styling support
- Automatic page number calculation
- Cross-platform support (Windows, Linux, macOS, Docker)
- No dependency on system fonts or libraries like libgdiplus
What to Consider
- IronPDF requires learning HTML-to-PDF patterns if your team primarily used programmatic PDF construction
- Very low-level PDF manipulation (byte-level editing, form field creation from scratch) may require different approaches
- Existing iTextSharp utility libraries and extensions will need replacement or adaptation
Conclusion
The "Page X of Y" footer in iTextSharp requires implementing page event handlers, managing templates, and calculating coordinates manually. For developers spending hours on what should be a simple feature, IronPDF's placeholder-based approach offers a more straightforward alternative that reduces footer implementation from 60+ lines of event handler code to a single HTML fragment.
Jacob Mellor is CTO at Iron Software and the original developer of IronPDF.
References
- iTextSharp: Creating a footer Page # of #{:rel="nofollow"} - Original Stack Overflow question with 88,647 views
- Page events for headers and footers - iText 5 Documentation{:rel="nofollow"} - Official iText documentation on page events
- Adding page events to PdfWriter{:rel="nofollow"} - Detailed tutorial on PdfWriter page events
- Add page number in footer of pdf using iTextsharp{:rel="nofollow"} - Community tutorial with code examples
- How to get total page count on footer{:rel="nofollow"} - C# Corner forum discussion
For the latest IronPDF documentation and tutorials, visit ironpdf.com. The HTML Headers and Footers example provides additional code samples.
Top comments (0)