Running headers and footers

Running headers and footers are to the text that appears in the top and bottom margins of each page to help with navigation through the paginated document. They typically include information such as page numbers, chapter title, and such. The running headers and footers are formatted using tables. Each table is a single row with three cells, for left, center, and right aligned text.

Default headers and footers

The default content for running headers is as follows:

  • For double-sided output in a book, the center header is the current section name.

  • For single-sided output, or double-sided not in a book, the center header is the title of the element that starts the page sequence, which could be article, chapter or appendix title.

  • Title page headers are blank.

  • Headers on the first page of a page sequence are blank.

  • If the draft.mode parameter is set to yes, then the left and right headers show the word "Draft" in the appropriate language.

The default content for footers is as follows:

  • For double-sided output, the page number appears in the outside corner (right footer on right-hand pages, left footer on left-hand pages).

  • For single-sided output, the page number appears in the center footer.

  • Title page footers are blank.

Changing the header or footer text

The content for running headers is defined in a stylesheet template named header.content. Likewise, the footers are defined in a template named footer.content. You see the default versions in fo/pagesetup.xsl. To customize the headers and footers, you redefine these templates in your customization layer.

The header.content template is called three times for each page-sequence, once each for left, center, and right header cells. It should select and return the appropriate text to fill each cell. The same is true for the footer.content template.

The header and footer content is considered static-content in FO, which means it is repeated in the same place on each page in a page sequence. However, the text itself can vary to show the current section title or page number. The static content is declared at the beginning of each different page sequence that is started during the processing of the document. Within a page sequence, FO supports variations for where a given page-master appears in the sequence, such as for odd- or even-numbered pages.

Table 13.5. Header/footer content examples

XSL-FODescription
<fo:page-number/>Inserts the current page number.
<xsl:apply-templates select="." 
  mode="title.markup"/>
Inserts the title of the current chapter, appendix, or other component.
<xsl:apply-templates select="." 
  mode="titleabbrev.markup"/>
Inserts the titleabbrev of the current chapter, appendix, or other component, if it is available. Otherwise it inserts the regular title.
<xsl:apply-templates select="." 
  mode="object.title.markup"/>
Inserts the chapter title with chapter number label. Likewise for appendices.
<fo:retrieve-marker ... />Used to retrieve the current section name.
<xsl:apply-templates select="//corpauthor[1]"/>Inserts the value of the first corpauthor element found anywhere in the document.
<xsl:call-template name="datetime.format">
  <xsl:with-param ...
Inserts a date timestamp. Seethe section “Adding a date timestamp”.
<xsl:call-template name="draft.text"/>Inserts the Draft message if draft.mode is currently on.
<fo:external-graphic ... />Inserts a graphical image. See the section “Graphic in header or footer” for details.

The header.content or footer.content template is called with the element that starts the page sequence as the context node. This means you can select elements to appear in the content using XPaths relative to that element. An example below adds corpauthor to a header.

The header.content or footer.content template is also called with several parameters that you can use in your logic for deciding what should appear in each position. There are also global parameters that can be included in the decision making. The parameters each template is called with include the following:

pageclass

There is a specific pageclass value for each type of page design that might be needed. For example, an index might be two-column layout while the rest of the book is single column. Each pageclass has a set of FO simple-page-masters defined for it. The following pageclass values are available by default, but this list could be extended by adding custom page masters.

titlepage  Division title page, including set, book, part.
lot        Page with a list of titles, including book table of contents,
                list of figures, etc.
front      Front matter pages, including preface, dedication
body       Main content pages
back       Back matter pages, including appendix, glossary, etc.
index      Alphabetical book-style index
sequence

Within a pageclass, the sequence of pages can have different page designs. For example, the first page of sequence might omit the running header so it will not detract from the main title. The enumerated sequence values are:

first      First page of a page class.
odd        Odd-numbered pages in the page class.
even       Even-numbered pages.
blank      Blank page at end of sequence, to even out page count.

If the output format is single-sided, then odd and even pages should have the same design, and the blank page is not called upon.

position

The location of text cell within the header or footer. The values are:

left
center
right
gentext-key

