Documentation page: PDF Header and Footer Generation. This page is also available as Markdown at pdf-header-and-footer-generation.md.

PDF Header and Footer Generation

PD4ML has long supported three levels of header/footer complexity: a plain text-only template, rich HTML-formatted content, and inline tags with fine-grained per-page-range scoping. This page first documents that original, pre-v4 approach -- built around a PD4PageMark object -- exactly as it has existed for a long time, then walks through how each of the same four patterns looks under the current v4 API, which replaces PD4PageMark with direct, string-based calls.

The legacy approach: PD4PageMark

1. Text-only headers/footers

The simplest form sets a page-number template plus a handful of presentation properties -- alignment, color, font size -- either as attributes on the <pd4ml:footer> JSP tag or as setters on a PD4PageMark object from the Java API:

<pd4ml:footer
    pageNumberTemplate="page $[page] of $[total]"
    titleAlignment="left"
    pageNumberAlignment="right"
    color="#008000"
    initialPageNumber="1"
    pagesToSkip="1"
    fontSize="14"
    areaHeight="18"/>
PD4PageMark footer = new PD4PageMark();
footer.setPageNumberTemplate("page $[page] of $[total]");
footer.setTitleAlignment(PD4PageMark.LEFT_ALIGN);
footer.setPageNumberAlignment(PD4PageMark.RIGHT_ALIGN);
footer.setColor(new Color(0x008000));
footer.setInitialPageNumber(1);
footer.setPagesToSkip(1);
footer.setFontSize(14);
footer.setAreaHeight(18);
pd4ml.setPageFooter(footer);

pagesToSkip excludes the given number of leading pages from getting a footer at all, and initialPageNumber lets the visible page count start somewhere other than 1 -- both useful for documents with an unnumbered cover page.

2. HTML-formatted headers/footers (PD4ML Pro)

For anything beyond plain text, the same tag/object pair instead takes a full HTML fragment. Setting areaHeight to -1 asks PD4ML to compute the reserved height automatically from the content, rather than specifying it up front:

<pd4ml:footer areaHeight="-1">
<font color="red"><i>page $[page] of $[total]</i></font>
</pd4ml:footer>
PD4PageMark footer = new PD4PageMark();
footer.setHtmlTemplate("<font color=\"red\"><i>page $[page] of $[total]</i></font>");
footer.setAreaHeight(-1);
pd4ml.setPageFooter(footer);

3. Inline headers/footers with scope control (PD4ML Pro)

Rather than going through the Java API or a single JSP tag at all, <pd4ml:page.footer> can be declared directly in the source HTML. Without a scope, it applies to every page; with one, it targets a specific page or range, and a later occurrence in the document overrides an earlier one from that point on:

<pd4ml:page.footer>
footer: $[page] of $[total]
</pd4ml:page.footer>
<pd4ml:page.footer scope="1">
first page footer: $[page] of $[total]<br> <img src="img1.gif">
</pd4ml:page.footer>
<pd4ml:page.footer scope="2+">
footer: $[page] of $[total]<br>
<img src="img2.gif">
</pd4ml:page.footer>

The example above defines one footer for the first page and a different one from the second page onward. scope also understands even, odd, and skiplast modifiers, combinable with explicit pages and ranges in one comma-separated expression:

scope="2,5-10,even,skiplast"

API-based per-page conditional content

For logic too dynamic to express as a static scope string, subclassing PD4PageMark and overriding getHtmlTemplate(int pageNumber) computes different markup for every page individually -- alternating left/right-aligned content by odd/even page, in this example:

PD4PageMark footer = new PD4PageMark() {
    public String getHtmlTemplate(int pageNumber) {
        if (pageNumber % 2 == 0) {
            return "<html><body>some left aligned stuff...";
        } else {
            return "<html><body>some right aligned stuff...";
        }
    }
};
pd4ml.setPageFooter(footer);

PD4PageMark exposes the same per-page override pattern for other properties too, when a single static value across the whole scope isn't enough:

PD4PageMark.getPageNumberTemplate(int pageNr);
PD4PageMark.getPageNumberAlignment(int pageNr);
PD4PageMark.getTitleTemplate(int pageNr);

See also this PDF header/footer forum discussion from PD4ML's own support archive for further worked examples of this API.

The current (v4) API

v4 removes the PD4PageMark object from this picture entirely. A header or footer is just an HTML string passed to setPageHeader(...)/setPageFooter(...), together with a pixel height and an optional scope string -- the same scope grammar used elsewhere in v4 for page size and margins (see the Programmer's Manual's §7, Page layout): a single page number, a range, "N+" for "this page onward", and modifiers including odd (the Programmer's Manual's own scope-grammar examples show odd; whether even and the legacy skiplast modifier specifically carry over is worth confirming against the current Javadoc before depending on them). The same content can equally be declared inline via <pd4ml:page.header>/<pd4ml:page.footer>, which take effect for the current and all following pages until superseded by a later occurrence -- a positional form of scoping, rather than an explicit scope attribute on the tag itself.

Text-only becomes plain HTML plus CSS

There's no dedicated "text-only" mode in v4 -- the individual PD4PageMark properties (titleAlignment, pageNumberAlignment, color, fontSize) are simply ordinary CSS on whatever HTML string is passed in, and pagesToSkip becomes a matter of choosing a scope that starts later rather than a dedicated property:

pd4ml.setPageFooter(
    "<div style='text-align:right; color:#008000; font-size:14pt'>page $[page] of $[total]</div>",
    18, "2+");   // "2+" skips the footer on page 1, in place of pagesToSkip="1"

initialPageNumber -- relabeling page 1 as some other starting number -- has no documented v4 equivalent as of this writing; a deployment relying on it should confirm current support before migrating.

The areaHeight="-1" auto-sizing behavior doesn't carry over -- a v4 footer's height is always given explicitly, as the second argument:

pd4ml.setPageFooter("<font color='red'><i>page $[page] of $[total]</i></font>", 24);

Inline, scoped headers/footers

<pd4ml:page.footer height="18">footer: $[page] of $[total]</pd4ml:page.footer>
<pd4ml:page.footer height="18">
first page footer: $[page] of $[total]<br><img src="img1.gif">
</pd4ml:page.footer>
<pd4ml:page.break>
<pd4ml:page.footer height="18">
footer: $[page] of $[total]<br><img src="img2.gif">
</pd4ml:page.footer>

Or, from the API, with an explicit scope string instead of relying on document position:

pd4ml.setPageFooter("first page footer: $[page] of $[total]", 18, "1");
pd4ml.setPageFooter("footer: $[page] of $[total]", 18, "2+");

Per-page conditional content, without a callback

Where the legacy API needed an anonymous PD4PageMark subclass overriding getHtmlTemplate(int pageNumber) to alternate content by page, v4's plain-string API reaches the same result with two scoped calls instead of a callback -- odd/even is exactly what the scope grammar's modifiers are for:

pd4ml.setPageFooter("<div style='text-align:left'>some left-aligned stuff...</div>", 20, "even");
pd4ml.setPageFooter("<div style='text-align:right'>some right-aligned stuff...</div>", 20, "odd");

See also

  • PD4ML Programmer's Manual -- §8 covers setPageHeader/setPageFooter, the built-in $[page]/$[total]/$[title] placeholders, and setDynamicData(...) for custom placeholders beyond those three.
  • PD4ML v3 to v4 Migration Guide -- §4 walks through the same PD4PageMark-to-string-API change side by side.