ScreenshotNeo

BlogHTML to image & PDF

How to Get Page Counts and Skip Headers or Footers on the Last Page with EvoPDF

Add current and total page numbers in EvoPDF, then replace or suppress headers and footers on the final page in Next and Classic.

By the ScreenshotNeo team1 October 20267 min read

Short answer: In EvoPDF Next, put {page_number} and {total_pages} in the HTML assigned to PdfHtmlHeader or PdfHtmlFooter. To hide or replace a footer only on the final page, use a page-specific HTML template and an OnPageRendering callback that checks whether placement.DocumentPageNumber == placement.DocumentPageCount. EvoPDF Classic uses different placeholders—&p; for the current page and &P; for the total—and controls visibility through its page-preparation event.

This distinction matters: Next and Classic do not use the same placeholder syntax or event model. Match the sample to the package generation installed in your project.

1. Add “Page X of Y” in EvoPDF Next

The current and total page values are substituted when EvoPDF renders the PDF header or footer. Put them directly in the HTML template.

Complete C# example

using EvoPdf;
using System;

class Program
{
    static void Main()
    {
        var converter = new HtmlToPdfConverter();

        converter.PdfHtmlFooter = @"
            <div style='width:100%; text-align:center; font:10pt Arial; color:#555;'>
                Page {page_number} of {total_pages}
            </div>";

        converter.PdfHtmlFooter.Height = 28;

        converter.ConvertUrl("https://example.com", "output.pdf");
    }
}

The same placeholders work in PdfHtmlHeader:

converter.PdfHtmlHeader = @"
    <div style='width:100%; text-align:right; font:9pt Arial;'>
        Page {page_number} of {total_pages}
    </div>";
converter.PdfHtmlHeader.Height = 24;

{page_number} is the current document page. {total_pages} is the document page count. These are EvoPDF Next converter header/footer placeholders documented in the EvoPDF support documentation.

For a final page with no footer, return false from the page-rendering callback when the document page number equals the document page count. For a different final-page footer, select a second template at that point.

using EvoPdf;
using System;

class Program
{
    static void Main()
    {
        var converter = new HtmlToPdfConverter();

        converter.PdfHtmlFooter = @"
            <div style='width:100%; text-align:center; font:10pt Arial;'>
                Page {page_number} of {total_pages}
            </div>";
        converter.PdfHtmlFooter.Height = 28;

        converter.OnPageRendering += (sender, placement) =>
        {
            bool isLastPage = placement.DocumentPageNumber == placement.DocumentPageCount;

            if (isLastPage)
            {
                // Returning false skips this header or footer on the final page.
                return false;
            }

            return true;
        };

        converter.ConvertUrl("https://example.com", "output.pdf");
    }
}

The exact callback signature can vary with the EvoPDF Next package version, so use the event declaration exposed by your installed assembly. The important condition is the comparison between DocumentPageNumber and DocumentPageCount.

var normalFooter = @"
    <div style='width:100%; text-align:center; font:10pt Arial;'>
        Page {page_number} of {total_pages}
    </div>";

var finalFooter = @"
    <div style='width:100%; text-align:center; font:9pt Arial; color:#666;'>
        End of report
    </div>";

converter.PdfHtmlFooter = normalFooter;
converter.PdfHtmlFooter.Height = 28;

converter.OnPageRendering += (sender, placement) =>
{
    if (placement.DocumentPageNumber == placement.DocumentPageCount)
    {
        // Select the final-page template using the page-specific API exposed by
        // your EvoPDF Next version.
        placement.PdfHtmlFooter = finalFooter;
        placement.PdfHtmlFooter.Height = 28;
    }

    return true;
};

EvoPDF’s Next migration guide shows this page-specific pattern for choosing a template when DocumentPageNumber == DocumentPageCount. Keep the reserved header/footer height consistent unless your version and template configuration explicitly support a different height. A different height can change the available content area and pagination.

Requirement Recommended EvoPDF mechanism
Page number on every page Next HTML in PdfHtmlHeader or PdfHtmlFooter with {page_number}.
Total page count Next HTML with {total_pages}.
No header/footer on the final page Page-rendering callback that returns false for the last page.
Different final-page footer Page-specific template selected by the same last-page condition.
Browser-mode header/footer Next browser templates using the pageNumber and totalPages HTML classes.
Classic converter &p;/&P; in EvoPdfTextElement plus the page-preparation event.

4. Next browser-mode templates

EvoPDF Next browser mode has a separate, lighter header/footer approach. In those templates, use HTML elements with the pageNumber and totalPages classes:

<div class="pageNumber"></div>
<span> of </span>
<div class="totalPages"></div>

Do not mix these browser-mode classes with the custom converter HTML placeholders without checking which rendering mode your code uses. The browser-mode behavior is described in EvoPDF’s Next browser header/footer documentation.