Some pages need to have a title generated for them, such as Table of Contents or Index. Since these titles need to appear in the language of the document, they are designated using a gentext-key, such as TableofContents. These keys appear in the localization files such as common/en.xml to identify a particular generated text string.

<l:gentext key="TableofContents" 
           text="Table of Contents"/>

In addition to these passed parameters, you can use these global parameters to select header/footer content.

double.sided

Selects single- or double-sided page layout. Typically a double-sided layout uses mirrored page designs for odd- and even-numbered pages, while a single-sided layout has a single page design.

draft.mode

This parameter indicates whether the formatted document is to be marked as Draft. This condition might be marked in the header or footer. See the section “Draft mode” for a description of how draft mode is turned on.

A customized version of the header.content template is typically a big xsl:choose structure where each xsl:when states its conditions for choosing the text for a particular header or footer cell. It only has to cover the cells that might have content. Here is a complete example.

Example 13.13. Customized header.content template

<xsl:template name="header.content">  
  <xsl:param name="pageclass" select="''"/>
  <xsl:param name="sequence" select="''"/>
  <xsl:param name="position" select="''"/>
  <xsl:param name="gentext-key" select="''"/>

  <fo:block>  1
    <!-- sequence can be odd, even, first, blank -->
    <!-- position can be left, center, right -->
    <xsl:choose>

      <xsl:when test="$sequence = 'odd' and $position = 'left'">  2
        <fo:retrieve-marker retrieve-class-name="section.head.marker"  3
                            retrieve-position="first-including-carryover"
                            retrieve-boundary="page-sequence"/>
      </xsl:when>

      <xsl:when test="$sequence = 'odd' and $position = 'center'">
        <xsl:call-template name="draft.text"/>  4
      </xsl:when>

      <xsl:when test="$sequence = 'odd' and $position = 'right'">
        <fo:page-number/>  5
      </xsl:when>

      <xsl:when test="$sequence = 'even' and $position = 'left'">  
        <fo:page-number/>
      </xsl:when>

      <xsl:when test="$sequence = 'even' and $position = 'center'">
        <xsl:call-template name="draft.text"/>
      </xsl:when>

      <xsl:when test="$sequence = 'even' and $position = 'right'">
        <xsl:apply-templates select="." mode="titleabbrev.markup"/>  6
      </xsl:when>

      <xsl:when test="$sequence = 'first' and $position = 'left'"> 7
      </xsl:when>

      <xsl:when test="$sequence = 'first' and $position = 'right'">  
      </xsl:when>

      <xsl:when test="$sequence = 'first' and $position = 'center'"> 
        <xsl:value-of 
               select="ancestor-or-self::book/bookinfo/corpauthor"/> 8 
      </xsl:when>

      <xsl:when test="$sequence = 'blank' and $position = 'left'">
        <fo:page-number/>
      </xsl:when>

      <xsl:when test="$sequence = 'blank' and $position = 'center'">
        <xsl:text>This page intentionally left blank</xsl:text>  9
      </xsl:when>

      <xsl:when test="$sequence = 'blank' and $position = 'right'">
      </xsl:when>

    </xsl:choose>
  </fo:block>
</xsl:template>

1

Make sure any content is enclosed in an fo:block so it is valid XSL-FO output.

2

Each xsl:when statement sets conditions for a particular header cell location's content.

3

Sets an fo:retrieve-marker to retrieve the text of the first section title on the page. See the section “Running section titles” for more information.

4

This built-in template returns the appropriately translated “Draft” label, but only if the conditions for printing it have been met. See the section “Draft mode” for a description of how draft mode is turned on.

5

Prints the current page number in that header cell.

6

This applies templates to the current element using mode="titleabbrev.markup". The current element is the one that starts the page-sequence, which might be a chapter, appendix, or TOC. This mode generates a title from the element's titleabbrev if available, otherwise from its title. This feature lets the running header track the current chapter or appendix name. You can add a titleabbrev when a title is too long to comfortably fit in the running header. If you want the full title including its label such as Chapter 3, then use mode="object.title.markup" instead.

7

You can include empty clauses in case you want to add something later.

8

If this page sequence is part of a book that has a corpauthor element, then that element's text is printed.

9

This entry generates the phrase “This page intentionally left blank” for generated blank pages. These occur when a double-sided chapter ends on an odd page, which generates a blank even page after it. This phrase is in English only, however, so adding a gentext template for multiple languages would probably be more flexible. See the draft.text template in fo/pagesetup.xsl for an example of using a gentext template.


As you can see from the example, there is quite a bit of flexibility in what can appear in a given header or footer location. You can use static text, or dynamic content. You can also include graphics.

Keep in mind that the sequence name odd does not include the sequence first, even though the first page may be an odd page. If you specify something for odd pages, you may also want to specify it for the first page too, as for example:

<xsl:when test="$double.sided != 0 
                and ($sequence = 'odd' or $sequence = 'first') 
                and $position='right'">

Running section titles

It is a common feature to put the current section title in the running header or footer. This is possible with XSL-FO, using the fo:marker and fo:retrieve-marker elements. The stylesheet automatically inserts the fo:marker elements, and you specify the fo:retrieve-marker element in your header or footer content.

You can control which levels of sections are included in the running section titles. Often only the first or second levels are important enough to warrant inclusion. The stylesheet parameter marker.section.level sets the maximum section level to be used. The default value is 2, so only sections at levels 1 and 2 are considered for the running titles. Set the parameter to 1 if you only want top-level sections, or set it to a higher number to include more levels. When the stylesheet processes your document, it inserts the following hidden marker for each section that meets the parameter condition:

<fo:marker marker-class-name="section.head.marker">
  My section title
</fo:marker>

The content of the element is the title to be displayed, if it is selected. The marker name section.head.marker is the same for all section levels by default. If you want to display both the first and second section levels on the same page, you would need to customize the stylesheet to output different marker names for the different levels.

When the pages are being laid out by the FO processor, each hidden marker is placed on the page within its section block. A section's block starts with the section title and extends as far as the section's content goes. That may be over more than one page. Then when the header or footer includes a fo:retrieve-marker, the processor looks for markers with the matching name. Which one it selects depends on the attributes on the fo:retrieve-marker element. The following example shows the typical attributes for running section titles:

<fo:retrieve-marker 
      retrieve-class-name="section.head.marker"
      retrieve-position="first-including-carryover"
      retrieve-boundary="page-sequence"/>

The retrieve-class-name attribute identifies the group of markers to select from. The retrieve-position="first-including-carryover" attribute picks up the first matching marker on the current page, or from a section carried over from a previous page if there is no marker on the current page. The retrieve-boundary="page-sequence" attribute sets the boundary for selecting a previous marker as the current page-sequence, as opposed to the whole document. That prevents carryover from the previous chapter.

Graphic in header or footer

Your page design may call for putting a graphical image in headers or footers. The overall process is the same as for other header or footer content. You just add the appropriate XSL-FO markup in the right place in your customized header.content or footer.content template, which are described in the section “Changing the header or footer text”.

The following is an example of the XSL-FO markup as it might appear in the template:

...
<xsl:when test="$position = 'center'">
  <fo:external-graphic content-height="1.2cm">
    <xsl:attribute name="src">
      <xsl:call-template name="fo-external-image">
        <xsl:with-param name="filename" select="$header.image.filename"/>
      </xsl:call-template>
    </xsl:attribute>
  </fo:external-graphic>
</xsl:when>
...

The fo:external-graphic element is used in XSL-FO to specify a graphical file to include in your output. The content-height attribute sets the height, to make sure it will fit within the allocated height for the header or footer. See the section “Allocating height for headers and footers” for details on adjusting the height to fit the graphic.

The image filename itself is specified here using a parameter value $header.image.filename. This is not a DocBook XSL parameter, but one that you could add to your stylesheet. A parameter that is specified at the top of the stylesheet makes it easier to change the filename without searching through code in your customization. But why does it call a template named fo-external-image instead of just adding a literal src attribute? Because different XSL-FO processors handle slightly different syntax for the file reference. That template handles generating the right syntax for each processor.

Multi-line header or footer