5. EvoPDF Classic syntax

If the project uses EvoPDF Classic, use its older placeholder and event model:

var footerText = new EvoPdf.PdfTextElement(
    0, 0,
    "Page &p; of &P;",
    new System.Drawing.Font("Arial", 9),
    System.Drawing.Color.Gray);

converter.PdfFooter.AddElement(footerText);

Classic documents use &p; for the current page and &P; for the total page count inside an EvoPdfTextElement. To change visibility by page, handle HtmlToPdfConverterPrepareRenderPdfPageEvent and inspect the page being prepared. This is separate from the Next OnPageRendering pattern. See the Classic header/footer documentation.

6. Do not confuse PDF footers with repeated table footers

An HTML table’s tfoot is not the same as EvoPDF’s converter footer. EvoPDF repeats table sections when their display style is table-header-group or table-footer-group. To stop repetition, set the section to table-row-group:

<style>
  table tfoot { display: table-row-group; }
</style>

Use a converter header/footer for page numbering and page-scoped visibility. Use table section styles for rows that belong to the table itself. The EvoPDF support page documents these behaviors.

7. Validation checklist

  1. Confirm whether the project references EvoPDF Next or Classic.
  2. Confirm whether the document is using converter HTML headers/footers or Next browser mode.
  3. Render a document that is one page, exactly two pages, and several pages.
  4. Check that the first page shows Page 1 of Y.
  5. Check that the final page has the intended absence or replacement footer.
  6. Inspect a document where content naturally flows onto a new page.
  7. Test long titles, tables, images, and large fonts because reserved header/footer height affects pagination.
  8. If several HTML documents are combined, verify which object owns the final document page count.

8. Troubleshooting

The placeholders appear as literal text

Cause: The template is being rendered as ordinary body HTML, or the Classic placeholders were used in a Next template (or vice versa). Fix: Put {page_number} and {total_pages} in Next PdfHtmlHeader/PdfHtmlFooter. Use &p; and &P; only with the Classic text-element mechanism.

Cause: The callback returns false unconditionally, or the page-number property is not read from the callback’s placement object. Fix: Return false only when placement.DocumentPageNumber == placement.DocumentPageCount; return true for all other pages.

Cause: The callback is attached to a different converter instance, or the code is using browser mode where converter footer visibility is not the active mechanism. Fix: Attach the event before conversion and verify the selected rendering mode and package generation.

The final page content moves unexpectedly

Cause: Header/footer height is reserved across pages, and the replacement template has a different height or margins. Fix: Keep heights and margins consistent, or validate the layout using the template and configuration recommended by the Next migration guide.

The total page count is wrong

Cause: The output is composed from multiple HTML inputs, or pagination changes after fonts, images, scripts, or margins finish loading. Fix: Validate the final combined document, wait for required content before conversion, and inspect the actual output page count rather than assuming each source HTML fragment has an independent count.

Cause: The table’s tfoot is styled as table-footer-group. Fix: Set it to table-row-group, or move the content into the converter-level footer if it is page furniture.

9. Performance, reliability, and cost considerations

  • Pagination is layout-dependent: changing footer height, fonts, margins, images, or delayed content can change page breaks and therefore the total page count.
  • Keep templates small: a simple HTML footer is easier to render consistently than one containing heavy scripts or remote assets.
  • Use representative fixtures: test short and long documents, tables spanning pages, and a final page with little remaining content.
  • Check version behavior: the official documentation describes Next and Classic separately; migration examples should be adapted to the API actually installed.
  • Budget conversion work: EvoPDF conversion cost and runtime depend on your application hosting and document workload. The supplied documentation does not provide a universal benchmark.

10. Or skip the browser setup

If the task is to capture a rendered web page as an image rather than produce a paginated PDF, ScreenshotNeo provides a one-call screenshot API. It can also return a PDF when you need a PDF capture workflow.

Use the same request from cURL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo API documentation for PDF and capture options. Cookie banners, newsletter popups, and chat widgets are removed before the shot; bot checks, blank pages, timeouts, failed loads, and cache hits are never billed. An MCP server lets AI agents take screenshots, and the free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

11. FAQ

Can CSS counters replace EvoPDF page placeholders?

CSS counters and converter-level PDF headers are separate mechanisms. Use the EvoPDF placeholders or browser-mode classes documented for your rendering mode when you need reliable current and total page values.

Yes. Use the same page-specific visibility mechanism, but test the condition against the first document page instead of the final page.

Does a repeated table header count as a PDF header?

No. A repeated thead belongs to the table layout. A PDF header belongs to the converter’s header configuration.

Which syntax should a new project use?

Use the Next syntax when the project uses EvoPDF Next. Reserve &p;, &P;, and the page-preparation event for Classic projects.