Some page designs cram more information into the headers or footers by stacking it onto multiple lines. For example, you could have the chapter title on the first line and the running section title on the second. There are two approaches to achieving this effect, using multiple blocks or customizing the layout table.

If you just need to stack two pieces of information into one location in the header or footer, then using two blocks is the easiest customization. In the appropriate xsl:when clause in your customization of the header.content or footer.content template, use two fo:block elements. The following example uses two stacked blocks:

...
<xsl:when test="$sequence = 'odd' and $position = 'left'">
  <fo:block>
    <xsl:apply-templates select="." mode="titleabbrev.markup"/>
  </fo:block>
  <fo:block> 
    <fo:retrieve-marker retrieve-class-name="section.head.marker"  
                        retrieve-position="first-including-carryover"
                        retrieve-boundary="page-sequence"/>
  </fo:block>
</xsl:when>
...

In this example, the first block would contain the chapter title, and the second block would contain the section title.

If your header or footer design uses stacked information in more than one position, then you may want to use the second method, which is customizing the layout table. Using multiple rows in a layout table ensures that the different information will align vertically.

The header.table template in fo/pagesetup.xsl lays out the table rows and cells in the page header. Likewise the footer.table lays out the footer area. You can customize either template to write a table to display whatever combination of rows and cells and spans you want, expressed in XSL-FO table elements.

As you will see in the original template, each fo:table-cell must have a call to the header.content template, and it must communicate that cell's location in the table using the position param. The original left, center, and right values are just strings that are matched in the default header.content template. You can add your own position values like row1col2, as long as you match it with a selection in your customized header.content template.

Changing header or footer styles

You can change the font family, size, style, and other attributes of the headers or footers such as borders and background color by customizing the default attribute-sets. You can put FO properties into the header.content.properties attribute set to change the running header style, or into the footer.content.properties attribute-set to change the running footer style. For example:

<xsl:attribute-set name="header.content.properties">
  <xsl:attribute name="font-family">Helvetica</xsl:attribute>
  <xsl:attribute name="font-size">9pt</xsl:attribute>
</xsl:attribute-set>

The rule lines in the headers and footers can be changed or turned off. If you want to change the style of the header rule line, you can customize the following template that is found in fo/pagesetup.xsl:

<xsl:template name="head.sep.rule">
  <xsl:param name="pageclass"/>
  <xsl:param name="sequence"/>
  <xsl:param name="gentext-key"/>

  <xsl:if test="$header.rule != 0">
    <xsl:attribute name="border-bottom-width">0.5pt</xsl:attribute>
    <xsl:attribute name="border-bottom-style">solid</xsl:attribute>
    <xsl:attribute name="border-bottom-color">black</xsl:attribute>
  </xsl:if>
</xsl:template>

An identical template named foot.sep.rule lets you control footer rules. The template parameters let you customize which pages have rules or have different rule styles. These parameters are described in the section “Changing the header or footer text”. For example, if you wanted to turn off the header rule line for book title pages and the first page of chapters, you could use the following customization:

Example 13.14. Customizing header rule lines

<xsl:template name="head.sep.rule">
  <xsl:param name="pageclass"/>
  <xsl:param name="sequence"/>
  <xsl:param name="gentext-key"/>

  <xsl:if test="$header.rule != 0">
    <xsl:choose>
      <xsl:when test="$pageclass = 'titlepage'">
        <!-- off -->
      </xsl:when>
      <xsl:when test="$pageclass = 'body' and $sequence = 'first'">
        <!-- off -->
      </xsl:when>
      <xsl:otherwise>
        <xsl:attribute name="border-bottom-width">0.5pt</xsl:attribute>
        <xsl:attribute name="border-bottom-style">solid</xsl:attribute>
        <xsl:attribute name="border-bottom-color">black</xsl:attribute>
      </xsl:choose>
    </xsl:choose>
  </xsl:if>
</xsl:template>

If you want to turn off the header rule entirely, set the stylesheet parameter header.rule to zero. To turn off the footer rule, set footer.rule to zero.

You can use the header.table.properties attribute-set to apply properties to the table used to lay out the header content. Likewise there is a footer.table.properties attribute-set for footers (both becoming available in version 1.72 of the stylesheets). You can use these attribute-sets to draw a border or apply a background color. The following example does both:

<xsl:attribute-set name="header.table.properties">
  <xsl:attribute name="background-color">#DDDDDD</xsl:attribute>
  <xsl:attribute name="border">0.5pt solid black</xsl:attribute>
  <xsl:attribute name="padding-left">5pt</xsl:attribute>
  <xsl:attribute name="padding-right">5pt</xsl:attribute>
</xsl:attribute-set>

<xsl:param name="header.rule">0</xsl:param>

To improve the appearance, this example adds some padding to separate the text from the border, and turns off the normal header rule so it does not override the new border.

If you need more control, then you will need to customize the template files that create the tables that format the headers or footers. These templates are named header.table and footer.table, respectively, and they are located in fo/pagesetup.xsl. Customizing these templates gives you complete control over the handling of content in the headers and footers.

Allocating widths in the headers and footers

The header or footer is laid out using a single row three-cell table for the left, center, and right positions. Text in the left cell is left-aligned, text in the middle cell is centered, and text in the right cell is right-aligned. By default, all three cell positions are assigned the same width. But you may need to adjust the relative widths to fit your pattern of text. A page number requires very little space, while a long chapter title may need more than a third of the page width.

You can use the header.column.widths and footer.column.widths parameters to adjust the relative widths. Each parameter takes three numbers separated by white space. Each number represents its proportion of the sum of the three numbers. For example:

<xsl:param name="header.column.widths">1 2 1</xsl:param>

The first number represents the relative width of the left position, the second number the relative width of the middle position, and the third number the relative width of the right position. That is for single-sided output. For double-sided output, a mirrored design is assumed, so the first number represents the inside position, and the third number represents the outside position. So in this example, the middle position is twice as wide as either of the other two positions.

Here are some guidelines for setting these width values:

  • The numbers do not have to be integers, but there must be three of them, and they must not be negative.

  • Each column's relative width is taken as its value divided by the sum of the three numbers. So the left position in the above example is 1 divided by 4, or 25% of the available width.

  • If you use the middle position and want to keep it centered, then the first and third numbers must be equal to each other. Otherwise the middle position will not be centered (which may be ok in your design).

  • You can set any of the numbers to zero, and the stylesheet will then allocate the space among the remaining numbers. For example, using 1 0 3 means the left postion is 25% and the right position is 75%. Using 0 0 1 means the right position is 100%. Just be sure your header.content template does not attempt to put content into a zero-width position, or you will likely generate errors.

  • If some content is longer than the allocated width, then the text will try to wrap to a second line within the cell. If the cell height is not tall enough, you may lose part of your text. See the section “Allocating height for headers and footers” to fix that.

If you find the table layout used in headers and footer to be too restrictive, you can always replace the header or footer table with your own XSL-FO layout. You'll need to customize the template named header.table or footer.table from fo/pagesetup.xsl. You can keep the same template name and template parameters, but you can customize how the information is used within the header or footer space.

Allocating height for headers and footers

If you plan to put more in the headers or footers than just short bits of text, then you need to provide sufficient space. The header text is positioned within the top margin using three parameters, as described in the section “Top and bottom margins”, with similar parameters for the footer text in the bottom margin.

The parameter that sets the height of the header area is region.before.extent. It gets that name from the XSL-FO standard (enough said). The default height is 0.4 inches, which is about 29 points. Since the default line-height is 12 points, there is room for two lines of text. That's helpful when you carry running titles in the header, because some titles may be long enough to wrap to a second line.

If you increase the header font size and line height by setting attributes in the header.content.properties attribute-set, you may also need to increase the region.before.extent parameter. Remember that the header area has to fit within the top margin area, though, so you may also need to increase those two margin parameters as well, to give it more space and to adjust its position. If you are changing the footer size, then it is the region.after.extent and the bottom margin parameters that would need adjustment.

If you add a graphic to the header or footer content, be sure its content-height property fits within the appropriate region extent.

Page numbering style

The DocBook XSL print stylesheet provides a pair of named templates, initial.page.number and format.page.number, that give you complete control over the page numbering style in your document. The default numbering style for a book uses lowercase roman numerals (i, ii, iii, etc.) for the front matter, and then restarts with arabic numeral 1 on the first chapter. But you may want a style that numbers consecutively in arabic numerals without restarting, or that starts with page 1 in the table of contents. instead of the title page.

Initial page number

Customize the initial.page.number template to change which parts of your document restart the numbering sequence. That template is called at the start of each page-sequence, which is the only place in XSL-FO where you can change the starting page number. The template works through several xsl:choose statements and returns a single value for the initial-page-number property that is added to the fo:page-sequence output element. If the template returns a literal number such as 1, then the calling page sequence will restart page numbering with that number. If the template returns auto, then the calling page sequence continues the page numbering from the previous page-sequence. If the template returns auto-odd, then the calling page sequence continues the page numbering but forces the new page-sequence to start on an odd numbered page (the processor will generate a blank page if needed).

Here is the first part of the original template, which resides in fo/pagesetup.xsl:

<xsl:template name="initial.page.number">
  <xsl:param name="element" select="local-name(.)"/>  1
  <xsl:param name="master-reference" select="''"/>  2

  <xsl:choose>
    <!-- double-sided output -->
    <xsl:when test="$double.sided != 0">  3
      <xsl:choose>
        <xsl:when test="$element = 'toc'">auto-odd</xsl:when>  4
        <xsl:when test="$element = 'book'">1</xsl:when>  5
        <xsl:when test="$element = 'part' and not(preceding::chapter)  
                        and not(preceding::part)">1</xsl:when>  6
        ...

1

The $element parameter that is passed to the template has the name of the element that starts this page sequence, such as book, chapter, appendix, etc.

2

The $master-reference parameter that is passed to the template has the name of the page-master, such as titlepage, lot, front, body, back, or index.

3

The first xsl:choose statement determines whether the document is being formatted for double-sided or single-sided output. Generally a value of auto is used for single-sided output, which gets the next consecutive page number (odd or even). A value of auto-odd is used for double-sided output to force each page-sequence to start on a right-hand (odd) page.

4

The table of contents (toc) continues the current page numbering without restarting. The auto-odd value forces it to start on an odd-numbered page. Although you may not have a literal toc element in your book, the stylesheet calls the template with that as the element name for table of contents page sequence.

5

When the element is a book, the starting page number is set to 1.

6

When the element is a part, the stylesheet has to determine if this is the first part element in the book. If so, the starting page number is 1. If it is not the first part, then the selection falls through to the xsl:otherwise statement that selects a value of auto-odd. The selection process for chapters has a similar set of conditions, so that only the first chapter restarts page numbering.

As you can see, the template provides complete control over when page numbering restarts. You can copy the template to your customization layer and change whatever parts you like.

Page number format

If you change the page numbering sequence, you may also want to change the page number format used for different page sequences. Customize the page.number.format template from fo/pagesetup.xsl to do that. It returns a value, either i or 1 that is used in the format property of the page-sequence element. Here is the original template:

<xsl:template name="page.number.format">
  <xsl:param name="element" select="local-name(.)"/>
  <xsl:param name="master-reference" select="''"/>

  <xsl:choose>
    <xsl:when test="$element = 'toc' and self::book">i</xsl:when>
    <xsl:when test="$element = 'preface'">i</xsl:when>
    <xsl:when test="$element = 'dedication'">i</xsl:when>
    <xsl:otherwise>1</xsl:otherwise>
  </xsl:choose>
</xsl:template>

As you can see, the front matter page sequences use the lowercase roman numeral by default, while all other page sequences use arabic numerals.

If you need Arabic-Indic page numbering, then use &#x661;, which is the number 1 in Arabic-Indic numbers. That tells the XSLT processor to use that sequence of Unicode numbers instead of the sequence starting with "1".

Consecutive page numbering

Most documents use consecutive page numbering from start to finish. However, a book document numbers the front matter with lowercase roman numerals, and then restarts numbering at 1 with arabic numerals in the first chapter. If you want a consecutive page numbering style throughout a book, then the customizations of the two page numbering templates are amazingly simple. Just add these two one-line templates to your customization layer. Then all your pages will be numbered consecutively with arabic numerals:

<xsl:template name="initial.page.number">auto-odd</xsl:template>
<xsl:template name="page.number.format">1</xsl:template>

These templates always return the same value, regardless of which page-sequence calls them. The first page sequence will start with 1 even when the value is auto-odd because the default value for the first page sequence is 1.

Page x of y numbering

Some document styles call for page numbering that indicates the total number of pages in the document, such as Page 3 of 38. If you are using the XEP XSL-FO processor, you can use its extension element to achieve this effect (see the section “XEP last page extension”). Otherwise, use the information in this section.

This numbering style works best when the pages are numbered consecutively through the document. If you use roman numerals in your front matter, for example, you would see a page number like iii of 38, which looks a bit odd. If you restart page numbering after the roman numerals, then you will also have a page 3 of 38. See the section “Consecutive page numbering” to avoid these problems.

There is no stylesheet parameter for this page numbering style, but you can implement it with a customization that has two parts.

  • Output an empty fo:block with an id at the end of your document.

  • In your page footer or header, create a reference to this empty block and then the regular page number.

The first part is the hardest. It requires customizing the template for the last page sequence in the document. But several elements can appear last, so this solution works best when all the documents using the stylesheet end with the same element, such as index. If that is the case, then copy the template that matches on book/index to your customization and make the following customization:

<xsl:template match="book/index|part/index">
  ...
      <fo:block id="END-OF-DOCUMENT"/>
    </fo:flow>
  </fo:page-sequence>
 </xsl:if>
</xsl:template>

This will output the empty block marker at the end of the index page sequence. Depending on the location of your page numbers, you then customize the template named footer.content or header.content as described further in the section “Changing the header or footer text”. Make the following changes:

...
<xsl:when test="$double.sided = 0 and $position='center'">
  <xsl:text>Page </xsl:text>  
  <fo:page-number/>
  <xsl:text> of </xsl:text>
  <fo:page-number-citation ref-id="END-OF-DOCUMENT"/>
</xsl:when>
...

This outputs the word Page followed by the current page number, followed by the word of followed by the page number reference to the empty block.

Note

This page numbering style does not work as well for double-sided output. In double-sided output, if the last page ends on an odd page, then a blank page is automatically generated to end on an even page. Unfortunately, there is no way to get the empty marker block onto that even-numbered blank page. So the last page reference is to the last page that has content, rather than the last page in the document. You might want to turn off the footer for generated blank pages by setting the stylesheet parameter footers.on.blank.pages to zero. Likewise for headers if your page numbers are in your header.

With some documents you could add a processing instruction at the end of the document and add a stylesheet customization to plant the empty block marker. You would put the processing instruction just before the closing tag of the last element that creates an fo:page-sequence. For example, if you have a book comprised of chapters, then you would place the processing instruction just before the closing tag of the last chapter. Putting it before the closing tag of the book element will not work, because that will place an fo:block outside of any fo:page-sequence, which will cause the XSL-FO processor to fail. This method also will not work if the last element's content is generated (such as index), or if the last element's template selects only certain children to process (such as glossary) instead of doing a general xsl:apply-templates.

XEP last page extension

The XEP XSL-FO processor has an extension to enable a reference to the last page number. With this extension, you do not have to force a marker block at the end of the content, and it will print the last page's number even when it is a generated blank page in double-sided output.

You put the extension element rx:page-number-citation-last where you want the last page number to be displayed in the header or footer. Like the fo:page-number-citation element, it requires a ref-id attribute, in this case to point to the fo:root element in the FO output. However, the DocBook stylesheet does not add an id to that element by default. So here are the steps to using this extension:

  1. Add the RenderX extension namespace to your customization layer's root element:

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                     xmlns:rx="http://www.renderx.com/XSL/Extensions"
    
  2. Add an id attribute of your choosing to the attribute-set named root.properties. The following example will add id="fo_root_element_id" to the fo:root element in the output.

    <xsl:attribute-set name="root.properties">
      <xsl:attribute name="id">fo_root_element_id</xsl:attribute>
    </xsl:attribute-set>
    
  3. Place the XEP extension element in your customization of the header.content or footer.content template where your page numbers appear, and use the ref-id attribute to reference the root id value you just specified.

    ...
    <xsl:when test="$double.sided = 0 and $position='center'">
      <xsl:text>Page </xsl:text>  
      <fo:page-number/>
      <xsl:text> of </xsl:text>
      <rx:last-page-number-citation ref-id="fo_root_element_id"/>
    </xsl:when>
    ...

Page number prefix

Some styles call for putting a chapter number prefix on page numbers and restarting the page count in each chapter. So pages in chapter one would be numbered 1-1, 1-2, 1-3, etc., and those in chapter two would be numbered 2-1, 2-2, 2-3, etc.

Currently there is no parameter in the stylesheets that turns on this style of page numbering. However, Jeff Beal has kindly made available a stylesheet customization for Page Number Prefixes that you can download from the DocBook Wiki site. His customization lets you add a chapter prefix to page numbers that print on the pages as well as in the TOC, index, and cross references.

Index entries may present problems for chapter-page numbering style. If you are using XEP and its index page numbering extensions to create page ranges, you will find that the chapter prefix is lost in the index (which renders the index useless). That's because the XEP index extensions handle the page number references after the stylesheet has completed its work, and the XEP processor is not aware of the page number prefix.

XSL Formatter from Antenna House has an extension attribute for page number prefixes. If you set the axf:page-number-prefix attribute to a string value, that string is appended to all instances of generated page numbers, including index page references. You generally add this property to each fo:page-sequence, with a string value appropriate to that page sequence. There is no stylesheet parameter that turns this feature on, so a customization is required. One way is to customize the template for mode="running.head.mode". That template is called at the beginning of each page sequence before any there is any output in the page sequence. You can customize the template to output this attribute, with a value created by a custom template mode that generates the prefix text for each chapter, appendix, etc.

Add extension property to each fo:page-sequence element:
<xsl:template match="*" mode="running.head.mode">
  <xsl:param name="master-reference" select="'unknown'"/>
  <xsl:param name="gentext-key" select="name(.)"/>

  <xsl:if test="$axf.extensions != 0">
    <xsl:attribute name="axf:page-number-prefix">
      <xsl:apply-templates select="." mode="page-number-prefix"/>
    </xsl:attribute>
  </xsl:if>
  ...
</xsl:template>

Example custom template to generate the prefix text:
<!-- Also match on elements inside a chapter for index and xref targets -->
<xsl:template match="chapter|chapter//*" mode="page-number-prefix">
  <xsl:if test="$page.number.prefixes != 0">
    <xsl:number count="chapter" from="book" level="any"/>
    <xsl:value-of select="$page.number.prefix.separator"/>
  </xsl:if>
</xsl:template>

Restart page numbering in each page-sequence:
<xsl:template name="initial.page.number">1</xsl:template>

Ending page number

If you use double-sided output, you have the choice of whether to end the document on an even-numbered page. That is, if the last bit of content lands on an odd-numbered page, should a blank even page be generated? If you are primarily interested in preparing a document to hand to a print vendor, then you probably want to end on an even page. But if you are primarily interested in generating a PDF file, you may not want to. That's because many people print their PDF on a single-sided printer, and a blank last page would just be a waste.

The default for double-sided output is to end on an even page. You can customize the force.page.count template to change that behavior. Here is the original template:

<xsl:template name="force.page.count">
  <xsl:param name="element" select="local-name(.)"/>
  <xsl:param name="master-reference" select="''"/>

  <xsl:choose>
    <!-- double-sided output -->
    <xsl:when test="$double.sided != 0">end-on-even</xsl:when>
    <!-- single-sided output -->
    <xsl:otherwise>no-force</xsl:otherwise>
  </xsl:choose>
</xsl:template>

The two choices are:

end-on-even

This will force a blank even-numbered page if the document content ends on an odd page.

no-force

No blank page is generated at the end.